From patchwork Wed Jul 25 17:44:01 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Konke Radlow X-Patchwork-Id: 1238571 Return-Path: X-Original-To: patchwork-linux-media@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork2.kernel.org (Postfix) with ESMTP id 29E3EDFFCD for ; Wed, 25 Jul 2012 15:54:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755313Ab2GYPyp (ORCPT ); Wed, 25 Jul 2012 11:54:45 -0400 Received: from ams-iport-4.cisco.com ([144.254.224.147]:9226 "EHLO ams-iport-4.cisco.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755116Ab2GYPyn (ORCPT ); Wed, 25 Jul 2012 11:54:43 -0400 X-Greylist: delayed 588 seconds by postgrey-1.27 at vger.kernel.org; Wed, 25 Jul 2012 11:54:39 EDT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=cisco.com; i=kradlow@cisco.com; l=30936; q=dns/txt; s=iport; t=1343231682; x=1344441282; h=from:to:cc:subject:date:message-id; bh=ZalmgPqXcX76rCIL2phSr2wsS7S0LFsTYL9ksRMeD98=; b=NcAk+Bc82VTqnyBw1GSo3JdL2nihNCLwuElscEcgjeJ4rS/YPSEFz6Q2 1QvxAfAycYhCqZMB36Gn0zjy1RXyx6XIZBwcSb6YzfeGtdp4a1FkHHOIe d2o+DEntI3u6ijGCn8QIjuFELHGRbjIPJC9qmIiLVIbqGdYtqmMYf7bXL k=; X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: Av0EAJcTEFCQ/khM/2dsb2JhbABFuWOBB4IhAQEEEgEHDVIQRgshNhkih1wDDJsijxaHXA2JToplaAkMhl8DizKIQ4FUgRSFN4Q/gx2BZoJfgVYB X-IronPort-AV: E=Sophos;i="4.77,653,1336348800"; d="scan'208";a="6891633" Received: from ams-core-3.cisco.com ([144.254.72.76]) by ams-iport-4.cisco.com with ESMTP; 25 Jul 2012 15:44:50 +0000 Received: from rds-testing-box.rd.tandberg.com ([10.47.19.80]) by ams-core-3.cisco.com (8.14.5/8.14.5) with ESMTP id q6PFinvi025935; Wed, 25 Jul 2012 15:44:50 GMT From: Konke Radlow To: linux-media@vger.kernel.org Cc: hverkuil@xs4all.nl, hdegoede@redhat.com Subject: [RFC PATCH 2/2] Initial version of RDS Control utility Signed-off-by: Konke Radlow Date: Wed, 25 Jul 2012 17:44:01 +0000 Message-Id: <89e7f656fc45f12f2cb5369738b3afd1f712674f.1343237398.git.kradlow@cisco.com> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: <1343238241-26772-1-git-send-email-kradlow@cisco.com> References: <1343238241-26772-1-git-send-email-kradlow@cisco.com> In-Reply-To: References: Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org --- Makefile.am | 3 +- configure.ac | 1 + utils/rds-ctl/Makefile.am | 5 + utils/rds-ctl/rds-ctl.cpp | 978 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 986 insertions(+), 1 deletion(-) create mode 100644 utils/rds-ctl/Makefile.am create mode 100644 utils/rds-ctl/rds-ctl.cpp diff --git a/Makefile.am b/Makefile.am index 6707f5f..47103a1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,8 @@ SUBDIRS += \ utils/v4l2-compliance \ utils/v4l2-ctl \ utils/v4l2-dbg \ - utils/v4l2-sysfs-path + utils/v4l2-sysfs-path \ + utils/rds-ctl if LINUX_OS SUBDIRS += \ diff --git a/configure.ac b/configure.ac index 1d7eb29..1ad99e6 100644 --- a/configure.ac +++ b/configure.ac @@ -28,6 +28,7 @@ AC_CONFIG_FILES([Makefile utils/v4l2-sysfs-path/Makefile utils/xc3028-firmware/Makefile utils/qv4l2/Makefile + utils/rds-ctl/Makefile contrib/freebsd/Makefile contrib/test/Makefile diff --git a/utils/rds-ctl/Makefile.am b/utils/rds-ctl/Makefile.am new file mode 100644 index 0000000..9a84257 --- /dev/null +++ b/utils/rds-ctl/Makefile.am @@ -0,0 +1,5 @@ +bin_PROGRAMS = rds-ctl + +rds_ctl_SOURCES = rds-ctl.cpp +rds_ctl_LDADD = ../../lib/libv4l2/libv4l2.la ../../lib/libv4l2rds/libv4l2rds.la + diff --git a/utils/rds-ctl/rds-ctl.cpp b/utils/rds-ctl/rds-ctl.cpp new file mode 100644 index 0000000..8ddb969 --- /dev/null +++ b/utils/rds-ctl/rds-ctl.cpp @@ -0,0 +1,978 @@ +/* + * rds-ctl.cpp is based on v4l2-ctl.cpp + * + * the following applies for all RDS related parts: + * Copyright 2012 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * Author: Konke Radlow + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_KLOG_H +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ARRAY_SIZE(arr) ((int)(sizeof(arr) / sizeof((arr)[0]))) + +typedef std::vector dev_vec; +typedef std::map dev_map; + +/* Short option list + + Please keep in alphabetical order. + That makes it easier to see which short options are still free. + + In general the lower case is used to set something and the upper + case is used to retrieve a setting. */ +enum Option { + OptSetDevice = 'd', + OptGetDriverInfo = 'D', + OptGetFreq = 'F', + OptSetFreq = 'f', + OptHelp = 'h', + OptReadRds = 'R', + OptGetTuner = 'T', + OptSetTuner = 't', + OptUseWrapper = 'w', + OptAll = 128, + OptFreqSeek, + OptListDevices, + OptOpenFile, + OptPrintBlock, + OptSilent, + OptTunerIndex, + OptVerbose, + OptWaitLimit, + OptLast = 256 +}; + +struct ctl_parameters { + bool terminate_decoding; + char options[OptLast]; + char fd_name[80]; + bool filemode_active; + double freq; + uint32_t wait_limit; + uint8_t tuner_index; + struct v4l2_hw_freq_seek freq_seek; +}; + +static struct ctl_parameters params; +static int app_result; + +static struct option long_options[] = { + {"all", no_argument, 0, OptAll}, + {"device", required_argument, 0, OptSetDevice}, + {"file", required_argument, 0, OptOpenFile}, + {"freq-seek", required_argument, 0, OptFreqSeek}, + {"get-freq", no_argument, 0, OptGetFreq}, + {"get-tuner", no_argument, 0, OptGetTuner}, + {"help", no_argument, 0, OptHelp}, + {"info", no_argument, 0, OptGetDriverInfo}, + {"list-devices", no_argument, 0, OptListDevices}, + {"print-block", no_argument, 0, OptPrintBlock}, + {"read-rds", no_argument, 0, OptReadRds}, + {"set-freq", required_argument, 0, OptSetFreq}, + {"tuner-index", required_argument, 0, OptTunerIndex}, + {"verbose", no_argument, 0, OptVerbose}, + {"wait-limit", required_argument, 0, OptWaitLimit}, + {"wrapper", no_argument, 0, OptUseWrapper}, + {0, 0, 0, 0} +}; + +static void usage_hint(void) +{ + fprintf(stderr, "Try 'rds-ctl --help' for more information.\n"); +} + +static void usage_common(void) +{ + printf("\nGeneral/Common options:\n" + " --all display all information available\n" + " -D, --info show driver info [VIDIOC_QUERYCAP]\n" + " -d, --device= use device \n" + " if is a single digit, then /dev/video is used\n" + " default: checks for RDS-capable devices,\n" + " uses device with lowest ID\n" + " -h, --help display this help message\n" + " -w, --wrapper use the libv4l2 wrapper library.\n" + " --list-devices list all v4l radio devices with RDS capabilities\n" + ); +} + +static void usage_tuner(void) +{ + printf("\nTuner/Modulator options:\n" + " -F, --get-freq query the frequency [VIDIOC_G_FREQUENCY]\n" + " -f, --set-freq=\n" + " set the frequency to MHz [VIDIOC_S_FREQUENCY]\n" + " -T, --get-tuner query the tuner settings [VIDIOC_G_TUNER]\n" + " --tuner-index= Use idx as tuner idx for tuner/modulator commands\n" + " --freq-seek=dir=<0/1>,wrap=<0/1>,spacing=\n" + " perform a hardware frequency seek [VIDIOC_S_HW_FREQ_SEEK]\n" + " dir is 0 (seek downward) or 1 (seek upward)\n" + " wrap is 0 (do not wrap around) or 1 (wrap around)\n" + " spacing sets the seek resolution (use 0 for default)\n" + ); +} + +static void usage_rds(void) +{ + printf("\nRDS options: \n" + " -R, --read-rds\n" + " enable reading of RDS data from device\n" + " --file=\n" + " open a RDS stream file dump instead of a device\n" + " all General and Tuner Options are disabled in this mode\n" + " --wait-limit=\n" + " defines the maximum wait duration for avaibility of new\n" + " RDS data\n" + " : 5000ms\n" + " --print-block\n" + " prints all valid RDS fields, whenever a value is updated\n" + " instead of printing only updated values\n" + " --verbose\n" + " turn on verbose mode - every received RDS group\n" + " will be printed\n" + ); +} + +static void usage(void) +{ + printf("Usage:\n"); + usage_common(); + usage_tuner(); + usage_rds(); +} + +static void signal_handler_interrupt(int signum) +{ + fprintf(stderr, "Interrupt received: Terminating program\n"); + params.terminate_decoding = true; +} + +static int test_open(const char *file, int oflag) +{ + return params.options[OptUseWrapper] ? v4l2_open(file, oflag) : open(file, oflag); +} + +static int test_close(int fd) +{ + return params.options[OptUseWrapper] ? v4l2_close(fd) : close(fd); +} + +static int test_ioctl(int fd, int cmd, void *arg) +{ + return params.options[OptUseWrapper] ? v4l2_ioctl(fd, cmd, arg) : ioctl(fd, cmd, arg); +} + +static int doioctl_name(int fd, unsigned long int request, void *parm, const char *name) +{ + int retval = test_ioctl(fd, request, parm); + + if (retval < 0) { + app_result = -1; + } + if (params.options[OptSilent]) return retval; + if (retval < 0) + printf("%s: failed: %s\n", name, strerror(errno)); + else if (params.options[OptVerbose]) + printf("%s: ok\n", name); + + return retval; +} + +#define doioctl(n, r, p) doioctl_name(n, r, p, #r) + +static const char *audmode2s(int audmode) +{ + switch (audmode) { + case V4L2_TUNER_MODE_STEREO: return "stereo"; + case V4L2_TUNER_MODE_LANG1: return "lang1"; + case V4L2_TUNER_MODE_LANG2: return "lang2"; + case V4L2_TUNER_MODE_LANG1_LANG2: return "bilingual"; + case V4L2_TUNER_MODE_MONO: return "mono"; + default: return "unknown"; + } +} + +static std::string rxsubchans2s(int rxsubchans) +{ + std::string s; + + if (rxsubchans & V4L2_TUNER_SUB_MONO) + s += "mono "; + if (rxsubchans & V4L2_TUNER_SUB_STEREO) + s += "stereo "; + if (rxsubchans & V4L2_TUNER_SUB_LANG1) + s += "lang1 "; + if (rxsubchans & V4L2_TUNER_SUB_LANG2) + s += "lang2 "; + if (rxsubchans & V4L2_TUNER_SUB_RDS) + s += "rds "; + return s; +} + +static std::string tcap2s(unsigned cap) +{ + std::string s; + + if (cap & V4L2_TUNER_CAP_LOW) + s += "62.5 Hz "; + else + s += "62.5 kHz "; + if (cap & V4L2_TUNER_CAP_NORM) + s += "multi-standard "; + if (cap & V4L2_TUNER_CAP_HWSEEK_BOUNDED) + s += "hwseek-bounded "; + if (cap & V4L2_TUNER_CAP_HWSEEK_WRAP) + s += "hwseek-wrap "; + if (cap & V4L2_TUNER_CAP_STEREO) + s += "stereo "; + if (cap & V4L2_TUNER_CAP_LANG1) + s += "lang1 "; + if (cap & V4L2_TUNER_CAP_LANG2) + s += "lang2 "; + if (cap & V4L2_TUNER_CAP_RDS) + s += "rds "; + if (cap & V4L2_TUNER_CAP_RDS_BLOCK_IO) + s += "rds block I/O "; + if (cap & V4L2_TUNER_CAP_RDS_CONTROLS) + s += "rds control "; + return s; +} + +static std::string cap2s(unsigned cap) +{ + std::string s; + + if (cap & V4L2_CAP_RDS_CAPTURE) + s += "\t\tRDS Capture\n"; + if (cap & V4L2_CAP_RDS_OUTPUT) + s += "\t\tRDS Output\n"; + if (cap & V4L2_CAP_TUNER) + s += "\t\tTuner\n"; + if (cap & V4L2_CAP_MODULATOR) + s += "\t\tModulator\n"; + if (cap & V4L2_CAP_AUDIO) + s += "\t\tAudio\n"; + if (cap & V4L2_CAP_RADIO) + s += "\t\tRadio\n"; + if (cap & V4L2_CAP_READWRITE) + s += "\t\tRead/Write\n"; + if (cap & V4L2_CAP_ASYNCIO) + s += "\t\tAsync I/O\n"; + if (cap & V4L2_CAP_STREAMING) + s += "\t\tStreaming\n"; + if (cap & V4L2_CAP_DEVICE_CAPS) + s += "\t\tDevice Capabilities\n"; + return s; +} + +static bool is_radio_dev(const char *name) +{ + return !memcmp(name, "radio", 5); +} + +static int calc_node_val(const char *s) +{ + int n = 0; + + s = strrchr(s, '/') + 1; + if (!memcmp(s, "video", 5)) n = 0; + else if (!memcmp(s, "radio", 5)) n = 0x100; + else if (!memcmp(s, "vbi", 3)) n = 0x200; + else if (!memcmp(s, "vtx", 3)) n = 0x300; + n += atol(s + (n >= 0x200 ? 3 : 5)); + return n; +} + +static bool sort_on_device_name(const std::string &s1, const std::string &s2) +{ + int n1 = calc_node_val(s1.c_str()); + int n2 = calc_node_val(s2.c_str()); + + return n1 < n2; +} + +static void print_devices(dev_vec files) +{ + dev_map cards; + int fd = -1; + std::string bus_info; + struct v4l2_capability vcap; + + for (dev_vec::iterator iter = files.begin(); + iter != files.end(); ++iter) { + fd = open(iter->c_str(), O_RDWR); + memset(&vcap, 0, sizeof(vcap)); + if (fd < 0) + continue; + doioctl(fd, VIDIOC_QUERYCAP, &vcap); + close(fd); + bus_info = (const char *)vcap.bus_info; + if (cards[bus_info].empty()) + cards[bus_info] += std::string((char *)vcap.card)\ + + " (" + bus_info + "):\n"; + cards[bus_info] += "\t" + (*iter); + cards[bus_info] += "\n"; + } + for (dev_map::iterator iter = cards.begin(); + iter != cards.end(); ++iter) { + printf("%s\n", iter->second.c_str()); + } +} +static dev_vec list_devices(void) +{ + DIR *dp; + struct dirent *ep; + dev_vec files; + dev_map links; + + struct v4l2_tuner vt; + + dp = opendir("/dev"); + if (dp == NULL) { + perror ("Couldn't open the directory"); + exit(1); + } + while ((ep = readdir(dp))) + if (is_radio_dev(ep->d_name)) + files.push_back(std::string("/dev/") + ep->d_name); + closedir(dp); + + /* Find device nodes which are links to other device nodes */ + for (dev_vec::iterator iter = files.begin(); \ + iter != files.end(); ) { + char link[64+1]; + int link_len; + std::string target; + + link_len = readlink(iter->c_str(), link, 64); + if (link_len < 0) { /* Not a link or error */ + iter++; + continue; + } + link[link_len] = '\0'; + + /* Only remove from files list if target itself is in list */ + if (link[0] != '/') /* Relative link */ + target = std::string("/dev/"); + target += link; + if (find(files.begin(), files.end(), target) == files.end()) { + iter++; + continue; + } + files.erase(iter); + } + /* Iterate through all devices, and remove all non-accessible devices + * and all devices that don't offer the RDS_BLOCK_IO capability */ + std::sort(files.begin(), files.end(), sort_on_device_name); + for (dev_vec::iterator iter = files.begin(); \ + iter != files.end(); ++iter) { + int fd = open(iter->c_str(), O_RDONLY | O_NONBLOCK); + std::string bus_info; + + if (fd < 0) { + iter = files.erase(iter); + continue; + } + memset(&vt, 0, sizeof(vt)); + if (doioctl(fd, VIDIOC_G_TUNER, &vt) != 0) { + close(fd); + iter = files.erase(iter); + continue; + } + /* remove device if it doesn't support rds block I/O */ + if (!vt.capability & V4L2_TUNER_CAP_RDS_BLOCK_IO) + iter = files.erase(iter); + } + return files; +} + +static int parse_subopt(char **subs, const char * const *subopts, char **value) +{ + int opt = getsubopt(subs, (char * const *)subopts, value); + + if (opt == -1) { + fprintf(stderr, "Invalid suboptions specified\n"); + return -1; + } + if (value == NULL) { + fprintf(stderr, "No value given to suboption <%s>\n", + subopts[opt]); + return -1; + } + return opt; +} + +static void parse_freq_seek(char *optarg, struct v4l2_hw_freq_seek &seek) +{ + char *value; + char *subs = optarg; + + while (*subs != '\0') { + static const char *const subopts[] = { + "dir", + "wrap", + "spacing", + NULL + }; + + switch (parse_subopt(&subs, subopts, &value)) { + case 0: + seek.seek_upward = strtol(value, 0L, 0); + break; + case 1: + seek.wrap_around = strtol(value, 0L, 0); + break; + case 2: + seek.spacing = strtol(value, 0L, 0); + break; + default: + usage_tuner(); + exit(1); + } + } +} + +static void print_byte(char byte, bool linebreak) +{ + int count=8; + printf(" "); + while(count--) + { + printf("%d", ( byte & 128 ) ? 1 : 0 ); + byte <<= 1; + } + if(linebreak) printf("\n"); +} + +static void print_rds_group(const struct v4l2_rds_group *rds_group) +{ + printf("\nComplete RDS data group received \n"); + printf("PI: %04x\n", rds_group->pi); + printf("Group: %u%c \n", rds_group->group_id, rds_group->group_version); + printf("Traffic Program: %s \n", (rds_group->traffic_prog)? "yes" : "no"); + printf("Program Type: %u \n", rds_group->pty); + printf("Data B:"); + print_byte(rds_group->data_b_lsb, true); + printf("Data C:"); + print_byte(rds_group->data_c_msb, false); + print_byte(rds_group->data_c_lsb, true); + printf("Data D:"); + print_byte(rds_group->data_d_msb, false); + print_byte(rds_group->data_d_lsb, true); + printf("\n"); +} + +static void print_decoder_info(uint8_t di) +{ + printf("\nDI: "); + if (di & V4L2_RDS_FLAG_STEREO) + printf("Stereo, "); + else + printf("Mono, "); + if (di & V4L2_RDS_FLAG_ARTIFICIAL_HEAD) + printf("Artificial Head, "); + else + printf("No Artificial Head, "); + if (di & V4L2_RDS_FLAG_COMPRESSED) + printf("Compressed"); + else + printf("Not Compressed"); +} + +static void print_rds_statistics(struct v4l2_rds_statistics *statistics) +{ + printf("\n\nRDS Statistics: \n"); + printf("received blocks / received groups: %u / %u\n",\ + statistics->block_cnt, statistics->group_cnt); + + float block_error_percentage = \ + ((float)statistics->block_error_cnt / statistics->block_cnt) * 100.0; + printf("block errors / group errors: %u (%3.2f%%) / %u \n",\ + statistics->block_error_cnt, + block_error_percentage, statistics->group_error_cnt); + float block_corrected_percentage = \ + ((float)statistics->block_corrected_cnt / statistics->block_cnt) * 100.0; + printf("corrected blocks: %u (%3.2f%%)\n",\ + statistics->block_corrected_cnt, block_corrected_percentage); + for(int i=0; i<16; i++) + printf("Group %02d: %u\n", i, statistics->group_type_cnt[i]); +} + +static void print_rds_af(struct v4l2_rds_af_set *af_set) +{ + int counter = 0; + + printf("\nAnnounced AFs: %u", af_set->announced_af); + for (int i = 0; i < af_set->size && i < af_set->announced_af; i++, counter++) { + if (af_set->af[i] >= 87500000 ) { + printf("\nAF%02d: %.1fMHz", counter, af_set->af[i] / 1000000.0); + continue; + } + printf("\nAF%02d: %.1fkHz", counter, af_set->af[i] / 1000.0); + } +} + +static void print_rds_pi(const struct v4l2_rds *handle) +{ + printf("\nArea Coverage: %s", v4l2_rds_get_coverage_str(handle)); +} + +static void print_rds_data(struct v4l2_rds *handle, uint32_t updated_fields) +{ + if (params.options[OptPrintBlock]) + updated_fields = 0xFFFFFFFF; + + if (updated_fields & V4L2_RDS_PI && + handle->valid_fields & V4L2_RDS_PI) { + printf("\nPI: %04x", handle->pi); + print_rds_pi(handle); + } + + if (updated_fields & V4L2_RDS_PS && + handle->valid_fields & V4L2_RDS_PS) { + printf("\nPS: "); + for (int i = 0; i < 8; ++i) { + /* filter out special characters */ + if (handle->ps[i] >= 0x20 && handle->ps[i] <= 0x7E) + printf("%lc",handle->ps[i]); + else + printf(" "); + } + } + + if (updated_fields & V4L2_RDS_PTY && handle->valid_fields & V4L2_RDS_PTY) + printf("\nPTY: %0u -> %s",handle->pty,\ + v4l2_rds_get_pty_str(handle)); + + if (updated_fields & V4L2_RDS_PTYN && handle->valid_fields & V4L2_RDS_PTYN) { + printf("\nPTYN: "); + for (int i = 0; i < 8; ++i) { + /* filter out special characters */ + if (handle->ptyn[i] >= 0x20 && handle->ptyn[i] <= 0x7E) + printf("%lc",handle->ptyn[i]); + else + printf(" "); + } + } + + if(updated_fields & V4L2_RDS_RT && handle->valid_fields & V4L2_RDS_RT){ + printf("\nRT: "); + for (int i = 0; i < handle->rt_length; ++i) { + /* filter out special characters */ + if (handle->rt[i] >= 0x20 && handle->rt[i] <= 0x7E) + printf("%lc",handle->rt[i]); + else + printf(" "); + } + } + + if (updated_fields & V4L2_RDS_TP && handle->valid_fields & V4L2_RDS_TP) + printf("\nTP: %s TA: %s", (handle->tp)? "yes":"no",\ + handle->ta? "yes":"no"); + if (updated_fields & V4L2_RDS_MS && handle->valid_fields & V4L2_RDS_MS) + printf("\nMS Flag: %s", (handle->ms)? "Music" : "Speech"); + if (updated_fields & V4L2_RDS_ECC && handle->valid_fields & V4L2_RDS_ECC) + printf("\nECC: %X%x, Country: %u -> %s",\ + handle->ecc >> 4, handle->ecc & 0x0f, handle->pi >> 12, \ + v4l2_rds_get_country_str(handle)); + if (updated_fields & V4L2_RDS_LC && handle->valid_fields & V4L2_RDS_LC) + printf("\nLanguage: %u -> %s ", handle->lc,\ + v4l2_rds_get_language_str(handle)); + if (updated_fields & V4L2_RDS_DI && handle->valid_fields & V4L2_RDS_DI) + print_decoder_info(handle->di); + if (updated_fields & V4L2_RDS_ODA && + handle->decode_information & V4L2_RDS_ODA) { + for (int i = 0; i < handle->rds_oda.size; ++i) + printf("\nODA Group: %02u%c, AID: %08x",handle->rds_oda.oda[i].group_id, + handle->rds_oda.oda[i].group_version, handle->rds_oda.oda[i].aid); + } + if (updated_fields & V4L2_RDS_AF && handle->valid_fields & V4L2_RDS_AF) + print_rds_af(&handle->rds_af); + if (params.options[OptPrintBlock]) + printf("\n"); +} + +static void read_rds(struct v4l2_rds *handle, const int fd, const int wait_limit) +{ + int byte_cnt = 0; + int error_cnt = 0; + uint32_t updated_fields = 0x00; + struct v4l2_rds_data rds_data; /* read buffer for rds blocks */ + + while(!params.terminate_decoding){ + memset(&rds_data, 0, sizeof(rds_data)); + if ((byte_cnt=read(fd, &rds_data, 3)) != 3) { + if(byte_cnt == 0){ + printf("\nEnd of input file reached \n"); + break; + } else if(++error_cnt > 2) { + fprintf(stderr, "\nError reading from "\ + "device (no RDS data available)\n"); + break; + } + /* wait for new data to arrive: transmission of 1 + * group takes ~88.7ms */ + usleep(wait_limit * 1000); + } + else if (byte_cnt == 3) { + error_cnt = 0; + /* true if a new group was decoded */ + if ((updated_fields = v4l2_rds_add(handle, &rds_data))) { + print_rds_data(handle, updated_fields); + if (params.options[OptVerbose]) + print_rds_group(v4l2_rds_get_group(handle)); + } + } + } +} + +static void read_rds_from_fd(const int fd) +{ + struct v4l2_rds *rds_handle; + + /* create an rds handle for the current device */ + if (!(rds_handle = v4l2_rds_create(true))) { + fprintf(stderr, "Failed to init RDS lib: %s\n", strerror(errno)); + exit(1); + } + + /* try to receive and decode RDS data */ + read_rds(rds_handle, fd, params.wait_limit); + print_rds_statistics(&rds_handle->rds_statistics); + + v4l2_rds_destroy(rds_handle); +} + +static int parse_cl(int argc, char **argv) +{ + int i = 0; + int idx = 0; + int opt = 0; + char short_options[26 * 2 * 2 + 1]; + + if (argc == 1) { + usage_hint(); + exit(1); + } + for (i = 0; long_options[i].name; i++) { + if (!isalpha(long_options[i].val)) + continue; + short_options[idx++] = long_options[i].val; + if (long_options[i].has_arg == required_argument) + short_options[idx++] = ':'; + } + while (1) { + // TODO: remove option_index ? + int option_index = 0; + + short_options[idx] = 0; + opt = getopt_long(argc, argv, short_options, + long_options, &option_index); + if (opt == -1) + break; + + params.options[(int)opt] = 1; + switch (opt) { + case OptSetDevice: + strncpy(params.fd_name, optarg, 80); + if (optarg[0] >= '0' && optarg[0] <= '9' && optarg[1] == 0) { + char newdev[20]; + char dev = optarg[0]; + + sprintf(newdev, "/dev/radio%c", dev); + strncpy(params.fd_name, newdev, 20); + } + break; + case OptSetFreq: + params.freq = strtod(optarg, NULL); + break; + case OptListDevices: + print_devices(list_devices()); + break; + case OptFreqSeek: + parse_freq_seek(optarg, params.freq_seek); + break; + case OptTunerIndex: + params.tuner_index = strtoul(optarg, NULL, 0); + break; + case OptOpenFile: + { + if (access(optarg, F_OK) != -1) { + params.filemode_active = true; + strncpy(params.fd_name, optarg, 80); + } else { + fprintf(stderr, "Unable to open file: %s\n", optarg); + return -1; + } + /* enable the read-rds option by default for convenience */ + params.options[OptReadRds] = 1; + break; + } + case OptWaitLimit: + params.wait_limit = strtoul(optarg, NULL, 0); + break; + case ':': + fprintf(stderr, "Option '%s' requires a value\n", + argv[optind]); + usage_hint(); + return 1; + case '?': + if (argv[optind]) + fprintf(stderr, "Unknown argument '%s'\n", argv[optind]); + usage_hint(); + return 1; + } + } + if (optind < argc) { + printf("unknown arguments: "); + while (optind < argc) + printf("%s ", argv[optind++]); + printf("\n"); + usage_hint(); + return 1; + } + if (params.options[OptAll]) { + params.options[OptGetDriverInfo] = 1; + params.options[OptGetFreq] = 1; + params.options[OptGetTuner] = 1; + params.options[OptSilent] = 1; + } + + return 0; +} + +static void print_driver_info(const struct v4l2_capability *vcap) +{ + + printf("Driver Info (%susing libv4l2):\n", + params.options[OptUseWrapper] ? "" : "not "); + printf("\tDriver name : %s\n", vcap->driver); + printf("\tCard type : %s\n", vcap->card); + printf("\tBus info : %s\n", vcap->bus_info); + printf("\tDriver version: %d.%d.%d\n", + vcap->version >> 16, + (vcap->version >> 8) & 0xff, + vcap->version & 0xff); + printf("\tCapabilities : 0x%08X\n", vcap->capabilities); + printf("%s", cap2s(vcap->capabilities).c_str()); + if (vcap->capabilities & V4L2_CAP_DEVICE_CAPS) { + printf("\tDevice Caps : 0x%08X\n", vcap->device_caps); + printf("%s", cap2s(vcap->device_caps).c_str()); + } +} + +static void set_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\ + struct v4l2_modulator *modulator, struct v4l2_tuner *tuner) +{ + int mode = -1; /* set audio mode */ + + if (params.options[OptSetFreq]) { + double fac = 16; + + if (capabilities & V4L2_CAP_MODULATOR) { + vf->type = V4L2_TUNER_RADIO; + modulator->index = params.tuner_index; + if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0) { + fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16; + } + } else { + vf->type = V4L2_TUNER_ANALOG_TV; + tuner->index = params.tuner_index; + if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) { + fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16; + vf->type = tuner->type; + } + } + vf->tuner = params.tuner_index; + vf->frequency = __u32(params.freq * fac); + if (doioctl(fd, VIDIOC_S_FREQUENCY, vf) == 0) + printf("Frequency for tuner %d set to %d (%f MHz)\n", + vf->tuner, vf->frequency, vf->frequency / fac); + } + + if (params.options[OptSetTuner]) { + struct v4l2_tuner vt; + + memset(&vt, 0, sizeof(struct v4l2_tuner)); + vt.index = params.tuner_index; + if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) { + if (mode != -1) + vt.audmode = mode; + doioctl(fd, VIDIOC_S_TUNER, &vt); + } + } + + if (params.options[OptFreqSeek]) { + params.freq_seek.tuner = params.tuner_index; + params.freq_seek.type = V4L2_TUNER_RADIO; + doioctl(fd, VIDIOC_S_HW_FREQ_SEEK, ¶ms.freq_seek); + } +} + +static void get_options(const int fd, const int capabilities, struct v4l2_frequency *vf,\ + struct v4l2_modulator *modulator, struct v4l2_tuner *tuner) +{ + if (params.options[OptGetFreq]) { + double fac = 16; + + if (capabilities & V4L2_CAP_MODULATOR) { + vf->type = V4L2_TUNER_RADIO; + modulator->index = params.tuner_index; + if (doioctl(fd, VIDIOC_G_MODULATOR, modulator) == 0) + fac = (modulator->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16; + } else { + vf->type = V4L2_TUNER_ANALOG_TV; + tuner->index = params.tuner_index; + if (doioctl(fd, VIDIOC_G_TUNER, tuner) == 0) { + fac = (tuner->capability & V4L2_TUNER_CAP_LOW) ? 16000 : 16; + vf->type = tuner->type; + } + } + vf->tuner = params.tuner_index; + if (doioctl(fd, VIDIOC_G_FREQUENCY, vf) == 0) + printf("Frequency for tuner %d: %d (%f MHz)\n", + vf->tuner, vf->frequency, vf->frequency / fac); + } + + if (params.options[OptGetTuner]) { + struct v4l2_tuner vt; + + memset(&vt, 0, sizeof(struct v4l2_tuner)); + vt.index = params.tuner_index; + if (doioctl(fd, VIDIOC_G_TUNER, &vt) == 0) { + printf("Tuner %d:\n", vt.index); + printf("\tName : %s\n", vt.name); + printf("\tCapabilities : %s\n",\ + tcap2s(vt.capability).c_str()); + if (vt.capability & V4L2_TUNER_CAP_LOW) + printf("\tFrequency range : %.1f MHz - %.1f MHz\n", + vt.rangelow / 16000.0, vt.rangehigh / 16000.0); + else + printf("\tFrequency range : %.1f MHz - %.1f MHz\n", + vt.rangelow / 16.0, vt.rangehigh / 16.0); + printf("\tSignal strength/AFC : %d%%/%d\n",\ + (int)((vt.signal / 655.35)+0.5), vt.afc); + printf("\tCurrent audio mode : %s\n", audmode2s(vt.audmode)); + printf("\tAvailable subchannels: %s\n", + rxsubchans2s(vt.rxsubchans).c_str()); + } + } +} + +int main(int argc, char **argv) +{ + int fd = -1; + + /* command args */ + struct v4l2_tuner tuner; /* set_freq/get_freq */ + struct v4l2_modulator modulator;/* set_freq/get_freq */ + struct v4l2_capability vcap; /* list_cap */ + struct v4l2_frequency vf; /* get_freq/set_freq */ + + memset(&tuner, 0, sizeof(tuner)); + memset(&modulator, 0, sizeof(modulator)); + memset(&vcap, 0, sizeof(vcap)); + memset(&vf, 0, sizeof(vf)); + strcpy(params.fd_name, "/dev/radio0"); + + /* define locale for unicode support */ + if (!setlocale(LC_CTYPE, "")) { + fprintf(stderr, "Can't set the specified locale!\n"); + return 1; + } + /* register signal handler for interrupt signal, to exit gracefully */ + signal(SIGINT, signal_handler_interrupt); + + /* try to parse the command line */ + parse_cl(argc, argv); + if (params.options[OptHelp]) { + usage(); + exit(0); + } + + /* File Mode: disables all other features, except for RDS decoding */ + if (params.filemode_active) { + if ((fd = open(params.fd_name, O_RDONLY|O_NONBLOCK)) < 0){ + perror("error opening file"); + exit(1); + } + read_rds_from_fd(fd); + test_close(fd); + exit(0); + } + + /* Device Mode: open the radio device as read-only and non-blocking */ + if (!params.options[OptSetDevice]) { + /* check the system for RDS capable devices */ + dev_vec devices = list_devices(); + if (devices.size() == 0) { + fprintf(stderr, "No RDS-capable device found\n"); + exit(1); + } + strncpy(params.fd_name, devices[0].c_str(), 80); + printf("Using device: %s\n", params.fd_name); + } + if ((fd = test_open(params.fd_name, O_RDONLY | O_NONBLOCK)) < 0) { + fprintf(stderr, "Failed to open %s: %s\n", params.fd_name, + strerror(errno)); + exit(1); + } + doioctl(fd, VIDIOC_QUERYCAP, &vcap); + + /* Info options */ + if (params.options[OptGetDriverInfo]) + print_driver_info(&vcap); + /* Set options */ + set_options(fd, vcap.capabilities, &vf, &modulator, &tuner); + /* Get options */ + get_options(fd, vcap.capabilities, &vf, &modulator, &tuner); + /* RDS decoding */ + if (params.options[OptReadRds]) + read_rds_from_fd(fd); + + test_close(fd); + exit(app_result); +}