diff options
-rw-r--r-- | COPYING | 339 | ||||
-rw-r--r-- | convenience/convenience.c | 304 | ||||
-rw-r--r-- | convenience/convenience.h | 142 | ||||
-rw-r--r-- | radio_output.cpp | 152 | ||||
-rw-r--r-- | radio_output.h | 75 | ||||
-rw-r--r-- | rtl_fm.c | 1117 | ||||
-rw-r--r-- | rtl_fm.h | 50 | ||||
-rw-r--r-- | rtlfmradio.pro | 7 | ||||
-rw-r--r-- | rtlfmradiotunercontrol.cpp | 225 |
9 files changed, 2314 insertions, 97 deletions
@@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/convenience/convenience.c b/convenience/convenience.c new file mode 100644 index 0000000..517dc4e --- /dev/null +++ b/convenience/convenience.c @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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, see <http://www.gnu.org/licenses/>. + */ + +/* a collection of user friendly tools + * todo: use strtol for more flexible int parsing + * */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef _WIN32 +#include <unistd.h> +#else +#include <windows.h> +#include <fcntl.h> +#include <io.h> +#define _USE_MATH_DEFINES +#endif + +#include <math.h> + +#include "rtl-sdr.h" + +double atofs(char *s) +/* standard suffixes */ +{ + char last; + int len; + double suff = 1.0; + len = strlen(s); + last = s[len-1]; + s[len-1] = '\0'; + switch (last) { + case 'g': + case 'G': + suff *= 1e3; + case 'm': + case 'M': + suff *= 1e3; + case 'k': + case 'K': + suff *= 1e3; + suff *= atof(s); + s[len-1] = last; + return suff; + } + s[len-1] = last; + return atof(s); +} + +double atoft(char *s) +/* time suffixes, returns seconds */ +{ + char last; + int len; + double suff = 1.0; + len = strlen(s); + last = s[len-1]; + s[len-1] = '\0'; + switch (last) { + case 'h': + case 'H': + suff *= 60; + case 'm': + case 'M': + suff *= 60; + case 's': + case 'S': + suff *= atof(s); + s[len-1] = last; + return suff; + } + s[len-1] = last; + return atof(s); +} + +double atofp(char *s) +/* percent suffixes */ +{ + char last; + int len; + double suff = 1.0; + len = strlen(s); + last = s[len-1]; + s[len-1] = '\0'; + switch (last) { + case '%': + suff *= 0.01; + suff *= atof(s); + s[len-1] = last; + return suff; + } + s[len-1] = last; + return atof(s); +} + +int nearest_gain(rtlsdr_dev_t *dev, int target_gain) +{ + int i, r, err1, err2, count, nearest; + int* gains; + r = rtlsdr_set_tuner_gain_mode(dev, 1); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); + return r; + } + count = rtlsdr_get_tuner_gains(dev, NULL); + if (count <= 0) { + return 0; + } + gains = malloc(sizeof(int) * count); + count = rtlsdr_get_tuner_gains(dev, gains); + nearest = gains[0]; + for (i=0; i<count; i++) { + err1 = abs(target_gain - nearest); + err2 = abs(target_gain - gains[i]); + if (err2 < err1) { + nearest = gains[i]; + } + } + free(gains); + return nearest; +} + +int verbose_set_frequency(rtlsdr_dev_t *dev, uint32_t frequency) +{ + int r; + r = rtlsdr_set_center_freq(dev, frequency); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to set center freq.\n"); + } else { + fprintf(stderr, "Tuned to %u Hz.\n", frequency); + } + return r; +} + +int verbose_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate) +{ + int r; + r = rtlsdr_set_sample_rate(dev, samp_rate); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to set sample rate.\n"); + } else { + fprintf(stderr, "Sampling at %u S/s.\n", samp_rate); + } + return r; +} + +int verbose_direct_sampling(rtlsdr_dev_t *dev, int on) +{ + int r; + r = rtlsdr_set_direct_sampling(dev, on); + if (r != 0) { + fprintf(stderr, "WARNING: Failed to set direct sampling mode.\n"); + return r; + } + if (on == 0) { + fprintf(stderr, "Direct sampling mode disabled.\n");} + if (on == 1) { + fprintf(stderr, "Enabled direct sampling mode, input 1/I.\n");} + if (on == 2) { + fprintf(stderr, "Enabled direct sampling mode, input 2/Q.\n");} + return r; +} + +int verbose_offset_tuning(rtlsdr_dev_t *dev) +{ + int r; + r = rtlsdr_set_offset_tuning(dev, 1); + if (r != 0) { + fprintf(stderr, "WARNING: Failed to set offset tuning.\n"); + } else { + fprintf(stderr, "Offset tuning mode enabled.\n"); + } + return r; +} + +int verbose_auto_gain(rtlsdr_dev_t *dev) +{ + int r; + r = rtlsdr_set_tuner_gain_mode(dev, 0); + if (r != 0) { + fprintf(stderr, "WARNING: Failed to set tuner gain.\n"); + } else { + fprintf(stderr, "Tuner gain set to automatic.\n"); + } + return r; +} + +int verbose_gain_set(rtlsdr_dev_t *dev, int gain) +{ + int r; + r = rtlsdr_set_tuner_gain_mode(dev, 1); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to enable manual gain.\n"); + return r; + } + r = rtlsdr_set_tuner_gain(dev, gain); + if (r != 0) { + fprintf(stderr, "WARNING: Failed to set tuner gain.\n"); + } else { + fprintf(stderr, "Tuner gain set to %0.2f dB.\n", gain/10.0); + } + return r; +} + +int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error) +{ + int r; + if (ppm_error == 0) { + return 0;} + r = rtlsdr_set_freq_correction(dev, ppm_error); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to set ppm error.\n"); + } else { + fprintf(stderr, "Tuner error set to %i ppm.\n", ppm_error); + } + return r; +} + +int verbose_reset_buffer(rtlsdr_dev_t *dev) +{ + int r; + r = rtlsdr_reset_buffer(dev); + if (r < 0) { + fprintf(stderr, "WARNING: Failed to reset buffers.\n");} + return r; +} + +int verbose_device_search(char *s) +{ + int i, device_count, device, offset; + char *s2; + char vendor[256], product[256], serial[256]; + device_count = rtlsdr_get_device_count(); + if (!device_count) { + fprintf(stderr, "No supported devices found.\n"); + return -1; + } + fprintf(stderr, "Found %d device(s):\n", device_count); + for (i = 0; i < device_count; i++) { + rtlsdr_get_device_usb_strings(i, vendor, product, serial); + fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial); + } + fprintf(stderr, "\n"); + /* does string look like raw id number */ + device = (int)strtol(s, &s2, 0); + if (s2[0] == '\0' && device >= 0 && device < device_count) { + fprintf(stderr, "Using device %d: %s\n", + device, rtlsdr_get_device_name((uint32_t)device)); + return device; + } + /* does string exact match a serial */ + for (i = 0; i < device_count; i++) { + rtlsdr_get_device_usb_strings(i, vendor, product, serial); + if (strcmp(s, serial) != 0) { + continue;} + device = i; + fprintf(stderr, "Using device %d: %s\n", + device, rtlsdr_get_device_name((uint32_t)device)); + return device; + } + /* does string prefix match a serial */ + for (i = 0; i < device_count; i++) { + rtlsdr_get_device_usb_strings(i, vendor, product, serial); + if (strncmp(s, serial, strlen(s)) != 0) { + continue;} + device = i; + fprintf(stderr, "Using device %d: %s\n", + device, rtlsdr_get_device_name((uint32_t)device)); + return device; + } + /* does string suffix match a serial */ + for (i = 0; i < device_count; i++) { + rtlsdr_get_device_usb_strings(i, vendor, product, serial); + offset = strlen(serial) - strlen(s); + if (offset < 0) { + continue;} + if (strncmp(s, serial+offset, strlen(s)) != 0) { + continue;} + device = i; + fprintf(stderr, "Using device %d: %s\n", + device, rtlsdr_get_device_name((uint32_t)device)); + return device; + } + fprintf(stderr, "No matching devices found.\n"); + return -1; +} + +// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/convenience/convenience.h b/convenience/convenience.h new file mode 100644 index 0000000..1faa2af --- /dev/null +++ b/convenience/convenience.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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, see <http://www.gnu.org/licenses/>. + */ + +/* a collection of user friendly tools */ + +/*! + * Convert standard suffixes (k, M, G) to double + * + * \param s a string to be parsed + * \return double + */ + +double atofs(char *s); + +/*! + * Convert time suffixes (s, m, h) to double + * + * \param s a string to be parsed + * \return seconds as double + */ + +double atoft(char *s); + +/*! + * Convert percent suffixe (%) to double + * + * \param s a string to be parsed + * \return double + */ + +double atofp(char *s); + +/*! + * Find nearest supported gain + * + * \param dev the device handle given by rtlsdr_open() + * \param target_gain in tenths of a dB + * \return 0 on success + */ + +int nearest_gain(rtlsdr_dev_t *dev, int target_gain); + +/*! + * Set device frequency and report status on stderr + * + * \param dev the device handle given by rtlsdr_open() + * \param frequency in Hz + * \return 0 on success + */ + +int verbose_set_frequency(rtlsdr_dev_t *dev, uint32_t frequency); + +/*! + * Set device sample rate and report status on stderr + * + * \param dev the device handle given by rtlsdr_open() + * \param samp_rate in samples/second + * \return 0 on success + */ + +int verbose_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate); + +/*! + * Enable or disable the direct sampling mode and report status on stderr + * + * \param dev the device handle given by rtlsdr_open() + * \param on 0 means disabled, 1 I-ADC input enabled, 2 Q-ADC input enabled + * \return 0 on success + */ + +int verbose_direct_sampling(rtlsdr_dev_t *dev, int on); + +/*! + * Enable offset tuning and report status on stderr + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on success + */ + +int verbose_offset_tuning(rtlsdr_dev_t *dev); + +/*! + * Enable auto gain and report status on stderr + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on success + */ + +int verbose_auto_gain(rtlsdr_dev_t *dev); + +/*! + * Set tuner gain and report status on stderr + * + * \param dev the device handle given by rtlsdr_open() + * \param gain in tenths of a dB + * \return 0 on success + */ + +int verbose_gain_set(rtlsdr_dev_t *dev, int gain); + +/*! + * Set the frequency correction value for the device and report status on stderr. + * + * \param dev the device handle given by rtlsdr_open() + * \param ppm_error correction value in parts per million (ppm) + * \return 0 on success + */ + +int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error); + +/*! + * Reset buffer + * + * \param dev the device handle given by rtlsdr_open() + * \return 0 on success + */ + +int verbose_reset_buffer(rtlsdr_dev_t *dev); + +/*! + * Find the closest matching device. + * + * \param s a string to be parsed + * \return dev_index int, -1 on error + */ + +int verbose_device_search(char *s); + diff --git a/radio_output.cpp b/radio_output.cpp new file mode 100644 index 0000000..350bf75 --- /dev/null +++ b/radio_output.cpp @@ -0,0 +1,152 @@ +/* + * A standalone AM/FM Radio QML plugin (for RTL2832U and Maxim hardware) + * Copyright © 2015-2016 Manuel Bachmann <manuel.bachmann@iot.bzh> + * Copyright © 2016 Scott Murray <scott.murray@konsulko.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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, see <http://www.gnu.org/licenses/>. + */ + +#include <iostream> +#include "radio_output.h" +#include "rtl_fm.h" + +RadioOutputAlsa::RadioOutputAlsa() : RadioOutputImplementation(), + dev(NULL), + hw_params(NULL) +{ + unsigned int rate = 24000; + + if (snd_pcm_open(&dev, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) { + std::cerr << "Could not open primary ALSA device" << std::endl; + works = false; + return; + } + + snd_pcm_hw_params_malloc(&hw_params); + snd_pcm_hw_params_any(dev, hw_params); + + snd_pcm_hw_params_set_access (dev, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format (dev, hw_params, SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near (dev, hw_params, &rate, 0); + snd_pcm_hw_params_set_channels (dev, hw_params, 2); + + if (snd_pcm_hw_params (dev, hw_params) < 0) { + std::cerr << "Could not set hardware parameters" << std::endl; + works = false; + } else { + works = true; + } + snd_pcm_hw_params_free (hw_params); + + snd_pcm_prepare (dev); +} + +RadioOutputAlsa::~RadioOutputAlsa() +{ + snd_pcm_close (dev); +} + +bool RadioOutputAlsa::play(void *buf, int len) +{ + int16_t *cbuf = (int16_t *)buf; + int frames = len / 2; + int res; + + if ((res = snd_pcm_writei(dev, cbuf, frames)) != frames) { + snd_pcm_recover(dev, res, 0); + snd_pcm_prepare(dev); + } + //snd_pcm_drain(dev); + + return true; +} + + +RadioOutputPulse::RadioOutputPulse() : RadioOutputImplementation(), + pa(NULL), + pa_spec(NULL) +{ + int error; + + pa_spec = (pa_sample_spec*) malloc(sizeof(pa_sample_spec)); + pa_spec->format = PA_SAMPLE_S16LE; + pa_spec->rate = 24000; + pa_spec->channels = 2; + + if (!(pa = pa_simple_new(NULL, "qtmultimedia-rtlfm-radio-plugin", PA_STREAM_PLAYBACK, NULL, + "radio-output", pa_spec, NULL, NULL, &error))) { + std::cerr << "Error connecting to PulseAudio : " << pa_strerror(error) << std::endl; + works = false; + } else { + std::cerr << "RadioOutputPulse::RadioOutputPulse: Connected to PulseAudio" << std::endl; + works = true; + } + + extra = 0; + output_buf = new unsigned char[RTL_FM_MAXIMUM_BUF_LENGTH]; + + free(pa_spec); +} + +RadioOutputPulse::~RadioOutputPulse() +{ + pa_simple_free(pa); + delete [] output_buf; +} + +bool RadioOutputPulse::play(void *buf, int len) +{ + int error; + size_t n = len * 2; + void *p; + + if (!buf) { + std::cerr << "Error buf == null!" << std::endl; + return false; + } + + // Handle the rtl_fm code giving us an odd number of samples, which + // PA does not like. This extra buffer copying approach is not + // particularly efficient, but works for now. It looks feasible to + // hack in something in the demod and output thread routines in + // rtl_fm.c to handle it there if more performance is required. + p = output_buf; + if(extra) { + memcpy(output_buf, extra_buf, sizeof(int16_t)); + if((extra + len) % 2) { + // We end up with len + 1 samples, n remains the same, store the extra + memcpy(output_buf + sizeof(int16_t), buf, n - 2); + memcpy(extra_buf, ((unsigned char*) buf) + n - 2, sizeof(int16_t)); + } else { + // We end up with an extra sample + memcpy(output_buf + sizeof(int16_t), buf, n); + n += 2; + extra = 0; + } + } else if(len % 2) { + // We have an extra sample, store it, and decrease n + n -= 2; + memcpy(output_buf + sizeof(int16_t), buf, n); + memcpy(extra_buf, ((unsigned char*) buf) + n, sizeof(int16_t)); + extra = 1; + } else { + p = buf; + } + + if (pa_simple_write(pa, p, n, &error) < 0) + std::cerr << "Error writing " << n << " bytes to PulseAudio : " << pa_strerror(error) << std::endl; + //pa_simple_drain(pa, &error); + + return true; +} diff --git a/radio_output.h b/radio_output.h new file mode 100644 index 0000000..5051101 --- /dev/null +++ b/radio_output.h @@ -0,0 +1,75 @@ +/* + * A standalone AM/FM Radio QML plugin (for RTL2832U and Maxim hardware) + * Copyright © 2015-2016 Manuel Bachmann <manuel.bachmann@iot.bzh> + * Copyright © 2016 Scott Murray <scott.murray@konsulko.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RADIO_OUTPUT_H +#define RADIO_OUTPUT_H + +#include <alsa/asoundlib.h> + +#include <pulse/simple.h> +#include <pulse/error.h> + + /* + * RadioOutputImplementation is a virtual class, with 2 implementations : + * - RadioOutputAlsa ; + * - RadioOutputPulse ; + */ +class RadioOutputImplementation +{ +public: + RadioOutputImplementation() {}; + virtual ~RadioOutputImplementation() {}; + + virtual bool play(void*, int) = 0; + + bool works; +}; + + +class RadioOutputAlsa : public RadioOutputImplementation +{ +public: + RadioOutputAlsa(); + ~RadioOutputAlsa() override; + +private: + bool play(void *, int) override; + + snd_pcm_t *dev; + snd_pcm_hw_params_t *hw_params; +}; + +class RadioOutputPulse : public RadioOutputImplementation +{ +public: + RadioOutputPulse(); + ~RadioOutputPulse() override; + +private: + bool play(void *, int) override; + + pa_simple *pa; + pa_sample_spec *pa_spec; + + unsigned int extra; + int16_t extra_buf[1]; + unsigned char *output_buf; +}; + +#endif // RTLSDR_RADIO_H diff --git a/rtl_fm.c b/rtl_fm.c new file mode 100644 index 0000000..dcf28ce --- /dev/null +++ b/rtl_fm.c @@ -0,0 +1,1117 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de> + * Copyright (C) 2012 by Hoernchen <la@tfc-server.de> + * Copyright (C) 2012 by Kyle Keen <keenerd@gmail.com> + * Copyright (C) 2013 by Elias Oenal <EliasOenal@gmail.com> + * Copyright (C) 2016 by Scott Murray <scott.murray@konsulko.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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, see <http://www.gnu.org/licenses/>. + */ + +/* + * Note that this version replaces the standalone main() with separate + * init/start/stop API calls to allow building into another application. + * Other than removing the separate controller thread and adding an output + * function callback, other changes have been kept to a minimum to + * potentially allow using other rtl_fm features by modifying rtl_fm_init. + * + * December 2016, Scott Murray <scott.murray@konsulko.com> + */ + +/* + * written because people could not do real time + * FM demod on Atom hardware with GNU radio + * based on rtl_sdr.c and rtl_tcp.c + * + * lots of locks, but that is okay + * (no many-to-many locks) + * + * todo: + * sanity checks + * scale squelch to other input parameters + * test all the demodulations + * pad output on hop + * frequency ranges could be stored better + * scaled AM demod amplification + * auto-hop after time limit + * peak detector to tune onto stronger signals + * fifo for active hop frequency + * clips + * noise squelch + * merge stereo patch + * merge soft agc patch + * merge udp patch + * testmode to detect overruns + * watchdog to reset bad dongle + * fix oversampling + */ + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <math.h> +#include <pthread.h> + +#include "rtl-sdr.h" +#include "rtl_fm.h" +#include "convenience/convenience.h" + +#define DEFAULT_SAMPLE_RATE 24000 +#define DEFAULT_BUF_LENGTH RTL_FM_DEFAULT_BUF_LENGTH +#define MAXIMUM_OVERSAMPLE RTL_FM_MAXIMUM_OVERSAMPLE +#define MAXIMUM_BUF_LENGTH RTL_FM_MAXIMUM_BUF_LENGTH +#define AUTO_GAIN -100 +#define BUFFER_DUMP 4096 + +#define FREQUENCIES_LIMIT 1000 + +static volatile int do_exit = 0; +static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1}; +static int ACTUAL_BUF_LENGTH; + +static int *atan_lut = NULL; +static int atan_lut_size = 131072; /* 512 KB */ +static int atan_lut_coef = 8; + +struct dongle_state +{ + int exit_flag; + pthread_t thread; + rtlsdr_dev_t *dev; + int dev_index; + uint32_t freq; + uint32_t rate; + int gain; + uint16_t buf16[MAXIMUM_BUF_LENGTH]; + uint32_t buf_len; + int ppm_error; + int offset_tuning; + int direct_sampling; + int mute; + struct demod_state *demod_target; +}; + +struct demod_state +{ + int exit_flag; + pthread_t thread; + int16_t lowpassed[MAXIMUM_BUF_LENGTH]; + int lp_len; + int16_t lp_i_hist[10][6]; + int16_t lp_q_hist[10][6]; + int16_t result[MAXIMUM_BUF_LENGTH]; + int16_t droop_i_hist[9]; + int16_t droop_q_hist[9]; + int result_len; + int rate_in; + int rate_out; + int rate_out2; + int now_r, now_j; + int pre_r, pre_j; + int prev_index; + int downsample; /* min 1, max 256 */ + int post_downsample; + int output_scale; + int squelch_level, conseq_squelch, squelch_hits, terminate_on_squelch; + int downsample_passes; + int comp_fir_size; + int custom_atan; + int deemph, deemph_a; + int now_lpr; + int prev_lpr_index; + int dc_block, dc_avg; + void (*mode_demod)(struct demod_state*); + pthread_rwlock_t rw; + pthread_cond_t ready; + pthread_mutex_t ready_m; + struct output_state *output_target; +}; + +struct output_state +{ + int exit_flag; + pthread_t thread; + rtl_fm_output_fn_t output_fn; + void *output_fn_data; + int16_t result[MAXIMUM_BUF_LENGTH]; + int result_len; + int rate; + pthread_rwlock_t rw; + pthread_cond_t ready; + pthread_mutex_t ready_m; +}; + +struct controller_state +{ + int exit_flag; + pthread_t thread; + uint32_t freqs[FREQUENCIES_LIMIT]; + int freq_len; + int freq_now; + int edge; + int wb_mode; + pthread_cond_t hop; + pthread_mutex_t hop_m; +}; + +// multiple of these, eventually +struct dongle_state dongle; +struct demod_state demod; +struct output_state output; +struct controller_state controller; + +#if 0 +static void sighandler(int signum) +{ + fprintf(stderr, "Signal caught, exiting!\n"); + do_exit = 1; + rtlsdr_cancel_async(dongle.dev); +} +#endif + +/* more cond dumbness */ +#define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m) +#define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m) + +/* {length, coef, coef, coef} and scaled by 2^15 + for now, only length 9, optimal way to get +85% bandwidth */ +#define CIC_TABLE_MAX 10 +int cic_9_tables[][10] = { + {0,}, + {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156}, + {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128}, + {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129}, + {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122}, + {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120}, + {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120}, + {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119}, + {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119}, + {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119}, + {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199}, +}; + +void rotate_90(unsigned char *buf, uint32_t len) +/* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j + or [0, 1, -3, 2, -4, -5, 7, -6] */ +{ + uint32_t i; + unsigned char tmp; + for (i=0; i<len; i+=8) { + /* uint8_t negation = 255 - x */ + tmp = 255 - buf[i+3]; + buf[i+3] = buf[i+2]; + buf[i+2] = tmp; + + buf[i+4] = 255 - buf[i+4]; + buf[i+5] = 255 - buf[i+5]; + + tmp = 255 - buf[i+6]; + buf[i+6] = buf[i+7]; + buf[i+7] = tmp; + } +} + +void low_pass(struct demod_state *d) +/* simple square window FIR */ +{ + int i=0, i2=0; + while (i < d->lp_len) { + d->now_r += d->lowpassed[i]; + d->now_j += d->lowpassed[i+1]; + i += 2; + d->prev_index++; + if (d->prev_index < d->downsample) { + continue; + } + d->lowpassed[i2] = d->now_r; // * d->output_scale; + d->lowpassed[i2+1] = d->now_j; // * d->output_scale; + d->prev_index = 0; + d->now_r = 0; + d->now_j = 0; + i2 += 2; + } + d->lp_len = i2; +} + +int low_pass_simple(int16_t *signal2, int len, int step) +// no wrap around, length must be multiple of step +{ + int i, i2, sum; + for(i=0; i < len; i+=step) { + sum = 0; + for(i2=0; i2<step; i2++) { + sum += (int)signal2[i + i2]; + } + //signal2[i/step] = (int16_t)(sum / step); + signal2[i/step] = (int16_t)(sum); + } + signal2[i/step + 1] = signal2[i/step]; + return len / step; +} + +void low_pass_real(struct demod_state *s) +/* simple square window FIR */ +// add support for upsampling? +{ + int i=0, i2=0; + int fast = (int)s->rate_out; + int slow = s->rate_out2; + while (i < s->result_len) { + s->now_lpr += s->result[i]; + i++; + s->prev_lpr_index += slow; + if (s->prev_lpr_index < fast) { + continue; + } + s->result[i2] = (int16_t)(s->now_lpr / (fast/slow)); + s->prev_lpr_index -= fast; + s->now_lpr = 0; + i2 += 1; + } + s->result_len = i2; +} + +void fifth_order(int16_t *data, int length, int16_t *hist) +/* for half of interleaved data */ +{ + int i; + int16_t a, b, c, d, e, f; + a = hist[1]; + b = hist[2]; + c = hist[3]; + d = hist[4]; + e = hist[5]; + f = data[0]; + /* a downsample should improve resolution, so don't fully shift */ + data[0] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; + for (i=4; i<length; i+=4) { + a = c; + b = d; + c = e; + d = f; + e = data[i-2]; + f = data[i]; + data[i/2] = (a + (b+e)*5 + (c+d)*10 + f) >> 4; + } + /* archive */ + hist[0] = a; + hist[1] = b; + hist[2] = c; + hist[3] = d; + hist[4] = e; + hist[5] = f; +} + +void generic_fir(int16_t *data, int length, int *fir, int16_t *hist) +/* Okay, not at all generic. Assumes length 9, fix that eventually. */ +{ + int d, temp, sum; + for (d=0; d<length; d+=2) { + temp = data[d]; + sum = 0; + sum += (hist[0] + hist[8]) * fir[1]; + sum += (hist[1] + hist[7]) * fir[2]; + sum += (hist[2] + hist[6]) * fir[3]; + sum += (hist[3] + hist[5]) * fir[4]; + sum += hist[4] * fir[5]; + data[d] = sum >> 15 ; + hist[0] = hist[1]; + hist[1] = hist[2]; + hist[2] = hist[3]; + hist[3] = hist[4]; + hist[4] = hist[5]; + hist[5] = hist[6]; + hist[6] = hist[7]; + hist[7] = hist[8]; + hist[8] = temp; + } +} + +/* define our own complex math ops + because ARMv5 has no hardware float */ + +void multiply(int ar, int aj, int br, int bj, int *cr, int *cj) +{ + *cr = ar*br - aj*bj; + *cj = aj*br + ar*bj; +} + +int polar_discriminant(int ar, int aj, int br, int bj) +{ + int cr, cj; + double angle; + multiply(ar, aj, br, -bj, &cr, &cj); + angle = atan2((double)cj, (double)cr); + return (int)(angle / 3.14159 * (1<<14)); +} + +int fast_atan2(int y, int x) +/* pre scaled for int16 */ +{ + int yabs, angle; + int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14 + if (x==0 && y==0) { + return 0; + } + yabs = y; + if (yabs < 0) { + yabs = -yabs; + } + if (x >= 0) { + angle = pi4 - pi4 * (x-yabs) / (x+yabs); + } else { + angle = pi34 - pi4 * (x+yabs) / (yabs-x); + } + if (y < 0) { + return -angle; + } + return angle; +} + +int polar_disc_fast(int ar, int aj, int br, int bj) +{ + int cr, cj; + multiply(ar, aj, br, -bj, &cr, &cj); + return fast_atan2(cj, cr); +} + +int atan_lut_init(void) +{ + int i = 0; + + atan_lut = malloc(atan_lut_size * sizeof(int)); + + for (i = 0; i < atan_lut_size; i++) { + atan_lut[i] = (int) (atan((double) i / (1<<atan_lut_coef)) / 3.14159 * (1<<14)); + } + + return 0; +} + +int polar_disc_lut(int ar, int aj, int br, int bj) +{ + int cr, cj, x, x_abs; + + multiply(ar, aj, br, -bj, &cr, &cj); + + /* special cases */ + if (cr == 0 || cj == 0) { + if (cr == 0 && cj == 0) + {return 0;} + if (cr == 0 && cj > 0) + {return 1 << 13;} + if (cr == 0 && cj < 0) + {return -(1 << 13);} + if (cj == 0 && cr > 0) + {return 0;} + if (cj == 0 && cr < 0) + {return 1 << 14;} + } + + /* real range -32768 - 32768 use 64x range -> absolute maximum: 2097152 */ + x = (cj << atan_lut_coef) / cr; + x_abs = abs(x); + + if (x_abs >= atan_lut_size) { + /* we can use linear range, but it is not necessary */ + return (cj > 0) ? 1<<13 : -1<<13; + } + + if (x > 0) { + return (cj > 0) ? atan_lut[x] : atan_lut[x] - (1<<14); + } else { + return (cj > 0) ? (1<<14) - atan_lut[-x] : -atan_lut[-x]; + } + + return 0; +} + +void fm_demod(struct demod_state *fm) +{ + int i, pcm; + int16_t *lp = fm->lowpassed; + pcm = polar_discriminant(lp[0], lp[1], + fm->pre_r, fm->pre_j); + fm->result[0] = (int16_t)pcm; + for (i = 2; i < (fm->lp_len-1); i += 2) { + switch (fm->custom_atan) { + case 0: + pcm = polar_discriminant(lp[i], lp[i+1], + lp[i-2], lp[i-1]); + break; + case 1: + pcm = polar_disc_fast(lp[i], lp[i+1], + lp[i-2], lp[i-1]); + break; + case 2: + pcm = polar_disc_lut(lp[i], lp[i+1], + lp[i-2], lp[i-1]); + break; + } + fm->result[i/2] = (int16_t)pcm; + } + fm->pre_r = lp[fm->lp_len - 2]; + fm->pre_j = lp[fm->lp_len - 1]; + fm->result_len = fm->lp_len/2; +} + +void am_demod(struct demod_state *fm) +// todo, fix this extreme laziness +{ + int i, pcm; + int16_t *lp = fm->lowpassed; + int16_t *r = fm->result; + for (i = 0; i < fm->lp_len; i += 2) { + // hypot uses floats but won't overflow + //r[i/2] = (int16_t)hypot(lp[i], lp[i+1]); + pcm = lp[i] * lp[i]; + pcm += lp[i+1] * lp[i+1]; + r[i/2] = (int16_t)sqrt(pcm) * fm->output_scale; + } + fm->result_len = fm->lp_len/2; + // lowpass? (3khz) highpass? (dc) +} + +void usb_demod(struct demod_state *fm) +{ + int i, pcm; + int16_t *lp = fm->lowpassed; + int16_t *r = fm->result; + for (i = 0; i < fm->lp_len; i += 2) { + pcm = lp[i] + lp[i+1]; + r[i/2] = (int16_t)pcm * fm->output_scale; + } + fm->result_len = fm->lp_len/2; +} + +void lsb_demod(struct demod_state *fm) +{ + int i, pcm; + int16_t *lp = fm->lowpassed; + int16_t *r = fm->result; + for (i = 0; i < fm->lp_len; i += 2) { + pcm = lp[i] - lp[i+1]; + r[i/2] = (int16_t)pcm * fm->output_scale; + } + fm->result_len = fm->lp_len/2; +} + +void raw_demod(struct demod_state *fm) +{ + int i; + for (i = 0; i < fm->lp_len; i++) { + fm->result[i] = (int16_t)fm->lowpassed[i]; + } + fm->result_len = fm->lp_len; +} + +void deemph_filter(struct demod_state *fm) +{ + static int avg; // cheating... + int i, d; + // de-emph IIR + // avg = avg * (1 - alpha) + sample * alpha; + for (i = 0; i < fm->result_len; i++) { + d = fm->result[i] - avg; + if (d > 0) { + avg += (d + fm->deemph_a/2) / fm->deemph_a; + } else { + avg += (d - fm->deemph_a/2) / fm->deemph_a; + } + fm->result[i] = (int16_t)avg; + } +} + +void dc_block_filter(struct demod_state *fm) +{ + int i, avg; + int64_t sum = 0; + for (i=0; i < fm->result_len; i++) { + sum += fm->result[i]; + } + avg = sum / fm->result_len; + avg = (avg + fm->dc_avg * 9) / 10; + for (i=0; i < fm->result_len; i++) { + fm->result[i] -= avg; + } + fm->dc_avg = avg; +} + +int mad(int16_t *samples, int len, int step) +/* mean average deviation */ +{ + int i=0, sum=0, ave=0; + if (len == 0) + {return 0;} + for (i=0; i<len; i+=step) { + sum += samples[i]; + } + ave = sum / (len * step); + sum = 0; + for (i=0; i<len; i+=step) { + sum += abs(samples[i] - ave); + } + return sum / (len / step); +} + +int rms(int16_t *samples, int len, int step) +/* largely lifted from rtl_power */ +{ + int i; + long p, t, s; + double dc, err; + + p = t = 0L; + for (i=0; i<len; i+=step) { + s = (long)samples[i]; + t += s; + p += s * s; + } + /* correct for dc offset in squares */ + dc = (double)(t*step) / (double)len; + err = t * 2 * dc - dc * dc * len; + + return (int)sqrt((p-err) / len); +} + +void arbitrary_upsample(int16_t *buf1, int16_t *buf2, int len1, int len2) +/* linear interpolation, len1 < len2 */ +{ + int i = 1; + int j = 0; + int tick = 0; + double frac; // use integers... + while (j < len2) { + frac = (double)tick / (double)len2; + buf2[j] = (int16_t)(buf1[i-1]*(1-frac) + buf1[i]*frac); + j++; + tick += len1; + if (tick > len2) { + tick -= len2; + i++; + } + if (i >= len1) { + i = len1 - 1; + tick = len2; + } + } +} + +void arbitrary_downsample(int16_t *buf1, int16_t *buf2, int len1, int len2) +/* fractional boxcar lowpass, len1 > len2 */ +{ + int i = 1; + int j = 0; + int tick = 0; + double remainder = 0; + double frac; // use integers... + buf2[0] = 0; + while (j < len2) { + frac = 1.0; + if ((tick + len2) > len1) { + frac = (double)(len1 - tick) / (double)len2;} + buf2[j] += (int16_t)((double)buf1[i] * frac + remainder); + remainder = (double)buf1[i] * (1.0-frac); + tick += len2; + i++; + if (tick > len1) { + j++; + buf2[j] = 0; + tick -= len1; + } + if (i >= len1) { + i = len1 - 1; + tick = len1; + } + } + for (j=0; j<len2; j++) { + buf2[j] = buf2[j] * len2 / len1;} +} + +void arbitrary_resample(int16_t *buf1, int16_t *buf2, int len1, int len2) +/* up to you to calculate lengths and make sure it does not go OOB + * okay for buffers to overlap, if you are downsampling */ +{ + if (len1 < len2) { + arbitrary_upsample(buf1, buf2, len1, len2); + } else { + arbitrary_downsample(buf1, buf2, len1, len2); + } +} + +void full_demod(struct demod_state *d) +{ + int i, ds_p; + int sr = 0; + ds_p = d->downsample_passes; + if (ds_p) { + for (i=0; i < ds_p; i++) { + fifth_order(d->lowpassed, (d->lp_len >> i), d->lp_i_hist[i]); + fifth_order(d->lowpassed+1, (d->lp_len >> i) - 1, d->lp_q_hist[i]); + } + d->lp_len = d->lp_len >> ds_p; + /* droop compensation */ + if (d->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) { + generic_fir(d->lowpassed, d->lp_len, + cic_9_tables[ds_p], d->droop_i_hist); + generic_fir(d->lowpassed+1, d->lp_len-1, + cic_9_tables[ds_p], d->droop_q_hist); + } + } else { + low_pass(d); + } + /* power squelch */ + if (d->squelch_level) { + sr = rms(d->lowpassed, d->lp_len, 1); + if (sr < d->squelch_level) { + d->squelch_hits++; + for (i=0; i<d->lp_len; i++) { + d->lowpassed[i] = 0; + } + } else { + d->squelch_hits = 0;} + } + d->mode_demod(d); /* lowpassed -> result */ + if (d->mode_demod == &raw_demod) { + return; + } + /* todo, fm noise squelch */ + // use nicer filter here too? + if (d->post_downsample > 1) { + d->result_len = low_pass_simple(d->result, d->result_len, d->post_downsample);} + if (d->deemph) { + deemph_filter(d);} + if (d->dc_block) { + dc_block_filter(d);} + if (d->rate_out2 > 0) { + low_pass_real(d); + //arbitrary_resample(d->result, d->result, d->result_len, d->result_len * d->rate_out2 / d->rate_out); + } +} + +static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx) +{ + int i; + struct dongle_state *s = ctx; + struct demod_state *d = s->demod_target; + + if (do_exit) { + return;} + if (!ctx) { + return;} + if (s->mute) { + for (i=0; i<s->mute; i++) { + buf[i] = 127;} + s->mute = 0; + } + if (!s->offset_tuning) { + rotate_90(buf, len);} + for (i=0; i<(int)len; i++) { + s->buf16[i] = (int16_t)buf[i] - 127;} + pthread_rwlock_wrlock(&d->rw); + memcpy(d->lowpassed, s->buf16, 2*len); + d->lp_len = len; + pthread_rwlock_unlock(&d->rw); + safe_cond_signal(&d->ready, &d->ready_m); +} + +static void *dongle_thread_fn(void *arg) +{ + struct dongle_state *s = arg; + fprintf(stderr, "dongle_thread_fn running\n"); + rtlsdr_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len); + fprintf(stderr, "dongle_thread_fn exited!\n"); + return 0; +} + +static void *demod_thread_fn(void *arg) +{ + struct demod_state *d = arg; + struct output_state *o = d->output_target; + fprintf(stderr, "demod_thread_fn running\n"); + while (!do_exit) { + safe_cond_wait(&d->ready, &d->ready_m); + pthread_rwlock_wrlock(&d->rw); + full_demod(d); + pthread_rwlock_unlock(&d->rw); + if (d->exit_flag) { + do_exit = 1; + } + if (d->squelch_level && d->squelch_hits > d->conseq_squelch) { + d->squelch_hits = d->conseq_squelch + 1; /* hair trigger */ + safe_cond_signal(&controller.hop, &controller.hop_m); + continue; + } + pthread_rwlock_wrlock(&o->rw); + memcpy(o->result, d->result, 2*d->result_len); + o->result_len = d->result_len; + pthread_rwlock_unlock(&o->rw); + safe_cond_signal(&o->ready, &o->ready_m); + } + fprintf(stderr, "demod_thread_fn exited!\n"); + return 0; +} + +static void *output_thread_fn(void *arg) +{ + struct output_state *s = arg; + fprintf(stderr, "output_thread_fn running\n"); + while (!do_exit) { + // use timedwait and pad out under runs + safe_cond_wait(&s->ready, &s->ready_m); + pthread_rwlock_rdlock(&s->rw); + if(s->output_fn) { + s->output_fn(s->result, s->result_len, s->output_fn_data); + } + pthread_rwlock_unlock(&s->rw); + } + fprintf(stderr, "output_thread_fn exited!\n"); + return 0; +} + +static void optimal_settings(int freq, int rate) +{ + // giant ball of hacks + // seems unable to do a single pass, 2:1 + int capture_freq, capture_rate; + struct dongle_state *d = &dongle; + struct demod_state *dm = &demod; + struct controller_state *cs = &controller; + dm->downsample = (1000000 / dm->rate_in) + 1; + if (dm->downsample_passes) { + dm->downsample_passes = (int)log2(dm->downsample) + 1; + dm->downsample = 1 << dm->downsample_passes; + } + capture_freq = freq; + capture_rate = dm->downsample * dm->rate_in; + if (!d->offset_tuning) { + capture_freq = freq + capture_rate/4;} + capture_freq += cs->edge * dm->rate_in / 2; + dm->output_scale = (1<<15) / (128 * dm->downsample); + if (dm->output_scale < 1) { + dm->output_scale = 1;} + if (dm->mode_demod == &fm_demod) { + dm->output_scale = 1;} + d->freq = (uint32_t)capture_freq; + d->rate = (uint32_t)capture_rate; +} + + +void frequency_range(struct controller_state *s, char *arg) +{ + char *start, *stop, *step; + int i; + start = arg; + stop = strchr(start, ':') + 1; + stop[-1] = '\0'; + step = strchr(stop, ':') + 1; + step[-1] = '\0'; + for(i=(int)atofs(start); i<=(int)atofs(stop); i+=(int)atofs(step)) + { + s->freqs[s->freq_len] = (uint32_t)i; + s->freq_len++; + if (s->freq_len >= FREQUENCIES_LIMIT) { + break;} + } + stop[-1] = ':'; + step[-1] = ':'; +} + +void dongle_init(struct dongle_state *s) +{ + s->rate = DEFAULT_SAMPLE_RATE; + s->gain = AUTO_GAIN; // tenths of a dB + s->mute = 0; + s->direct_sampling = 0; + s->offset_tuning = 0; + s->demod_target = &demod; +} + +void demod_init(struct demod_state *s) +{ + s->rate_in = DEFAULT_SAMPLE_RATE; + s->rate_out = DEFAULT_SAMPLE_RATE; + s->squelch_level = 0; + s->conseq_squelch = 10; + s->terminate_on_squelch = 0; + s->squelch_hits = 11; + s->downsample_passes = 0; + s->comp_fir_size = 0; + s->prev_index = 0; + s->post_downsample = 1; // once this works, default = 4 + s->custom_atan = 0; + s->deemph = 0; + s->rate_out2 = -1; // flag for disabled + s->mode_demod = &fm_demod; + s->pre_j = s->pre_r = s->now_r = s->now_j = 0; + s->prev_lpr_index = 0; + s->deemph_a = 0; + s->now_lpr = 0; + s->dc_block = 0; + s->dc_avg = 0; + pthread_rwlock_init(&s->rw, NULL); + pthread_cond_init(&s->ready, NULL); + pthread_mutex_init(&s->ready_m, NULL); + s->output_target = &output; +} + +void demod_cleanup(struct demod_state *s) +{ + pthread_rwlock_destroy(&s->rw); + pthread_cond_destroy(&s->ready); + pthread_mutex_destroy(&s->ready_m); +} + +void output_init(struct output_state *s) +{ + s->rate = DEFAULT_SAMPLE_RATE; + s->output_fn = NULL; + s->output_fn_data = NULL; + pthread_rwlock_init(&s->rw, NULL); + pthread_cond_init(&s->ready, NULL); + pthread_mutex_init(&s->ready_m, NULL); +} + +void output_cleanup(struct output_state *s) +{ + pthread_rwlock_destroy(&s->rw); + pthread_cond_destroy(&s->ready); + pthread_mutex_destroy(&s->ready_m); +} + +void controller_init(struct controller_state *s) +{ + s->freqs[0] = 100000000; + s->freq_len = 0; + s->edge = 0; + s->wb_mode = 0; + pthread_cond_init(&s->hop, NULL); + pthread_mutex_init(&s->hop_m, NULL); +} + +void controller_cleanup(struct controller_state *s) +{ + pthread_cond_destroy(&s->hop); + pthread_mutex_destroy(&s->hop_m); +} + +void sanity_checks(void) +{ + if (controller.freq_len == 0) { + fprintf(stderr, "Please specify a frequency.\n"); + exit(1); + } + + if (controller.freq_len >= FREQUENCIES_LIMIT) { + fprintf(stderr, "Too many channels, maximum %i.\n", FREQUENCIES_LIMIT); + exit(1); + } + + if (controller.freq_len > 1 && demod.squelch_level == 0) { + fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n"); + exit(1); + } + +} + +int rtl_fm_init(uint32_t freq, + uint32_t sample_rate, + uint32_t resample_rate, + rtl_fm_output_fn_t output_fn, + void *output_fn_data) +{ + int r = 0; + + dongle_init(&dongle); + demod_init(&demod); + output_init(&output); + controller_init(&controller); + + /* + * Simulate the effects of command line arguments: + * + * -W wbfm -s <sample rate> -r <resample rate> + */ + + /* Set initial frequency */ + controller.freqs[0] = freq; + controller.freq_len++; + + /* Set mode to wbfm */ + controller.wb_mode = 1; + demod.mode_demod = &fm_demod; + demod.rate_in = 170000; + demod.rate_out = 170000; + demod.rate_out2 = 32000; + demod.custom_atan = 1; + //demod.post_downsample = 4; + demod.deemph = 1; + demod.squelch_level = 0; + + /* Set sample rate */ + demod.rate_in = sample_rate; + demod.rate_out = sample_rate; + + /* Set resample rate */ + output.rate = (int) resample_rate; + demod.rate_out2 = (int) resample_rate; + + /* Set output function pointer */ + if(output_fn) { + output.output_fn = output_fn; + output.output_fn_data = output_fn_data; + } + + /* quadruple sample_rate to limit to Δθ to ±π/2 */ + demod.rate_in *= demod.post_downsample; + + if (!output.rate) { + output.rate = demod.rate_out; + } + + sanity_checks(); + + if (controller.freq_len > 1) { + demod.terminate_on_squelch = 0; + } + + ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH; + + dongle.dev_index = verbose_device_search("0"); + if (dongle.dev_index < 0) { + return -1; + } + + r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index); + if (r < 0) { + fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dongle.dev_index); + return r; + } + + if (demod.deemph) { + demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6))))); + } + + /* Set the tuner gain */ + if (dongle.gain == AUTO_GAIN) { + verbose_auto_gain(dongle.dev); + } else { + dongle.gain = nearest_gain(dongle.dev, dongle.gain); + verbose_gain_set(dongle.dev, dongle.gain); + } + + verbose_ppm_set(dongle.dev, dongle.ppm_error); + + //r = rtlsdr_set_testmode(dongle.dev, 1); + + return r; +} + +void rtl_fm_start(void) +{ + int i; + struct controller_state *s = &controller; + + /* + * A bunch of the following is pulled from the controller_thread_fn, + * which has been removed. + */ + + /* Reset endpoint before we start reading from it (mandatory) */ + verbose_reset_buffer(dongle.dev); + + if (s->wb_mode) { + for (i=0; i < s->freq_len; i++) { + s->freqs[i] += 16000;} + } + + /* set up primary channel */ + optimal_settings(s->freqs[0], demod.rate_in); + if (dongle.direct_sampling) { + verbose_direct_sampling(dongle.dev, 1);} + if (dongle.offset_tuning) { + verbose_offset_tuning(dongle.dev);} + + /* Set the frequency */ + verbose_set_frequency(dongle.dev, dongle.freq); + fprintf(stderr, "Oversampling input by: %ix.\n", demod.downsample); + fprintf(stderr, "Oversampling output by: %ix.\n", demod.post_downsample); + fprintf(stderr, "Buffer size: %0.2fms\n", + 1000 * 0.5 * (float)ACTUAL_BUF_LENGTH / (float)dongle.rate); + + /* Set the sample rate */ + verbose_set_sample_rate(dongle.dev, dongle.rate); + fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample); + usleep(100000); + + pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output)); + pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod)); + pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle)); +} + +void rtl_fm_set_freq(uint32_t freq) +{ + int i; + struct controller_state *s = &controller; + struct demod_state *dm = &demod; + + s->freqs[0] = freq; + s->freq_len = 1; + + if (s->wb_mode) { + for (i=0; i < s->freq_len; i++) { + s->freqs[i] += 16000;} + } + + optimal_settings(s->freqs[0], demod.rate_in); + if (dongle.offset_tuning) { + verbose_offset_tuning(dongle.dev);} + rtlsdr_set_center_freq(dongle.dev, dongle.freq); + + // It does not look like refreshing the sample rate is desirable + // (e.g. the scanning code in the removed controller thread function + // did not do it), and behavior seemed a bit less robust with it + // present. However, I am leaving this here as a reminder to revisit + // via some more testing. + //rtlsdr_set_sample_rate(dongle.dev, dongle.rate); + + // This triggers a mute during the frequency change + dongle.mute = BUFFER_DUMP; +} + +void rtl_fm_stop(void) +{ + rtlsdr_cancel_async(dongle.dev); + pthread_join(dongle.thread, NULL); + safe_cond_signal(&demod.ready, &demod.ready_m); + pthread_join(demod.thread, NULL); + safe_cond_signal(&output.ready, &output.ready_m); + pthread_join(output.thread, NULL); +} + +void rtl_fm_cleanup(void) +{ + //dongle_cleanup(&dongle); + demod_cleanup(&demod); + output_cleanup(&output); + controller_cleanup(&controller); + + rtlsdr_close(dongle.dev); +} + +// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/rtl_fm.h b/rtl_fm.h new file mode 100644 index 0000000..a35d341 --- /dev/null +++ b/rtl_fm.h @@ -0,0 +1,50 @@ +/* + * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver + * Copyright (C) 2016 by Scott Murray <scott.murray@konsulko.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef RTL_FM_H +#define RTL_FM_H + +#define RTL_FM_DEFAULT_BUF_LENGTH (1 * 16384) +#define RTL_FM_MAXIMUM_OVERSAMPLE 16 +#define RTL_FM_MAXIMUM_BUF_LENGTH (RTL_FM_MAXIMUM_OVERSAMPLE * RTL_FM_DEFAULT_BUF_LENGTH) + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*rtl_fm_output_fn_t)(int16_t *result, int result_len, void *data); + +int rtl_fm_init(uint32_t freq, + uint32_t sample_rate, + uint32_t resample_rate, + rtl_fm_output_fn_t output_fn, + void *output_fn_data); + +void rtl_fm_start(void); + +void rtl_fm_set_freq(uint32_t freq); + +void rtl_fm_stop(void); + +void rtl_fm_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif /* RTL_FM_H */ diff --git a/rtlfmradio.pro b/rtlfmradio.pro index 0b8d406..5824ec7 100644 --- a/rtlfmradio.pro +++ b/rtlfmradio.pro @@ -1,10 +1,11 @@ TEMPLATE = lib -CONFIG += plugin c++11 +CONFIG += plugin c++11 link_pkgconfig +PKGCONFIG += librtlsdr libpulse-simple alsa TARGET = rtlfmradio QT = multimedia -HEADERS = rtlfmradioplugin.h rtlfmradioservice.h rtlfmradiotunercontrol.h -SOURCES = rtlfmradioplugin.cpp rtlfmradioservice.cpp rtlfmradiotunercontrol.cpp +HEADERS = rtlfmradioplugin.h rtlfmradioservice.h rtlfmradiotunercontrol.h radio_output.h rtl_fm.h convenience/convenience.h +SOURCES = rtlfmradioplugin.cpp rtlfmradioservice.cpp rtlfmradiotunercontrol.cpp radio_output.cpp rtl_fm.c convenience/convenience.c DISTFILES += rtlfmradio.json target.path = $$[QT_INSTALL_PLUGINS]/mediaservice diff --git a/rtlfmradiotunercontrol.cpp b/rtlfmradiotunercontrol.cpp index 863cb06..1fdf8d3 100644 --- a/rtlfmradiotunercontrol.cpp +++ b/rtlfmradiotunercontrol.cpp @@ -1,14 +1,19 @@ -/* Copyright (C) 2016, The Qt Company Ltd. All Rights Reserved. +/* + * Copyright (C) 2016, The Qt Company Ltd. All Rights Reserved. + * Copyright (C) 2016, Scott Murray <scott.murray@konsulko.com> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ #include "rtlfmradiotunercontrol.h" +#include "radio_output.h" +#include "rtl_fm.h" #include <QtCore/QDebug> #include <QtCore/QTimer> -#include <QtCore/QProcess> +#include <QtCore/QSettings> class RtlFmRadioTunerControl::Private { @@ -26,97 +31,112 @@ public: QRadioTuner::StereoMode stereoMode; int signalStrength; int volume; - bool muted; + static bool muted; bool searching; QRadioTuner::Error error; QString errorString; QTimer timer; bool searchOne; int step; - - QProcess rtlFm; - QProcess aplay; + bool present; + QPair<int, int> fmBandPlan; private: - QMap<int, QString> amFrequenciesTokyo = { - { 594000, QString::fromUtf8("NHK Tokyo #1") } - , { 693000, QString::fromUtf8("NHK Tokyo #2") } - , { 810000, QString::fromUtf8("Eagle 810 (AFN TOKYO)") } - , { 954000, QString::fromUtf8("TBS Radio Tokyo") } - , { 1134000, QString::fromUtf8("Nippon Cultural Broadcasting") } - , { 1242000, QString::fromUtf8("Nippon Broadcasting") } + QMap<QString, QPair<int, int> > knownFmBandPlans = { + { QString::fromUtf8("US"), qMakePair(87900000, 107900000) }, + { QString::fromUtf8("JP"), qMakePair(76100000, 89900000) } }; - QMap<int, QString> fmFrequenciesTokyo = { - { 76100000, QString::fromUtf8("Inter FM") } - , { 77100000, QString::fromUtf8("The Open University of Japan") } - , { 80000000, QString::fromUtf8("TOKYO FM") } - , { 81300000, QString::fromUtf8("J-WAVE") } - , { 82500000, QString::fromUtf8("NHK FM Tokyo") } - }; + static void output_thread_fn(int16_t *result, int result_len, void *ctx); + static RadioOutputImplementation *mRadioOutput; }; +bool RtlFmRadioTunerControl::Private::muted; +RadioOutputImplementation *RtlFmRadioTunerControl::Private::mRadioOutput; + RtlFmRadioTunerControl::Private::Private(RtlFmRadioTunerControl *parent) - : q(parent) - , state(QRadioTuner::StoppedState) - , band(QRadioTuner::FM) - , frequency(76100) - , stereoMode(QRadioTuner::Auto) - , signalStrength(100) - , volume(50) - , muted(false) - , searching(false) - , error(QRadioTuner::NoError) - , searchOne(true) - , step(0) -{ + : q(parent), + state(QRadioTuner::StoppedState), + band(QRadioTuner::FM), + stereoMode(QRadioTuner::Auto), + signalStrength(100), + volume(50), + searching(false), + error(QRadioTuner::NoError), + searchOne(true), + step(0) +{ + mRadioOutput = NULL; + muted = false; + + // Initialize output + // Note that the optional ALSA support is currently not entirely + // functional and needs further debugging. + char *impl_env = getenv("RADIO_OUTPUT"); + if (impl_env) { + if (strcasecmp(impl_env, "Pulse") == 0) + mRadioOutput = new RadioOutputPulse(); + if (strcasecmp(impl_env, "Alsa") == 0) + mRadioOutput = new RadioOutputAlsa(); + } + if (!mRadioOutput) { + mRadioOutput = new RadioOutputPulse(); + if (!mRadioOutput->works) + mRadioOutput = new RadioOutputAlsa(); + } + + // Initialize RTL-SDR dongle + present = false; + if(rtl_fm_init(frequency, 200000, 48000, output_thread_fn, NULL) < 0) { + qDebug("%s: no RTL USB adapter?", Q_FUNC_INFO); + } else { + present = true; + } + + // Initialize FM band plan + QSettings settings("AGL", "qtmultimedia-rtlfm-radio-plugin"); + if(!settings.contains("fmbandplan")) + settings.setValue("fmbandplan", QString::fromUtf8("US")); + if(!knownFmBandPlans.contains(settings.value("fmbandplan").toString())) + settings.setValue("fmbandplan", QString::fromUtf8("US")); + fmBandPlan = knownFmBandPlans.value(settings.value("fmbandplan").toString()); + + // Initialize frequency to lower bound of band plan + frequency = fmBandPlan.first; + connect(q, &RtlFmRadioTunerControl::stationFound, [this](int frequency, const QString &name) { - if (rtlFm.state() != QProcess::NotRunning) - q->start(); + qDebug() << frequency << name; }); connect(q, &RtlFmRadioTunerControl::frequencyChanged, [this](int frequency) { - QMap<int, QString> knownBands; - switch (band) { - case QRadioTuner::AM: - knownBands = amFrequenciesTokyo; - break; - case QRadioTuner::FM: - knownBands = fmFrequenciesTokyo; - break; - default: - qFatal("%s:%d %d not supported", Q_FUNC_INFO, __LINE__, band); - break; - } - if (knownBands.contains(frequency)) { - QMetaObject::invokeMethod(q, "stationFound", Qt::QueuedConnection, Q_ARG(int, frequency), Q_ARG(QString, knownBands.value(frequency))); - if (timer.isActive() && searchOne) - timer.stop(); - } + // Note that the following effectively disables any attempt at seeking. + // If seeking support is required some logic will need to be added back + // here (and elsewhere). + if (searching) { + emit q->stationFound(frequency, QString("%1 MHz").arg((double) frequency / 1000000, 0, 'f', 1)); + if (timer.isActive() && searchOne) + timer.stop(); + } }); - connect(&timer, &QTimer::timeout, [this]() { q->setFrequency(frequency + step); }); +} - connect(&rtlFm, &QProcess::readyReadStandardOutput, [this]() { - aplay.write(rtlFm.readAllStandardOutput()); - }); - connect(&rtlFm, &QProcess::readyReadStandardError, [this]() { - qDebug() << rtlFm.readAllStandardError(); - }); +void RtlFmRadioTunerControl::Private::output_thread_fn(int16_t *result, int result_len, void *ctx) +{ + if (!muted) + mRadioOutput->play((void*) result, result_len); } RtlFmRadioTunerControl::Private::~Private() { - aplay.kill(); - aplay.waitForFinished(); - rtlFm.kill(); - rtlFm.waitForFinished(); + delete mRadioOutput; + rtl_fm_cleanup(); } RtlFmRadioTunerControl::RtlFmRadioTunerControl(QObject *parent) - : QRadioTunerControl(parent) - , d(new Private(this)) + : QRadioTunerControl(parent), + d(new Private(this)) { } @@ -137,15 +157,20 @@ QRadioTuner::Band RtlFmRadioTunerControl::band() const void RtlFmRadioTunerControl::setBand(QRadioTuner::Band band) { - if (d->band == band) return; + // We only support FM, so this is a no-op, but leaving the code below + // #if'ed out for reference. +#if 0 + if (d->band == band) + return; d->band = band; emit bandChanged(band); +#endif } bool RtlFmRadioTunerControl::isBandSupported(QRadioTuner::Band band) const { + // We only support FM. switch (band) { - case QRadioTuner::AM: case QRadioTuner::FM: return true; default: @@ -162,9 +187,10 @@ int RtlFmRadioTunerControl::frequency() const int RtlFmRadioTunerControl::frequencyStep(QRadioTuner::Band band) const { int ret = 0; + switch (band) { case QRadioTuner::AM: - ret = 1000; // 1kHz + ret = 1000; // 1 kHz break; case QRadioTuner::FM: ret = 100000; // 0.1 MHz @@ -178,12 +204,13 @@ int RtlFmRadioTunerControl::frequencyStep(QRadioTuner::Band band) const QPair<int,int> RtlFmRadioTunerControl::frequencyRange(QRadioTuner::Band band) const { QPair<int,int> ret; + switch (band) { case QRadioTuner::AM: ret = qMakePair<int,int>(531000, 1602000); break; case QRadioTuner::FM: - ret = qMakePair<int,int>(76000000, 107900000); + ret = QPair<int,int>(d->fmBandPlan); break; default: break; @@ -193,6 +220,9 @@ QPair<int,int> RtlFmRadioTunerControl::frequencyRange(QRadioTuner::Band band) co void RtlFmRadioTunerControl::setFrequency(int frequency) { + if(!d->present) + return; + QPair<int,int> range = frequencyRange(d->band); frequency = qBound(range.first, frequency, range.second); if (d->frequency == frequency) { @@ -201,12 +231,14 @@ void RtlFmRadioTunerControl::setFrequency(int frequency) return; } d->frequency = frequency; + rtl_fm_set_freq(frequency); emit frequencyChanged(frequency); } bool RtlFmRadioTunerControl::isStereo() const { bool ret = false; + switch (d->stereoMode) { case QRadioTuner::ForceStereo: case QRadioTuner::Auto: @@ -225,7 +257,8 @@ QRadioTuner::StereoMode RtlFmRadioTunerControl::stereoMode() const void RtlFmRadioTunerControl::setStereoMode(QRadioTuner::StereoMode stereoMode) { - if (d->stereoMode == stereoMode) return; + if (d->stereoMode == stereoMode) + return; bool prevStereo = isStereo(); d->stereoMode = stereoMode; bool currentStereo = isStereo(); @@ -246,7 +279,8 @@ int RtlFmRadioTunerControl::volume() const void RtlFmRadioTunerControl::setVolume(int volume) { volume = qBound(0, volume, 100); - if (d->volume == volume) return; + if (d->volume == volume) + return; d->volume = volume; emit volumeChanged(volume); } @@ -258,7 +292,8 @@ bool RtlFmRadioTunerControl::isMuted() const void RtlFmRadioTunerControl::setMuted(bool muted) { - if (d->muted == muted) return; + if (d->muted == muted) + return; d->muted = muted; emit mutedChanged(muted); } @@ -302,41 +337,43 @@ void RtlFmRadioTunerControl::cancelSearch() void RtlFmRadioTunerControl::setSearching(bool searching) { - if (d->searching == searching) return; + if (d->searching == searching) + return; d->searching = searching; emit searchingChanged(searching); } void RtlFmRadioTunerControl::start() { - stop(); - d->aplay.start(QStringLiteral("aplay"), QStringList() << QStringLiteral("-r") << QStringLiteral("48000") << QStringLiteral("-f") << QStringLiteral("S16_LE")); - d->aplay.waitForStarted(); -// d->rtlFm.start(QStringLiteral("sudo"), QStringList() << QStringLiteral("rtl_fm -f %1 -M wbfm -s 200000 -r 48000").arg(d->frequency * 1000).split(" ")); - QString modulation; - switch (d->band) { - case QRadioTuner::AM: - modulation = QStringLiteral("-M wbfm"); - break; - case QRadioTuner::FM: - modulation = QStringLiteral("-M am"); - break; - default: - break; + if(!d->present) + return; + + qDebug() << "RtlFmRadioTunerControl::start - enter\n"; + if(d->state == QRadioTuner::ActiveState) { + qDebug() << "RtlFmRadioTunerControl::start - already running, exit\n"; + return; } - d->rtlFm.start(QStringLiteral("rtl_fm"), QStringLiteral("-f %1 %2 -s 200000 -r 48000").arg(d->frequency).arg(modulation).split(" ")); - d->rtlFm.waitForStarted(); + //rtl_fm_stop(); + rtl_fm_start(); d->state = QRadioTuner::ActiveState; + qDebug() << "RtlFmRadioTunerControl::start - exit\n"; } void RtlFmRadioTunerControl::stop() { - d->aplay.kill(); - d->aplay.waitForFinished(); - d->rtlFm.kill(); - d->rtlFm.waitForFinished(); + if(!d->present) + return; + + qDebug() << "RtlFmRadioTunerControl::stop - enter\n"; + if(d->state == QRadioTuner::StoppedState) { + qDebug() << "RtlFmRadioTunerControl::stop - already stopped, exit\n"; + return; + } + + rtl_fm_stop(); d->state = QRadioTuner::StoppedState; + qDebug() << "RtlFmRadioTunerControl::stop - exit\n"; } QRadioTuner::Error RtlFmRadioTunerControl::error() const |