├── .gitignore
├── LICENSE
├── README.md
├── build.sh
├── convenience
├── convenience.c
└── convenience.h
├── getopt
├── getopt.c
└── getopt.h
├── rtl_fm.c
├── rtl_fm_python.c
├── rtl_fm_python_common.py
├── rtl_fm_python_thread.py
├── rtl_fm_python_web.py
├── start_web.sh
└── static
├── index.html
└── rtl_fm_python.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | build
10 | dist
11 | eggs
12 | parts
13 | bin
14 | var
15 | sdist
16 | develop-eggs
17 | .installed.cfg
18 | lib
19 | lib64
20 | __pycache__
21 |
22 | # Installer logs
23 | pip-log.txt
24 |
25 | # Unit test / coverage reports
26 | .coverage
27 | .tox
28 | nosetests.xml
29 |
30 | # Translations
31 | *.mo
32 |
33 | # Mr Developer
34 | .mr.developer.cfg
35 | .project
36 | .pydevproject
37 | *~
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | rtl_fm_python
2 | =============
3 |
4 | An API and web application to interact with a running instance of RTL_FM
5 |
6 |
7 |
8 | # What
9 |
10 | This is a Python library built upon the RTL-SDR project and allows you to use the
11 | RTL-SDR dongle to tune in arbitrary stations either with a simple web application
12 | running on a built-in server, or programmatically with Python or any language using
13 | the REST API provided.
14 |
15 | - http://sdr.osmocom.org/trac/wiki/rtl-sdr
16 | - http://www.reddit.com/r/rtlsdr
17 |
18 | # Why
19 |
20 | I wanted a minimalist remote control for demodulated audio coming from the usb stick
21 | and something that could also provide that functionality on the Raspberry PI, or
22 | allow for control of multiple dongles through web scripting and VPNs.
23 |
24 | # Features
25 |
26 | - Based on the rtl_fm utility from the RTL-SDR Project https://github.com/steve-m/librtlsdr
27 | - Drop in replacement for rtl_fm
28 | - Live web interface based on React http://facebook.github.io/react/ and Flask http://flask.pocoo.org/
29 | - RESTful API
30 | - Change frequency, demodulation, and gain while running
31 | - Read the RMS signal level
32 | - Interact with rtl_fm with Python
33 |
34 | # License
35 |
36 | GPLv2
37 |
38 | # How to Build
39 |
40 | - Install the RTL-SDR software
41 | - Install Python dependencies for Flask
42 |
43 | sudo pip install flask
44 |
45 | - Compile and link the modified rtm_fm source rtl_fm_python.c
46 |
47 | ./build.sh
48 |
49 | If you have problems let me know. I may not be able to help as I'm not very experienced
50 | with building C applications.
51 |
52 | # Issues
53 |
54 | - Can crash, probably
55 | - Works best if started with WBFM modulation and sample rates if you're going to be switching around demodulation.
56 | - May get out of sync with the features of rtl_fm due to my time and interest. Pull requests accepted!
57 |
58 | # How to Run
59 |
60 | ## Web Interface & API
61 |
62 | Use the script *rtl_fm_python_web.py* as a replacement for *rtl_fm*. If you need to change the
63 | port or the host, that is available at the end of the file.
64 |
65 | Included is a script called *start_web.sh* that shows an example usage. This script tunes to a
66 | broadcast FM station and pipes the audio to Pulse Audio.
67 |
68 | By default the application should be running at http://127.0.0.1:10100/
69 |
70 | ## Python interactive mode
71 |
72 | Use the script *rtl_fm_python_thread.py* with flags identical to the rtl_fm command. When
73 | you start the application you will be placed into an interactive shell where you can
74 | issue commands.
75 |
76 | # REST API
77 |
78 | ## /state
79 |
80 | Returns the current state of the device, for example:
81 |
82 | {
83 | "autogain": true,
84 | "freq_i": 102500000,
85 | "freq_s": "102.5M",
86 | "gain": -100,
87 | "mod": "w",
88 | "s_level": 14
89 | }
90 |
91 | Modulation modes are denoted as a single letter, w for WBFM, f for FM, a for AM, l for LSB, u for USB, and r for RAW.
92 |
93 | ## /frequency/ *value*
94 |
95 | Tune the device to a specific integer frequency. For example:
96 |
97 | /frequency/101100000
98 | /frequency/144390000
99 | /frequency/162550000
100 |
101 | ## /frequency/human/ *value*
102 |
103 | Tune to a human readable string representation of a frequency. For example:
104 |
105 | /frequency/human/101.1M
106 | /frequency/human/144390.0K
107 | /frequency/human/0.16255G
108 |
109 | ## /demod/ *value*
110 |
111 | Switch the modulation. For example:
112 |
113 | /demod/w
114 | /demod/f
115 | /demod/a
116 | /demod/l
117 | /demod/u
118 | /demod/r
119 |
120 | ## /gain/list
121 |
122 | Returns the real gain values available. For example:
123 |
124 | {
125 | "gains": [
126 | -10,
127 | 15,
128 | 40,
129 | 65,
130 | 90,
131 | 115,
132 | 140,
133 | 165,
134 | 190,
135 | 215,
136 | 240,
137 | 290,
138 | 340,
139 | 420
140 | ]
141 | }
142 |
143 |
144 | ## /gain/ *value*
145 |
146 | Set a real gain value for the device. For example:
147 |
148 | /gain/-10
149 | /gain/115
150 | /gain/340
151 |
152 | This call turns off automatic gain.
153 |
154 | ## /gain/human/ *value*
155 |
156 | Sets the gain given an arbitrary number scale and tries to find a gain that matches. For example:
157 |
158 | /gain/human/0
159 | /gain/human/40
160 |
161 | This call turns off automatic gain.
162 |
163 | ## /gain/auto
164 |
165 | Sets the device to be in auto gain mode. The gain may read -100 in the above state call.
166 |
167 | # Python Functions
168 |
169 | ## Device functions
170 |
171 | get_s_level()
172 | get_frequency()
173 | set_demod_fm()
174 | set_demod_wbfm()
175 | set_demod_am()
176 | set_demod_lsb()
177 | set_demod_usb()
178 | set_demod_raw()
179 | set_frequency(frequency)
180 | set_squelch(level) #untested
181 | get_demod()
182 | set_demod(modulation) #w,f,a,l,u,r
183 | get_gains()
184 | get_gain()
185 | set_gain(value)
186 | get_auto_gain()
187 | set_gain_human(human_value)
188 | set_freq_human(human_frequency)
189 | get_freq_human()
190 |
191 | ## Utility functions
192 |
193 | str_to_freq(human_frequency)
194 | freq_to_str(frequency)
195 | printstderr(text)
196 |
197 | # Thanks
198 |
199 | The rtl-sdr team and community for being awesome.
200 |
201 |
202 |
203 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | gcc -I /usr/include/libusb-1.0 -I ./convenience/ -I ./getopt -shared -Wl,-soname,rtl_fm_python -o rtl_fm_python.so -fPIC rtl_fm_python.c convenience/convenience.c getopt/getopt.c -lrtlsdr
2 |
--------------------------------------------------------------------------------
/convenience/convenience.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 by Kyle Keen
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 2 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | /* a collection of user friendly tools
19 | * todo: use strtol for more flexible int parsing
20 | * */
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | #ifndef _WIN32
27 | #include
28 | #else
29 | #include
30 | #include
31 | #include
32 | #define _USE_MATH_DEFINES
33 | #endif
34 |
35 | #include
36 |
37 | #include "rtl-sdr.h"
38 |
39 | double atofs(char *s)
40 | /* standard suffixes */
41 | {
42 | char last;
43 | int len;
44 | double suff = 1.0;
45 | len = strlen(s);
46 | last = s[len-1];
47 | s[len-1] = '\0';
48 | switch (last) {
49 | case 'g':
50 | case 'G':
51 | suff *= 1e3;
52 | case 'm':
53 | case 'M':
54 | suff *= 1e3;
55 | case 'k':
56 | case 'K':
57 | suff *= 1e3;
58 | suff *= atof(s);
59 | s[len-1] = last;
60 | return suff;
61 | }
62 | s[len-1] = last;
63 | return atof(s);
64 | }
65 |
66 | double atoft(char *s)
67 | /* time suffixes, returns seconds */
68 | {
69 | char last;
70 | int len;
71 | double suff = 1.0;
72 | len = strlen(s);
73 | last = s[len-1];
74 | s[len-1] = '\0';
75 | switch (last) {
76 | case 'h':
77 | case 'H':
78 | suff *= 60;
79 | case 'm':
80 | case 'M':
81 | suff *= 60;
82 | case 's':
83 | case 'S':
84 | suff *= atof(s);
85 | s[len-1] = last;
86 | return suff;
87 | }
88 | s[len-1] = last;
89 | return atof(s);
90 | }
91 |
92 | double atofp(char *s)
93 | /* percent suffixes */
94 | {
95 | char last;
96 | int len;
97 | double suff = 1.0;
98 | len = strlen(s);
99 | last = s[len-1];
100 | s[len-1] = '\0';
101 | switch (last) {
102 | case '%':
103 | suff *= 0.01;
104 | suff *= atof(s);
105 | s[len-1] = last;
106 | return suff;
107 | }
108 | s[len-1] = last;
109 | return atof(s);
110 | }
111 |
112 | int nearest_gain(rtlsdr_dev_t *dev, int target_gain)
113 | {
114 | int i, r, err1, err2, count, nearest;
115 | int* gains;
116 | r = rtlsdr_set_tuner_gain_mode(dev, 1);
117 | if (r < 0) {
118 | fprintf(stderr, "WARNING: Failed to enable manual gain.\n");
119 | return r;
120 | }
121 | count = rtlsdr_get_tuner_gains(dev, NULL);
122 | if (count <= 0) {
123 | return 0;
124 | }
125 | gains = malloc(sizeof(int) * count);
126 | count = rtlsdr_get_tuner_gains(dev, gains);
127 | nearest = gains[0];
128 | for (i=0; i= 0 && device < device_count) {
263 | fprintf(stderr, "Using device %d: %s\n",
264 | device, rtlsdr_get_device_name((uint32_t)device));
265 | return device;
266 | }
267 | /* does string exact match a serial */
268 | for (i = 0; i < device_count; i++) {
269 | rtlsdr_get_device_usb_strings(i, vendor, product, serial);
270 | if (strcmp(s, serial) != 0) {
271 | continue;}
272 | device = i;
273 | fprintf(stderr, "Using device %d: %s\n",
274 | device, rtlsdr_get_device_name((uint32_t)device));
275 | return device;
276 | }
277 | /* does string prefix match a serial */
278 | for (i = 0; i < device_count; i++) {
279 | rtlsdr_get_device_usb_strings(i, vendor, product, serial);
280 | if (strncmp(s, serial, strlen(s)) != 0) {
281 | continue;}
282 | device = i;
283 | fprintf(stderr, "Using device %d: %s\n",
284 | device, rtlsdr_get_device_name((uint32_t)device));
285 | return device;
286 | }
287 | /* does string suffix match a serial */
288 | for (i = 0; i < device_count; i++) {
289 | rtlsdr_get_device_usb_strings(i, vendor, product, serial);
290 | offset = strlen(serial) - strlen(s);
291 | if (offset < 0) {
292 | continue;}
293 | if (strncmp(s, serial+offset, strlen(s)) != 0) {
294 | continue;}
295 | device = i;
296 | fprintf(stderr, "Using device %d: %s\n",
297 | device, rtlsdr_get_device_name((uint32_t)device));
298 | return device;
299 | }
300 | fprintf(stderr, "No matching devices found.\n");
301 | return -1;
302 | }
303 |
304 | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab
305 |
--------------------------------------------------------------------------------
/convenience/convenience.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 by Kyle Keen
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 2 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | /* a collection of user friendly tools */
19 |
20 | /*!
21 | * Convert standard suffixes (k, M, G) to double
22 | *
23 | * \param s a string to be parsed
24 | * \return double
25 | */
26 |
27 | double atofs(char *s);
28 |
29 | /*!
30 | * Convert time suffixes (s, m, h) to double
31 | *
32 | * \param s a string to be parsed
33 | * \return seconds as double
34 | */
35 |
36 | double atoft(char *s);
37 |
38 | /*!
39 | * Convert percent suffixe (%) to double
40 | *
41 | * \param s a string to be parsed
42 | * \return double
43 | */
44 |
45 | double atofp(char *s);
46 |
47 | /*!
48 | * Find nearest supported gain
49 | *
50 | * \param dev the device handle given by rtlsdr_open()
51 | * \param target_gain in tenths of a dB
52 | * \return 0 on success
53 | */
54 |
55 | int nearest_gain(rtlsdr_dev_t *dev, int target_gain);
56 |
57 | /*!
58 | * Set device frequency and report status on stderr
59 | *
60 | * \param dev the device handle given by rtlsdr_open()
61 | * \param frequency in Hz
62 | * \return 0 on success
63 | */
64 |
65 | int verbose_set_frequency(rtlsdr_dev_t *dev, uint32_t frequency);
66 |
67 | /*!
68 | * Set device sample rate and report status on stderr
69 | *
70 | * \param dev the device handle given by rtlsdr_open()
71 | * \param samp_rate in samples/second
72 | * \return 0 on success
73 | */
74 |
75 | int verbose_set_sample_rate(rtlsdr_dev_t *dev, uint32_t samp_rate);
76 |
77 | /*!
78 | * Enable or disable the direct sampling mode and report status on stderr
79 | *
80 | * \param dev the device handle given by rtlsdr_open()
81 | * \param on 0 means disabled, 1 I-ADC input enabled, 2 Q-ADC input enabled
82 | * \return 0 on success
83 | */
84 |
85 | int verbose_direct_sampling(rtlsdr_dev_t *dev, int on);
86 |
87 | /*!
88 | * Enable offset tuning and report status on stderr
89 | *
90 | * \param dev the device handle given by rtlsdr_open()
91 | * \return 0 on success
92 | */
93 |
94 | int verbose_offset_tuning(rtlsdr_dev_t *dev);
95 |
96 | /*!
97 | * Enable auto gain and report status on stderr
98 | *
99 | * \param dev the device handle given by rtlsdr_open()
100 | * \return 0 on success
101 | */
102 |
103 | int verbose_auto_gain(rtlsdr_dev_t *dev);
104 |
105 | /*!
106 | * Set tuner gain and report status on stderr
107 | *
108 | * \param dev the device handle given by rtlsdr_open()
109 | * \param gain in tenths of a dB
110 | * \return 0 on success
111 | */
112 |
113 | int verbose_gain_set(rtlsdr_dev_t *dev, int gain);
114 |
115 | /*!
116 | * Set the frequency correction value for the device and report status on stderr.
117 | *
118 | * \param dev the device handle given by rtlsdr_open()
119 | * \param ppm_error correction value in parts per million (ppm)
120 | * \return 0 on success
121 | */
122 |
123 | int verbose_ppm_set(rtlsdr_dev_t *dev, int ppm_error);
124 |
125 | /*!
126 | * Reset buffer
127 | *
128 | * \param dev the device handle given by rtlsdr_open()
129 | * \return 0 on success
130 | */
131 |
132 | int verbose_reset_buffer(rtlsdr_dev_t *dev);
133 |
134 | /*!
135 | * Find the closest matching device.
136 | *
137 | * \param s a string to be parsed
138 | * \return dev_index int, -1 on error
139 | */
140 |
141 | int verbose_device_search(char *s);
142 |
143 |
--------------------------------------------------------------------------------
/getopt/getopt.c:
--------------------------------------------------------------------------------
1 | /* Getopt for GNU.
2 | NOTE: getopt is now part of the C library, so if you don't know what
3 | "Keep this file name-space clean" means, talk to drepper@gnu.org
4 | before changing it!
5 | Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001
6 | Free Software Foundation, Inc.
7 | This file is part of the GNU C Library.
8 |
9 | The GNU C Library is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU Lesser General Public
11 | License as published by the Free Software Foundation; either
12 | version 2.1 of the License, or (at your option) any later version.
13 |
14 | The GNU C Library is distributed in the hope that it will be useful,
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 | Lesser General Public License for more details.
18 |
19 | You should have received a copy of the GNU Lesser General Public
20 | License along with the GNU C Library; if not, write to the Free
21 | Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
22 | 02111-1307 USA. */
23 | /* This tells Alpha OSF/1 not to define a getopt prototype in .
24 | Ditto for AIX 3.2 and . */
25 | #ifndef _NO_PROTO
26 | # define _NO_PROTO
27 | #endif
28 |
29 | #ifdef HAVE_CONFIG_H
30 | # include
31 | #endif
32 |
33 | #if !defined __STDC__ || !__STDC__
34 | /* This is a separate conditional since some stdc systems
35 | reject `defined (const)'. */
36 | # ifndef const
37 | # define const
38 | # endif
39 | #endif
40 |
41 | #include
42 |
43 | /* Comment out all this code if we are using the GNU C Library, and are not
44 | actually compiling the library itself. This code is part of the GNU C
45 | Library, but also included in many other GNU distributions. Compiling
46 | and linking in this code is a waste when using the GNU C library
47 | (especially if it is a shared library). Rather than having every GNU
48 | program understand `configure --with-gnu-libc' and omit the object files,
49 | it is simpler to just do this in the source for each such file. */
50 |
51 | #define GETOPT_INTERFACE_VERSION 2
52 | #if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
53 | # include
54 | # if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
55 | # define ELIDE_CODE
56 | # endif
57 | #endif
58 |
59 | #ifndef ELIDE_CODE
60 |
61 |
62 | /* This needs to come after some library #include
63 | to get __GNU_LIBRARY__ defined. */
64 | #ifdef __GNU_LIBRARY__
65 | /* Don't include stdlib.h for non-GNU C libraries because some of them
66 | contain conflicting prototypes for getopt. */
67 | # include
68 | # include
69 | #endif /* GNU C library. */
70 |
71 | #ifdef VMS
72 | # include
73 | # if HAVE_STRING_H - 0
74 | # include
75 | # endif
76 | #endif
77 |
78 | #ifndef _
79 | /* This is for other GNU distributions with internationalized messages. */
80 | # if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC
81 | # include
82 | # ifndef _
83 | # define _(msgid) gettext (msgid)
84 | # endif
85 | # else
86 | # define _(msgid) (msgid)
87 | # endif
88 | #endif
89 |
90 | /* This version of `getopt' appears to the caller like standard Unix `getopt'
91 | but it behaves differently for the user, since it allows the user
92 | to intersperse the options with the other arguments.
93 |
94 | As `getopt' works, it permutes the elements of ARGV so that,
95 | when it is done, all the options precede everything else. Thus
96 | all application programs are extended to handle flexible argument order.
97 |
98 | Setting the environment variable POSIXLY_CORRECT disables permutation.
99 | Then the behavior is completely standard.
100 |
101 | GNU application programs can use a third alternative mode in which
102 | they can distinguish the relative order of options and other arguments. */
103 |
104 | #include "getopt.h"
105 |
106 | /* For communication from `getopt' to the caller.
107 | When `getopt' finds an option that takes an argument,
108 | the argument value is returned here.
109 | Also, when `ordering' is RETURN_IN_ORDER,
110 | each non-option ARGV-element is returned here. */
111 |
112 | char *optarg;
113 |
114 | /* Index in ARGV of the next element to be scanned.
115 | This is used for communication to and from the caller
116 | and for communication between successive calls to `getopt'.
117 |
118 | On entry to `getopt', zero means this is the first call; initialize.
119 |
120 | When `getopt' returns -1, this is the index of the first of the
121 | non-option elements that the caller should itself scan.
122 |
123 | Otherwise, `optind' communicates from one call to the next
124 | how much of ARGV has been scanned so far. */
125 |
126 | /* 1003.2 says this must be 1 before any call. */
127 | int optind = 1;
128 |
129 | /* Formerly, initialization of getopt depended on optind==0, which
130 | causes problems with re-calling getopt as programs generally don't
131 | know that. */
132 |
133 | int __getopt_initialized;
134 |
135 | /* The next char to be scanned in the option-element
136 | in which the last option character we returned was found.
137 | This allows us to pick up the scan where we left off.
138 |
139 | If this is zero, or a null string, it means resume the scan
140 | by advancing to the next ARGV-element. */
141 |
142 | static char *nextchar;
143 |
144 | /* Callers store zero here to inhibit the error message
145 | for unrecognized options. */
146 |
147 | int opterr = 1;
148 |
149 | /* Set to an option character which was unrecognized.
150 | This must be initialized on some systems to avoid linking in the
151 | system's own getopt implementation. */
152 |
153 | int optopt = '?';
154 |
155 | /* Describe how to deal with options that follow non-option ARGV-elements.
156 |
157 | If the caller did not specify anything,
158 | the default is REQUIRE_ORDER if the environment variable
159 | POSIXLY_CORRECT is defined, PERMUTE otherwise.
160 |
161 | REQUIRE_ORDER means don't recognize them as options;
162 | stop option processing when the first non-option is seen.
163 | This is what Unix does.
164 | This mode of operation is selected by either setting the environment
165 | variable POSIXLY_CORRECT, or using `+' as the first character
166 | of the list of option characters.
167 |
168 | PERMUTE is the default. We permute the contents of ARGV as we scan,
169 | so that eventually all the non-options are at the end. This allows options
170 | to be given in any order, even with programs that were not written to
171 | expect this.
172 |
173 | RETURN_IN_ORDER is an option available to programs that were written
174 | to expect options and other ARGV-elements in any order and that care about
175 | the ordering of the two. We describe each non-option ARGV-element
176 | as if it were the argument of an option with character code 1.
177 | Using `-' as the first character of the list of option characters
178 | selects this mode of operation.
179 |
180 | The special argument `--' forces an end of option-scanning regardless
181 | of the value of `ordering'. In the case of RETURN_IN_ORDER, only
182 | `--' can cause `getopt' to return -1 with `optind' != ARGC. */
183 |
184 | static enum
185 | {
186 | REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
187 | } ordering;
188 |
189 | /* Value of POSIXLY_CORRECT environment variable. */
190 | static char *posixly_correct;
191 |
192 | #ifdef __GNU_LIBRARY__
193 | /* We want to avoid inclusion of string.h with non-GNU libraries
194 | because there are many ways it can cause trouble.
195 | On some systems, it contains special magic macros that don't work
196 | in GCC. */
197 | # include
198 | # define my_index strchr
199 | #else
200 |
201 | # if 1 //HAVE_STRING_H
202 | # include
203 | # else
204 | # include
205 | # endif
206 |
207 | /* Avoid depending on library functions or files
208 | whose names are inconsistent. */
209 |
210 | #ifndef getenv
211 | #ifdef _MSC_VER
212 | // DDK will complain if you don't use the stdlib defined getenv
213 | #include
214 | #else
215 | extern char *getenv ();
216 | #endif
217 | #endif
218 |
219 | static char *
220 | my_index (str, chr)
221 | const char *str;
222 | int chr;
223 | {
224 | while (*str)
225 | {
226 | if (*str == chr)
227 | return (char *) str;
228 | str++;
229 | }
230 | return 0;
231 | }
232 |
233 | /* If using GCC, we can safely declare strlen this way.
234 | If not using GCC, it is ok not to declare it. */
235 | #ifdef __GNUC__
236 | /* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
237 | That was relevant to code that was here before. */
238 | # if (!defined __STDC__ || !__STDC__) && !defined strlen
239 | /* gcc with -traditional declares the built-in strlen to return int,
240 | and has done so at least since version 2.4.5. -- rms. */
241 | extern int strlen (const char *);
242 | # endif /* not __STDC__ */
243 | #endif /* __GNUC__ */
244 |
245 | #endif /* not __GNU_LIBRARY__ */
246 |
247 | /* Handle permutation of arguments. */
248 |
249 | /* Describe the part of ARGV that contains non-options that have
250 | been skipped. `first_nonopt' is the index in ARGV of the first of them;
251 | `last_nonopt' is the index after the last of them. */
252 |
253 | static int first_nonopt;
254 | static int last_nonopt;
255 |
256 | #ifdef _LIBC
257 | /* Stored original parameters.
258 | XXX This is no good solution. We should rather copy the args so
259 | that we can compare them later. But we must not use malloc(3). */
260 | extern int __libc_argc;
261 | extern char **__libc_argv;
262 |
263 | /* Bash 2.0 gives us an environment variable containing flags
264 | indicating ARGV elements that should not be considered arguments. */
265 |
266 | # ifdef USE_NONOPTION_FLAGS
267 | /* Defined in getopt_init.c */
268 | extern char *__getopt_nonoption_flags;
269 |
270 | static int nonoption_flags_max_len;
271 | static int nonoption_flags_len;
272 | # endif
273 |
274 | # ifdef USE_NONOPTION_FLAGS
275 | # define SWAP_FLAGS(ch1, ch2) \
276 | if (nonoption_flags_len > 0) \
277 | { \
278 | char __tmp = __getopt_nonoption_flags[ch1]; \
279 | __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
280 | __getopt_nonoption_flags[ch2] = __tmp; \
281 | }
282 | # else
283 | # define SWAP_FLAGS(ch1, ch2)
284 | # endif
285 | #else /* !_LIBC */
286 | # define SWAP_FLAGS(ch1, ch2)
287 | #endif /* _LIBC */
288 |
289 | /* Exchange two adjacent subsequences of ARGV.
290 | One subsequence is elements [first_nonopt,last_nonopt)
291 | which contains all the non-options that have been skipped so far.
292 | The other is elements [last_nonopt,optind), which contains all
293 | the options processed since those non-options were skipped.
294 |
295 | `first_nonopt' and `last_nonopt' are relocated so that they describe
296 | the new indices of the non-options in ARGV after they are moved. */
297 |
298 | #if defined __STDC__ && __STDC__
299 | static void exchange (char **);
300 | #endif
301 |
302 | static void
303 | exchange (argv)
304 | char **argv;
305 | {
306 | int bottom = first_nonopt;
307 | int middle = last_nonopt;
308 | int top = optind;
309 | char *tem;
310 |
311 | /* Exchange the shorter segment with the far end of the longer segment.
312 | That puts the shorter segment into the right place.
313 | It leaves the longer segment in the right place overall,
314 | but it consists of two parts that need to be swapped next. */
315 |
316 | #if defined _LIBC && defined USE_NONOPTION_FLAGS
317 | /* First make sure the handling of the `__getopt_nonoption_flags'
318 | string can work normally. Our top argument must be in the range
319 | of the string. */
320 | if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len)
321 | {
322 | /* We must extend the array. The user plays games with us and
323 | presents new arguments. */
324 | char *new_str = malloc (top + 1);
325 | if (new_str == NULL)
326 | nonoption_flags_len = nonoption_flags_max_len = 0;
327 | else
328 | {
329 | memset (__mempcpy (new_str, __getopt_nonoption_flags,
330 | nonoption_flags_max_len),
331 | '\0', top + 1 - nonoption_flags_max_len);
332 | nonoption_flags_max_len = top + 1;
333 | __getopt_nonoption_flags = new_str;
334 | }
335 | }
336 | #endif
337 |
338 | while (top > middle && middle > bottom)
339 | {
340 | if (top - middle > middle - bottom)
341 | {
342 | /* Bottom segment is the short one. */
343 | int len = middle - bottom;
344 | register int i;
345 |
346 | /* Swap it with the top part of the top segment. */
347 | for (i = 0; i < len; i++)
348 | {
349 | tem = argv[bottom + i];
350 | argv[bottom + i] = argv[top - (middle - bottom) + i];
351 | argv[top - (middle - bottom) + i] = tem;
352 | SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
353 | }
354 | /* Exclude the moved bottom segment from further swapping. */
355 | top -= len;
356 | }
357 | else
358 | {
359 | /* Top segment is the short one. */
360 | int len = top - middle;
361 | register int i;
362 |
363 | /* Swap it with the bottom part of the bottom segment. */
364 | for (i = 0; i < len; i++)
365 | {
366 | tem = argv[bottom + i];
367 | argv[bottom + i] = argv[middle + i];
368 | argv[middle + i] = tem;
369 | SWAP_FLAGS (bottom + i, middle + i);
370 | }
371 | /* Exclude the moved top segment from further swapping. */
372 | bottom += len;
373 | }
374 | }
375 |
376 | /* Update records for the slots the non-options now occupy. */
377 |
378 | first_nonopt += (optind - last_nonopt);
379 | last_nonopt = optind;
380 | }
381 |
382 | /* Initialize the internal data when the first call is made. */
383 |
384 | #if defined __STDC__ && __STDC__
385 | static const char *_getopt_initialize (int, char *const *, const char *);
386 | #endif
387 | static const char *
388 | _getopt_initialize (argc, argv, optstring)
389 | int argc;
390 | char *const *argv;
391 | const char *optstring;
392 | {
393 | /* Start processing options with ARGV-element 1 (since ARGV-element 0
394 | is the program name); the sequence of previously skipped
395 | non-option ARGV-elements is empty. */
396 |
397 | first_nonopt = last_nonopt = optind;
398 |
399 | nextchar = NULL;
400 |
401 | posixly_correct = getenv ("POSIXLY_CORRECT");
402 |
403 | /* Determine how to handle the ordering of options and nonoptions. */
404 |
405 | if (optstring[0] == '-')
406 | {
407 | ordering = RETURN_IN_ORDER;
408 | ++optstring;
409 | }
410 | else if (optstring[0] == '+')
411 | {
412 | ordering = REQUIRE_ORDER;
413 | ++optstring;
414 | }
415 | else if (posixly_correct != NULL)
416 | ordering = REQUIRE_ORDER;
417 | else
418 | ordering = PERMUTE;
419 |
420 | #if defined _LIBC && defined USE_NONOPTION_FLAGS
421 | if (posixly_correct == NULL
422 | && argc == __libc_argc && argv == __libc_argv)
423 | {
424 | if (nonoption_flags_max_len == 0)
425 | {
426 | if (__getopt_nonoption_flags == NULL
427 | || __getopt_nonoption_flags[0] == '\0')
428 | nonoption_flags_max_len = -1;
429 | else
430 | {
431 | const char *orig_str = __getopt_nonoption_flags;
432 | int len = nonoption_flags_max_len = strlen (orig_str);
433 | if (nonoption_flags_max_len < argc)
434 | nonoption_flags_max_len = argc;
435 | __getopt_nonoption_flags =
436 | (char *) malloc (nonoption_flags_max_len);
437 | if (__getopt_nonoption_flags == NULL)
438 | nonoption_flags_max_len = -1;
439 | else
440 | memset (__mempcpy (__getopt_nonoption_flags, orig_str, len),
441 | '\0', nonoption_flags_max_len - len);
442 | }
443 | }
444 | nonoption_flags_len = nonoption_flags_max_len;
445 | }
446 | else
447 | nonoption_flags_len = 0;
448 | #endif
449 |
450 | return optstring;
451 | }
452 |
453 | /* Scan elements of ARGV (whose length is ARGC) for option characters
454 | given in OPTSTRING.
455 |
456 | If an element of ARGV starts with '-', and is not exactly "-" or "--",
457 | then it is an option element. The characters of this element
458 | (aside from the initial '-') are option characters. If `getopt'
459 | is called repeatedly, it returns successively each of the option characters
460 | from each of the option elements.
461 |
462 | If `getopt' finds another option character, it returns that character,
463 | updating `optind' and `nextchar' so that the next call to `getopt' can
464 | resume the scan with the following option character or ARGV-element.
465 |
466 | If there are no more option characters, `getopt' returns -1.
467 | Then `optind' is the index in ARGV of the first ARGV-element
468 | that is not an option. (The ARGV-elements have been permuted
469 | so that those that are not options now come last.)
470 |
471 | OPTSTRING is a string containing the legitimate option characters.
472 | If an option character is seen that is not listed in OPTSTRING,
473 | return '?' after printing an error message. If you set `opterr' to
474 | zero, the error message is suppressed but we still return '?'.
475 |
476 | If a char in OPTSTRING is followed by a colon, that means it wants an arg,
477 | so the following text in the same ARGV-element, or the text of the following
478 | ARGV-element, is returned in `optarg'. Two colons mean an option that
479 | wants an optional arg; if there is text in the current ARGV-element,
480 | it is returned in `optarg', otherwise `optarg' is set to zero.
481 |
482 | If OPTSTRING starts with `-' or `+', it requests different methods of
483 | handling the non-option ARGV-elements.
484 | See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
485 |
486 | Long-named options begin with `--' instead of `-'.
487 | Their names may be abbreviated as long as the abbreviation is unique
488 | or is an exact match for some defined option. If they have an
489 | argument, it follows the option name in the same ARGV-element, separated
490 | from the option name by a `=', or else the in next ARGV-element.
491 | When `getopt' finds a long-named option, it returns 0 if that option's
492 | `flag' field is nonzero, the value of the option's `val' field
493 | if the `flag' field is zero.
494 |
495 | The elements of ARGV aren't really const, because we permute them.
496 | But we pretend they're const in the prototype to be compatible
497 | with other systems.
498 |
499 | LONGOPTS is a vector of `struct option' terminated by an
500 | element containing a name which is zero.
501 |
502 | LONGIND returns the index in LONGOPT of the long-named option found.
503 | It is only valid when a long-named option has been found by the most
504 | recent call.
505 |
506 | If LONG_ONLY is nonzero, '-' as well as '--' can introduce
507 | long-named options. */
508 |
509 | int
510 | _getopt_internal (argc, argv, optstring, longopts, longind, long_only)
511 | int argc;
512 | char *const *argv;
513 | const char *optstring;
514 | const struct option *longopts;
515 | int *longind;
516 | int long_only;
517 | {
518 | int print_errors = opterr;
519 | if (optstring[0] == ':')
520 | print_errors = 0;
521 |
522 | if (argc < 1)
523 | return -1;
524 |
525 | optarg = NULL;
526 |
527 | if (optind == 0 || !__getopt_initialized)
528 | {
529 | if (optind == 0)
530 | optind = 1; /* Don't scan ARGV[0], the program name. */
531 | optstring = _getopt_initialize (argc, argv, optstring);
532 | __getopt_initialized = 1;
533 | }
534 |
535 | /* Test whether ARGV[optind] points to a non-option argument.
536 | Either it does not have option syntax, or there is an environment flag
537 | from the shell indicating it is not an option. The later information
538 | is only used when the used in the GNU libc. */
539 | #if defined _LIBC && defined USE_NONOPTION_FLAGS
540 | # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \
541 | || (optind < nonoption_flags_len \
542 | && __getopt_nonoption_flags[optind] == '1'))
543 | #else
544 | # define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0')
545 | #endif
546 |
547 | if (nextchar == NULL || *nextchar == '\0')
548 | {
549 | /* Advance to the next ARGV-element. */
550 |
551 | /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
552 | moved back by the user (who may also have changed the arguments). */
553 | if (last_nonopt > optind)
554 | last_nonopt = optind;
555 | if (first_nonopt > optind)
556 | first_nonopt = optind;
557 |
558 | if (ordering == PERMUTE)
559 | {
560 | /* If we have just processed some options following some non-options,
561 | exchange them so that the options come first. */
562 |
563 | if (first_nonopt != last_nonopt && last_nonopt != optind)
564 | exchange ((char **) argv);
565 | else if (last_nonopt != optind)
566 | first_nonopt = optind;
567 |
568 | /* Skip any additional non-options
569 | and extend the range of non-options previously skipped. */
570 |
571 | while (optind < argc && NONOPTION_P)
572 | optind++;
573 | last_nonopt = optind;
574 | }
575 |
576 | /* The special ARGV-element `--' means premature end of options.
577 | Skip it like a null option,
578 | then exchange with previous non-options as if it were an option,
579 | then skip everything else like a non-option. */
580 |
581 | if (optind != argc && !strcmp (argv[optind], "--"))
582 | {
583 | optind++;
584 |
585 | if (first_nonopt != last_nonopt && last_nonopt != optind)
586 | exchange ((char **) argv);
587 | else if (first_nonopt == last_nonopt)
588 | first_nonopt = optind;
589 | last_nonopt = argc;
590 |
591 | optind = argc;
592 | }
593 |
594 | /* If we have done all the ARGV-elements, stop the scan
595 | and back over any non-options that we skipped and permuted. */
596 |
597 | if (optind == argc)
598 | {
599 | /* Set the next-arg-index to point at the non-options
600 | that we previously skipped, so the caller will digest them. */
601 | if (first_nonopt != last_nonopt)
602 | optind = first_nonopt;
603 | return -1;
604 | }
605 |
606 | /* If we have come to a non-option and did not permute it,
607 | either stop the scan or describe it to the caller and pass it by. */
608 |
609 | if (NONOPTION_P)
610 | {
611 | if (ordering == REQUIRE_ORDER)
612 | return -1;
613 | optarg = argv[optind++];
614 | return 1;
615 | }
616 |
617 | /* We have found another option-ARGV-element.
618 | Skip the initial punctuation. */
619 |
620 | nextchar = (argv[optind] + 1
621 | + (longopts != NULL && argv[optind][1] == '-'));
622 | }
623 |
624 | /* Decode the current option-ARGV-element. */
625 |
626 | /* Check whether the ARGV-element is a long option.
627 |
628 | If long_only and the ARGV-element has the form "-f", where f is
629 | a valid short option, don't consider it an abbreviated form of
630 | a long option that starts with f. Otherwise there would be no
631 | way to give the -f short option.
632 |
633 | On the other hand, if there's a long option "fubar" and
634 | the ARGV-element is "-fu", do consider that an abbreviation of
635 | the long option, just like "--fu", and not "-f" with arg "u".
636 |
637 | This distinction seems to be the most useful approach. */
638 |
639 | if (longopts != NULL
640 | && (argv[optind][1] == '-'
641 | || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1])))))
642 | {
643 | char *nameend;
644 | const struct option *p;
645 | const struct option *pfound = NULL;
646 | int exact = 0;
647 | int ambig = 0;
648 | int indfound = -1;
649 | int option_index;
650 |
651 | for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
652 | /* Do nothing. */ ;
653 |
654 | /* Test all long options for either exact match
655 | or abbreviated matches. */
656 | for (p = longopts, option_index = 0; p->name; p++, option_index++)
657 | if (!strncmp (p->name, nextchar, nameend - nextchar))
658 | {
659 | if ((unsigned int) (nameend - nextchar)
660 | == (unsigned int) strlen (p->name))
661 | {
662 | /* Exact match found. */
663 | pfound = p;
664 | indfound = option_index;
665 | exact = 1;
666 | break;
667 | }
668 | else if (pfound == NULL)
669 | {
670 | /* First nonexact match found. */
671 | pfound = p;
672 | indfound = option_index;
673 | }
674 | else if (long_only
675 | || pfound->has_arg != p->has_arg
676 | || pfound->flag != p->flag
677 | || pfound->val != p->val)
678 | /* Second or later nonexact match found. */
679 | ambig = 1;
680 | }
681 |
682 | if (ambig && !exact)
683 | {
684 | if (print_errors)
685 | fprintf (stderr, _("%s: option `%s' is ambiguous\n"),
686 | argv[0], argv[optind]);
687 | nextchar += strlen (nextchar);
688 | optind++;
689 | optopt = 0;
690 | return '?';
691 | }
692 |
693 | if (pfound != NULL)
694 | {
695 | option_index = indfound;
696 | optind++;
697 | if (*nameend)
698 | {
699 | /* Don't test has_arg with >, because some C compilers don't
700 | allow it to be used on enums. */
701 | if (pfound->has_arg)
702 | optarg = nameend + 1;
703 | else
704 | {
705 | if (print_errors)
706 | {
707 | if (argv[optind - 1][1] == '-')
708 | /* --option */
709 | fprintf (stderr,
710 | _("%s: option `--%s' doesn't allow an argument\n"),
711 | argv[0], pfound->name);
712 | else
713 | /* +option or -option */
714 | fprintf (stderr,
715 | _("%s: option `%c%s' doesn't allow an argument\n"),
716 | argv[0], argv[optind - 1][0], pfound->name);
717 | }
718 |
719 | nextchar += strlen (nextchar);
720 |
721 | optopt = pfound->val;
722 | return '?';
723 | }
724 | }
725 | else if (pfound->has_arg == 1)
726 | {
727 | if (optind < argc)
728 | optarg = argv[optind++];
729 | else
730 | {
731 | if (print_errors)
732 | fprintf (stderr,
733 | _("%s: option `%s' requires an argument\n"),
734 | argv[0], argv[optind - 1]);
735 | nextchar += strlen (nextchar);
736 | optopt = pfound->val;
737 | return optstring[0] == ':' ? ':' : '?';
738 | }
739 | }
740 | nextchar += strlen (nextchar);
741 | if (longind != NULL)
742 | *longind = option_index;
743 | if (pfound->flag)
744 | {
745 | *(pfound->flag) = pfound->val;
746 | return 0;
747 | }
748 | return pfound->val;
749 | }
750 |
751 | /* Can't find it as a long option. If this is not getopt_long_only,
752 | or the option starts with '--' or is not a valid short
753 | option, then it's an error.
754 | Otherwise interpret it as a short option. */
755 | if (!long_only || argv[optind][1] == '-'
756 | || my_index (optstring, *nextchar) == NULL)
757 | {
758 | if (print_errors)
759 | {
760 | if (argv[optind][1] == '-')
761 | /* --option */
762 | fprintf (stderr, _("%s: unrecognized option `--%s'\n"),
763 | argv[0], nextchar);
764 | else
765 | /* +option or -option */
766 | fprintf (stderr, _("%s: unrecognized option `%c%s'\n"),
767 | argv[0], argv[optind][0], nextchar);
768 | }
769 | nextchar = (char *) "";
770 | optind++;
771 | optopt = 0;
772 | return '?';
773 | }
774 | }
775 |
776 | /* Look at and handle the next short option-character. */
777 |
778 | {
779 | char c = *nextchar++;
780 | char *temp = my_index (optstring, c);
781 |
782 | /* Increment `optind' when we start to process its last character. */
783 | if (*nextchar == '\0')
784 | ++optind;
785 |
786 | if (temp == NULL || c == ':')
787 | {
788 | if (print_errors)
789 | {
790 | if (posixly_correct)
791 | /* 1003.2 specifies the format of this message. */
792 | fprintf (stderr, _("%s: illegal option -- %c\n"),
793 | argv[0], c);
794 | else
795 | fprintf (stderr, _("%s: invalid option -- %c\n"),
796 | argv[0], c);
797 | }
798 | optopt = c;
799 | return '?';
800 | }
801 | /* Convenience. Treat POSIX -W foo same as long option --foo */
802 | if (temp[0] == 'W' && temp[1] == ';')
803 | {
804 | char *nameend;
805 | const struct option *p;
806 | const struct option *pfound = NULL;
807 | int exact = 0;
808 | int ambig = 0;
809 | int indfound = 0;
810 | int option_index;
811 |
812 | /* This is an option that requires an argument. */
813 | if (*nextchar != '\0')
814 | {
815 | optarg = nextchar;
816 | /* If we end this ARGV-element by taking the rest as an arg,
817 | we must advance to the next element now. */
818 | optind++;
819 | }
820 | else if (optind == argc)
821 | {
822 | if (print_errors)
823 | {
824 | /* 1003.2 specifies the format of this message. */
825 | fprintf (stderr, _("%s: option requires an argument -- %c\n"),
826 | argv[0], c);
827 | }
828 | optopt = c;
829 | if (optstring[0] == ':')
830 | c = ':';
831 | else
832 | c = '?';
833 | return c;
834 | }
835 | else
836 | /* We already incremented `optind' once;
837 | increment it again when taking next ARGV-elt as argument. */
838 | optarg = argv[optind++];
839 |
840 | /* optarg is now the argument, see if it's in the
841 | table of longopts. */
842 |
843 | for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++)
844 | /* Do nothing. */ ;
845 |
846 | /* Test all long options for either exact match
847 | or abbreviated matches. */
848 | for (p = longopts, option_index = 0; p->name; p++, option_index++)
849 | if (!strncmp (p->name, nextchar, nameend - nextchar))
850 | {
851 | if ((unsigned int) (nameend - nextchar) == strlen (p->name))
852 | {
853 | /* Exact match found. */
854 | pfound = p;
855 | indfound = option_index;
856 | exact = 1;
857 | break;
858 | }
859 | else if (pfound == NULL)
860 | {
861 | /* First nonexact match found. */
862 | pfound = p;
863 | indfound = option_index;
864 | }
865 | else
866 | /* Second or later nonexact match found. */
867 | ambig = 1;
868 | }
869 | if (ambig && !exact)
870 | {
871 | if (print_errors)
872 | fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"),
873 | argv[0], argv[optind]);
874 | nextchar += strlen (nextchar);
875 | optind++;
876 | return '?';
877 | }
878 | if (pfound != NULL)
879 | {
880 | option_index = indfound;
881 | if (*nameend)
882 | {
883 | /* Don't test has_arg with >, because some C compilers don't
884 | allow it to be used on enums. */
885 | if (pfound->has_arg)
886 | optarg = nameend + 1;
887 | else
888 | {
889 | if (print_errors)
890 | fprintf (stderr, _("\
891 | %s: option `-W %s' doesn't allow an argument\n"),
892 | argv[0], pfound->name);
893 |
894 | nextchar += strlen (nextchar);
895 | return '?';
896 | }
897 | }
898 | else if (pfound->has_arg == 1)
899 | {
900 | if (optind < argc)
901 | optarg = argv[optind++];
902 | else
903 | {
904 | if (print_errors)
905 | fprintf (stderr,
906 | _("%s: option `%s' requires an argument\n"),
907 | argv[0], argv[optind - 1]);
908 | nextchar += strlen (nextchar);
909 | return optstring[0] == ':' ? ':' : '?';
910 | }
911 | }
912 | nextchar += strlen (nextchar);
913 | if (longind != NULL)
914 | *longind = option_index;
915 | if (pfound->flag)
916 | {
917 | *(pfound->flag) = pfound->val;
918 | return 0;
919 | }
920 | return pfound->val;
921 | }
922 | nextchar = NULL;
923 | return 'W'; /* Let the application handle it. */
924 | }
925 | if (temp[1] == ':')
926 | {
927 | if (temp[2] == ':')
928 | {
929 | /* This is an option that accepts an argument optionally. */
930 | if (*nextchar != '\0')
931 | {
932 | optarg = nextchar;
933 | optind++;
934 | }
935 | else
936 | optarg = NULL;
937 | nextchar = NULL;
938 | }
939 | else
940 | {
941 | /* This is an option that requires an argument. */
942 | if (*nextchar != '\0')
943 | {
944 | optarg = nextchar;
945 | /* If we end this ARGV-element by taking the rest as an arg,
946 | we must advance to the next element now. */
947 | optind++;
948 | }
949 | else if (optind == argc)
950 | {
951 | if (print_errors)
952 | {
953 | /* 1003.2 specifies the format of this message. */
954 | fprintf (stderr,
955 | _("%s: option requires an argument -- %c\n"),
956 | argv[0], c);
957 | }
958 | optopt = c;
959 | if (optstring[0] == ':')
960 | c = ':';
961 | else
962 | c = '?';
963 | }
964 | else
965 | /* We already incremented `optind' once;
966 | increment it again when taking next ARGV-elt as argument. */
967 | optarg = argv[optind++];
968 | nextchar = NULL;
969 | }
970 | }
971 | return c;
972 | }
973 | }
974 |
975 | int
976 | getopt (argc, argv, optstring)
977 | int argc;
978 | char *const *argv;
979 | const char *optstring;
980 | {
981 | return _getopt_internal (argc, argv, optstring,
982 | (const struct option *) 0,
983 | (int *) 0,
984 | 0);
985 | }
986 |
987 | #endif /* Not ELIDE_CODE. */
988 |
989 | #ifdef TEST
990 |
991 | /* Compile with -DTEST to make an executable for use in testing
992 | the above definition of `getopt'. */
993 |
994 | int
995 | main (argc, argv)
996 | int argc;
997 | char **argv;
998 | {
999 | int c;
1000 | int digit_optind = 0;
1001 |
1002 | while (1)
1003 | {
1004 | int this_option_optind = optind ? optind : 1;
1005 |
1006 | c = getopt (argc, argv, "abc:d:0123456789");
1007 | if (c == -1)
1008 | break;
1009 |
1010 | switch (c)
1011 | {
1012 | case '0':
1013 | case '1':
1014 | case '2':
1015 | case '3':
1016 | case '4':
1017 | case '5':
1018 | case '6':
1019 | case '7':
1020 | case '8':
1021 | case '9':
1022 | if (digit_optind != 0 && digit_optind != this_option_optind)
1023 | printf ("digits occur in two different argv-elements.\n");
1024 | digit_optind = this_option_optind;
1025 | printf ("option %c\n", c);
1026 | break;
1027 |
1028 | case 'a':
1029 | printf ("option a\n");
1030 | break;
1031 |
1032 | case 'b':
1033 | printf ("option b\n");
1034 | break;
1035 |
1036 | case 'c':
1037 | printf ("option c with value `%s'\n", optarg);
1038 | break;
1039 |
1040 | case '?':
1041 | break;
1042 |
1043 | default:
1044 | printf ("?? getopt returned character code 0%o ??\n", c);
1045 | }
1046 | }
1047 |
1048 | if (optind < argc)
1049 | {
1050 | printf ("non-option ARGV-elements: ");
1051 | while (optind < argc)
1052 | printf ("%s ", argv[optind++]);
1053 | printf ("\n");
1054 | }
1055 |
1056 | exit (0);
1057 | }
1058 |
1059 | #endif /* TEST */
1060 |
--------------------------------------------------------------------------------
/getopt/getopt.h:
--------------------------------------------------------------------------------
1 | /* Declarations for getopt.
2 | Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc.
3 | This file is part of the GNU C Library.
4 |
5 | The GNU C Library is free software; you can redistribute it and/or
6 | modify it under the terms of the GNU Lesser General Public
7 | License as published by the Free Software Foundation; either
8 | version 2.1 of the License, or (at your option) any later version.
9 |
10 | The GNU C Library is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 | Lesser General Public License for more details.
14 |
15 | You should have received a copy of the GNU Lesser General Public
16 | License along with the GNU C Library; if not, write to the Free
17 | Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
18 | 02111-1307 USA. */
19 |
20 | #ifndef _GETOPT_H
21 |
22 | #ifndef __need_getopt
23 | # define _GETOPT_H 1
24 | #endif
25 |
26 | /* If __GNU_LIBRARY__ is not already defined, either we are being used
27 | standalone, or this is the first header included in the source file.
28 | If we are being used with glibc, we need to include , but
29 | that does not exist if we are standalone. So: if __GNU_LIBRARY__ is
30 | not defined, include , which will pull in for us
31 | if it's from glibc. (Why ctype.h? It's guaranteed to exist and it
32 | doesn't flood the namespace with stuff the way some other headers do.) */
33 | #if !defined __GNU_LIBRARY__
34 | # include
35 | #endif
36 |
37 | #ifdef __cplusplus
38 | extern "C" {
39 | #endif
40 |
41 | /* For communication from `getopt' to the caller.
42 | When `getopt' finds an option that takes an argument,
43 | the argument value is returned here.
44 | Also, when `ordering' is RETURN_IN_ORDER,
45 | each non-option ARGV-element is returned here. */
46 |
47 | extern char *optarg;
48 |
49 | /* Index in ARGV of the next element to be scanned.
50 | This is used for communication to and from the caller
51 | and for communication between successive calls to `getopt'.
52 |
53 | On entry to `getopt', zero means this is the first call; initialize.
54 |
55 | When `getopt' returns -1, this is the index of the first of the
56 | non-option elements that the caller should itself scan.
57 |
58 | Otherwise, `optind' communicates from one call to the next
59 | how much of ARGV has been scanned so far. */
60 |
61 | extern int optind;
62 |
63 | /* Callers store zero here to inhibit the error message `getopt' prints
64 | for unrecognized options. */
65 |
66 | extern int opterr;
67 |
68 | /* Set to an option character which was unrecognized. */
69 |
70 | extern int optopt;
71 |
72 | #ifndef __need_getopt
73 | /* Describe the long-named options requested by the application.
74 | The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
75 | of `struct option' terminated by an element containing a name which is
76 | zero.
77 |
78 | The field `has_arg' is:
79 | no_argument (or 0) if the option does not take an argument,
80 | required_argument (or 1) if the option requires an argument,
81 | optional_argument (or 2) if the option takes an optional argument.
82 |
83 | If the field `flag' is not NULL, it points to a variable that is set
84 | to the value given in the field `val' when the option is found, but
85 | left unchanged if the option is not found.
86 |
87 | To have a long-named option do something other than set an `int' to
88 | a compiled-in constant, such as set a value from `optarg', set the
89 | option's `flag' field to zero and its `val' field to a nonzero
90 | value (the equivalent single-letter option character, if there is
91 | one). For long options that have a zero `flag' field, `getopt'
92 | returns the contents of the `val' field. */
93 |
94 | struct option
95 | {
96 | # if (defined __STDC__ && __STDC__) || defined __cplusplus
97 | const char *name;
98 | # else
99 | char *name;
100 | # endif
101 | /* has_arg can't be an enum because some compilers complain about
102 | type mismatches in all the code that assumes it is an int. */
103 | int has_arg;
104 | int *flag;
105 | int val;
106 | };
107 |
108 | /* Names for the values of the `has_arg' field of `struct option'. */
109 |
110 | # define no_argument 0
111 | # define required_argument 1
112 | # define optional_argument 2
113 | #endif /* need getopt */
114 |
115 |
116 | /* Get definitions and prototypes for functions to process the
117 | arguments in ARGV (ARGC of them, minus the program name) for
118 | options given in OPTS.
119 |
120 | Return the option character from OPTS just read. Return -1 when
121 | there are no more options. For unrecognized options, or options
122 | missing arguments, `optopt' is set to the option letter, and '?' is
123 | returned.
124 |
125 | The OPTS string is a list of characters which are recognized option
126 | letters, optionally followed by colons, specifying that that letter
127 | takes an argument, to be placed in `optarg'.
128 |
129 | If a letter in OPTS is followed by two colons, its argument is
130 | optional. This behavior is specific to the GNU `getopt'.
131 |
132 | The argument `--' causes premature termination of argument
133 | scanning, explicitly telling `getopt' that there are no more
134 | options.
135 |
136 | If OPTS begins with `--', then non-option arguments are treated as
137 | arguments to the option '\0'. This behavior is specific to the GNU
138 | `getopt'. */
139 |
140 | #if (defined __STDC__ && __STDC__) || defined __cplusplus
141 | # ifdef __GNU_LIBRARY__
142 | /* Many other libraries have conflicting prototypes for getopt, with
143 | differences in the consts, in stdlib.h. To avoid compilation
144 | errors, only prototype getopt for the GNU C library. */
145 | extern int getopt (int __argc, char *const *__argv, const char *__shortopts);
146 | # else /* not __GNU_LIBRARY__ */
147 | extern int getopt ();
148 | # endif /* __GNU_LIBRARY__ */
149 |
150 | # ifndef __need_getopt
151 | extern int getopt_long (int __argc, char *const *__argv, const char *__shortopts,
152 | const struct option *__longopts, int *__longind);
153 | extern int getopt_long_only (int __argc, char *const *__argv,
154 | const char *__shortopts,
155 | const struct option *__longopts, int *__longind);
156 |
157 | /* Internal only. Users should not call this directly. */
158 | extern int _getopt_internal (int __argc, char *const *__argv,
159 | const char *__shortopts,
160 | const struct option *__longopts, int *__longind,
161 | int __long_only);
162 | # endif
163 | #else /* not __STDC__ */
164 | extern int getopt ();
165 | # ifndef __need_getopt
166 | extern int getopt_long ();
167 | extern int getopt_long_only ();
168 |
169 | extern int _getopt_internal ();
170 | # endif
171 | #endif /* __STDC__ */
172 |
173 | #ifdef __cplusplus
174 | }
175 | #endif
176 |
177 | /* Make sure we later can get all the definitions and declarations. */
178 | #undef __need_getopt
179 |
180 | #endif /* getopt.h */
181 |
--------------------------------------------------------------------------------
/rtl_fm.c:
--------------------------------------------------------------------------------
1 | /*
2 | * rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
3 | * Copyright (C) 2012 by Steve Markgraf
4 | * Copyright (C) 2012 by Hoernchen
5 | * Copyright (C) 2012 by Kyle Keen
6 | * Copyright (C) 2013 by Elias Oenal
7 | *
8 | * This program is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU General Public License as published by
10 | * the Free Software Foundation, either version 2 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * This program is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 |
23 | /*
24 | * written because people could not do real time
25 | * FM demod on Atom hardware with GNU radio
26 | * based on rtl_sdr.c and rtl_tcp.c
27 | *
28 | * lots of locks, but that is okay
29 | * (no many-to-many locks)
30 | *
31 | * todo:
32 | * sanity checks
33 | * scale squelch to other input parameters
34 | * test all the demodulations
35 | * pad output on hop
36 | * frequency ranges could be stored better
37 | * scaled AM demod amplification
38 | * auto-hop after time limit
39 | * peak detector to tune onto stronger signals
40 | * fifo for active hop frequency
41 | * clips
42 | * noise squelch
43 | * merge stereo patch
44 | * merge soft agc patch
45 | * merge udp patch
46 | * testmode to detect overruns
47 | * watchdog to reset bad dongle
48 | * fix oversampling
49 | */
50 |
51 | #include
52 | #include
53 | #include
54 | #include
55 | #include
56 |
57 | #ifndef _WIN32
58 | #include
59 | #else
60 | #include
61 | #include
62 | #include
63 | #include "getopt/getopt.h"
64 | #define usleep(x) Sleep(x/1000)
65 | #ifdef _MSC_VER
66 | #define round(x) (x > 0.0 ? floor(x + 0.5): ceil(x - 0.5))
67 | #endif
68 | #define _USE_MATH_DEFINES
69 | #endif
70 |
71 | #include
72 | #include
73 | #include
74 |
75 | #include "rtl-sdr.h"
76 | #include "convenience/convenience.h"
77 |
78 | #define DEFAULT_SAMPLE_RATE 24000
79 | #define DEFAULT_BUF_LENGTH (1 * 16384)
80 | #define MAXIMUM_OVERSAMPLE 16
81 | #define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH)
82 | #define AUTO_GAIN -100
83 | #define BUFFER_DUMP 4096
84 |
85 | #define FREQUENCIES_LIMIT 1000
86 |
87 | static volatile int do_exit = 0;
88 | static int lcm_post[17] = {1,1,1,3,1,5,3,7,1,9,5,11,3,13,7,15,1};
89 | static int ACTUAL_BUF_LENGTH;
90 |
91 | static int *atan_lut = NULL;
92 | static int atan_lut_size = 131072; /* 512 KB */
93 | static int atan_lut_coef = 8;
94 |
95 | struct dongle_state
96 | {
97 | int exit_flag;
98 | pthread_t thread;
99 | rtlsdr_dev_t *dev;
100 | int dev_index;
101 | uint32_t freq;
102 | uint32_t rate;
103 | int gain;
104 | uint16_t buf16[MAXIMUM_BUF_LENGTH];
105 | uint32_t buf_len;
106 | int ppm_error;
107 | int offset_tuning;
108 | int direct_sampling;
109 | int mute;
110 | struct demod_state *demod_target;
111 | };
112 |
113 | struct demod_state
114 | {
115 | int exit_flag;
116 | pthread_t thread;
117 | int16_t lowpassed[MAXIMUM_BUF_LENGTH];
118 | int lp_len;
119 | int16_t lp_i_hist[10][6];
120 | int16_t lp_q_hist[10][6];
121 | int16_t result[MAXIMUM_BUF_LENGTH];
122 | int16_t droop_i_hist[9];
123 | int16_t droop_q_hist[9];
124 | int result_len;
125 | int rate_in;
126 | int rate_out;
127 | int rate_out2;
128 | int now_r, now_j;
129 | int pre_r, pre_j;
130 | int prev_index;
131 | int downsample; /* min 1, max 256 */
132 | int post_downsample;
133 | int output_scale;
134 | int squelch_level, conseq_squelch, squelch_hits, terminate_on_squelch;
135 | int downsample_passes;
136 | int comp_fir_size;
137 | int custom_atan;
138 | int deemph, deemph_a;
139 | int now_lpr;
140 | int prev_lpr_index;
141 | int dc_block, dc_avg;
142 | void (*mode_demod)(struct demod_state*);
143 | pthread_rwlock_t rw;
144 | pthread_cond_t ready;
145 | pthread_mutex_t ready_m;
146 | struct output_state *output_target;
147 | };
148 |
149 | struct output_state
150 | {
151 | int exit_flag;
152 | pthread_t thread;
153 | FILE *file;
154 | char *filename;
155 | int16_t result[MAXIMUM_BUF_LENGTH];
156 | int result_len;
157 | int rate;
158 | pthread_rwlock_t rw;
159 | pthread_cond_t ready;
160 | pthread_mutex_t ready_m;
161 | };
162 |
163 | struct controller_state
164 | {
165 | int exit_flag;
166 | pthread_t thread;
167 | uint32_t freqs[FREQUENCIES_LIMIT];
168 | int freq_len;
169 | int freq_now;
170 | int edge;
171 | int wb_mode;
172 | pthread_cond_t hop;
173 | pthread_mutex_t hop_m;
174 | };
175 |
176 | // multiple of these, eventually
177 | struct dongle_state dongle;
178 | struct demod_state demod;
179 | struct output_state output;
180 | struct controller_state controller;
181 |
182 | void usage(void)
183 | {
184 | fprintf(stderr,
185 | "rtl_fm, a simple narrow band FM demodulator for RTL2832 based DVB-T receivers\n\n"
186 | "Use:\trtl_fm -f freq [-options] [filename]\n"
187 | "\t-f frequency_to_tune_to [Hz]\n"
188 | "\t use multiple -f for scanning (requires squelch)\n"
189 | "\t ranges supported, -f 118M:137M:25k\n"
190 | "\t[-M modulation (default: fm)]\n"
191 | "\t fm, wbfm, raw, am, usb, lsb\n"
192 | "\t wbfm == -M fm -s 170k -o 4 -A fast -r 32k -l 0 -E deemp\n"
193 | "\t raw mode outputs 2x16 bit IQ pairs\n"
194 | "\t[-s sample_rate (default: 24k)]\n"
195 | "\t[-d device_index (default: 0)]\n"
196 | "\t[-g tuner_gain (default: automatic)]\n"
197 | "\t[-l squelch_level (default: 0/off)]\n"
198 | //"\t for fm squelch is inverted\n"
199 | //"\t[-o oversampling (default: 1, 4 recommended)]\n"
200 | "\t[-p ppm_error (default: 0)]\n"
201 | "\t[-E enable_option (default: none)]\n"
202 | "\t use multiple -E to enable multiple options\n"
203 | "\t edge: enable lower edge tuning\n"
204 | "\t dc: enable dc blocking filter\n"
205 | "\t deemp: enable de-emphasis filter\n"
206 | "\t direct: enable direct sampling\n"
207 | "\t offset: enable offset tuning\n"
208 | "\tfilename ('-' means stdout)\n"
209 | "\t omitting the filename also uses stdout\n\n"
210 | "Experimental options:\n"
211 | "\t[-r resample_rate (default: none / same as -s)]\n"
212 | "\t[-t squelch_delay (default: 10)]\n"
213 | "\t +values will mute/scan, -values will exit\n"
214 | "\t[-F fir_size (default: off)]\n"
215 | "\t enables low-leakage downsample filter\n"
216 | "\t size can be 0 or 9. 0 has bad roll off\n"
217 | "\t[-A std/fast/lut choose atan math (default: std)]\n"
218 | //"\t[-C clip_path (default: off)\n"
219 | //"\t (create time stamped raw clips, requires squelch)\n"
220 | //"\t (path must have '\%s' and will expand to date_time_freq)\n"
221 | //"\t[-H hop_fifo (default: off)\n"
222 | //"\t (fifo will contain the active frequency)\n"
223 | "\n"
224 | "Produces signed 16 bit ints, use Sox or aplay to hear them.\n"
225 | "\trtl_fm ... | play -t raw -r 24k -es -b 16 -c 1 -V1 -\n"
226 | "\t | aplay -r 24k -f S16_LE -t raw -c 1\n"
227 | "\t -M wbfm | play -r 32k ... \n"
228 | "\t -s 22050 | multimon -t raw /dev/stdin\n\n");
229 | exit(1);
230 | }
231 |
232 | #ifdef _WIN32
233 | BOOL WINAPI
234 | sighandler(int signum)
235 | {
236 | if (CTRL_C_EVENT == signum) {
237 | fprintf(stderr, "Signal caught, exiting!\n");
238 | do_exit = 1;
239 | rtlsdr_cancel_async(dongle.dev);
240 | return TRUE;
241 | }
242 | return FALSE;
243 | }
244 | #else
245 | static void sighandler(int signum)
246 | {
247 | fprintf(stderr, "Signal caught, exiting!\n");
248 | do_exit = 1;
249 | rtlsdr_cancel_async(dongle.dev);
250 | }
251 | #endif
252 |
253 | /* more cond dumbness */
254 | #define safe_cond_signal(n, m) pthread_mutex_lock(m); pthread_cond_signal(n); pthread_mutex_unlock(m)
255 | #define safe_cond_wait(n, m) pthread_mutex_lock(m); pthread_cond_wait(n, m); pthread_mutex_unlock(m)
256 |
257 | /* {length, coef, coef, coef} and scaled by 2^15
258 | for now, only length 9, optimal way to get +85% bandwidth */
259 | #define CIC_TABLE_MAX 10
260 | int cic_9_tables[][10] = {
261 | {0,},
262 | {9, -156, -97, 2798, -15489, 61019, -15489, 2798, -97, -156},
263 | {9, -128, -568, 5593, -24125, 74126, -24125, 5593, -568, -128},
264 | {9, -129, -639, 6187, -26281, 77511, -26281, 6187, -639, -129},
265 | {9, -122, -612, 6082, -26353, 77818, -26353, 6082, -612, -122},
266 | {9, -120, -602, 6015, -26269, 77757, -26269, 6015, -602, -120},
267 | {9, -120, -582, 5951, -26128, 77542, -26128, 5951, -582, -120},
268 | {9, -119, -580, 5931, -26094, 77505, -26094, 5931, -580, -119},
269 | {9, -119, -578, 5921, -26077, 77484, -26077, 5921, -578, -119},
270 | {9, -119, -577, 5917, -26067, 77473, -26067, 5917, -577, -119},
271 | {9, -199, -362, 5303, -25505, 77489, -25505, 5303, -362, -199},
272 | };
273 |
274 | #ifdef _MSC_VER
275 | double log2(double n)
276 | {
277 | return log(n) / log(2.0);
278 | }
279 | #endif
280 |
281 | void rotate_90(unsigned char *buf, uint32_t len)
282 | /* 90 rotation is 1+0j, 0+1j, -1+0j, 0-1j
283 | or [0, 1, -3, 2, -4, -5, 7, -6] */
284 | {
285 | uint32_t i;
286 | unsigned char tmp;
287 | for (i=0; ilp_len) {
307 | d->now_r += d->lowpassed[i];
308 | d->now_j += d->lowpassed[i+1];
309 | i += 2;
310 | d->prev_index++;
311 | if (d->prev_index < d->downsample) {
312 | continue;
313 | }
314 | d->lowpassed[i2] = d->now_r; // * d->output_scale;
315 | d->lowpassed[i2+1] = d->now_j; // * d->output_scale;
316 | d->prev_index = 0;
317 | d->now_r = 0;
318 | d->now_j = 0;
319 | i2 += 2;
320 | }
321 | d->lp_len = i2;
322 | }
323 |
324 | int low_pass_simple(int16_t *signal2, int len, int step)
325 | // no wrap around, length must be multiple of step
326 | {
327 | int i, i2, sum;
328 | for(i=0; i < len; i+=step) {
329 | sum = 0;
330 | for(i2=0; i2rate_out;
346 | int slow = s->rate_out2;
347 | while (i < s->result_len) {
348 | s->now_lpr += s->result[i];
349 | i++;
350 | s->prev_lpr_index += slow;
351 | if (s->prev_lpr_index < fast) {
352 | continue;
353 | }
354 | s->result[i2] = (int16_t)(s->now_lpr / (fast/slow));
355 | s->prev_lpr_index -= fast;
356 | s->now_lpr = 0;
357 | i2 += 1;
358 | }
359 | s->result_len = i2;
360 | }
361 |
362 | void fifth_order(int16_t *data, int length, int16_t *hist)
363 | /* for half of interleaved data */
364 | {
365 | int i;
366 | int16_t a, b, c, d, e, f;
367 | a = hist[1];
368 | b = hist[2];
369 | c = hist[3];
370 | d = hist[4];
371 | e = hist[5];
372 | f = data[0];
373 | /* a downsample should improve resolution, so don't fully shift */
374 | data[0] = (a + (b+e)*5 + (c+d)*10 + f) >> 4;
375 | for (i=4; i> 4;
383 | }
384 | /* archive */
385 | hist[0] = a;
386 | hist[1] = b;
387 | hist[2] = c;
388 | hist[3] = d;
389 | hist[4] = e;
390 | hist[5] = f;
391 | }
392 |
393 | void generic_fir(int16_t *data, int length, int *fir, int16_t *hist)
394 | /* Okay, not at all generic. Assumes length 9, fix that eventually. */
395 | {
396 | int d, temp, sum;
397 | for (d=0; d> 15 ;
406 | hist[0] = hist[1];
407 | hist[1] = hist[2];
408 | hist[2] = hist[3];
409 | hist[3] = hist[4];
410 | hist[4] = hist[5];
411 | hist[5] = hist[6];
412 | hist[6] = hist[7];
413 | hist[7] = hist[8];
414 | hist[8] = temp;
415 | }
416 | }
417 |
418 | /* define our own complex math ops
419 | because ARMv5 has no hardware float */
420 |
421 | void multiply(int ar, int aj, int br, int bj, int *cr, int *cj)
422 | {
423 | *cr = ar*br - aj*bj;
424 | *cj = aj*br + ar*bj;
425 | }
426 |
427 | int polar_discriminant(int ar, int aj, int br, int bj)
428 | {
429 | int cr, cj;
430 | double angle;
431 | multiply(ar, aj, br, -bj, &cr, &cj);
432 | angle = atan2((double)cj, (double)cr);
433 | return (int)(angle / 3.14159 * (1<<14));
434 | }
435 |
436 | int fast_atan2(int y, int x)
437 | /* pre scaled for int16 */
438 | {
439 | int yabs, angle;
440 | int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14
441 | if (x==0 && y==0) {
442 | return 0;
443 | }
444 | yabs = y;
445 | if (yabs < 0) {
446 | yabs = -yabs;
447 | }
448 | if (x >= 0) {
449 | angle = pi4 - pi4 * (x-yabs) / (x+yabs);
450 | } else {
451 | angle = pi34 - pi4 * (x+yabs) / (yabs-x);
452 | }
453 | if (y < 0) {
454 | return -angle;
455 | }
456 | return angle;
457 | }
458 |
459 | int polar_disc_fast(int ar, int aj, int br, int bj)
460 | {
461 | int cr, cj;
462 | multiply(ar, aj, br, -bj, &cr, &cj);
463 | return fast_atan2(cj, cr);
464 | }
465 |
466 | int atan_lut_init(void)
467 | {
468 | int i = 0;
469 |
470 | atan_lut = malloc(atan_lut_size * sizeof(int));
471 |
472 | for (i = 0; i < atan_lut_size; i++) {
473 | atan_lut[i] = (int) (atan((double) i / (1< 0)
490 | {return 1 << 13;}
491 | if (cr == 0 && cj < 0)
492 | {return -(1 << 13);}
493 | if (cj == 0 && cr > 0)
494 | {return 0;}
495 | if (cj == 0 && cr < 0)
496 | {return 1 << 14;}
497 | }
498 |
499 | /* real range -32768 - 32768 use 64x range -> absolute maximum: 2097152 */
500 | x = (cj << atan_lut_coef) / cr;
501 | x_abs = abs(x);
502 |
503 | if (x_abs >= atan_lut_size) {
504 | /* we can use linear range, but it is not necessary */
505 | return (cj > 0) ? 1<<13 : -1<<13;
506 | }
507 |
508 | if (x > 0) {
509 | return (cj > 0) ? atan_lut[x] : atan_lut[x] - (1<<14);
510 | } else {
511 | return (cj > 0) ? (1<<14) - atan_lut[-x] : -atan_lut[-x];
512 | }
513 |
514 | return 0;
515 | }
516 |
517 | void fm_demod(struct demod_state *fm)
518 | {
519 | int i, pcm;
520 | int16_t *lp = fm->lowpassed;
521 | pcm = polar_discriminant(lp[0], lp[1],
522 | fm->pre_r, fm->pre_j);
523 | fm->result[0] = (int16_t)pcm;
524 | for (i = 2; i < (fm->lp_len-1); i += 2) {
525 | switch (fm->custom_atan) {
526 | case 0:
527 | pcm = polar_discriminant(lp[i], lp[i+1],
528 | lp[i-2], lp[i-1]);
529 | break;
530 | case 1:
531 | pcm = polar_disc_fast(lp[i], lp[i+1],
532 | lp[i-2], lp[i-1]);
533 | break;
534 | case 2:
535 | pcm = polar_disc_lut(lp[i], lp[i+1],
536 | lp[i-2], lp[i-1]);
537 | break;
538 | }
539 | fm->result[i/2] = (int16_t)pcm;
540 | }
541 | fm->pre_r = lp[fm->lp_len - 2];
542 | fm->pre_j = lp[fm->lp_len - 1];
543 | fm->result_len = fm->lp_len/2;
544 | }
545 |
546 | void am_demod(struct demod_state *fm)
547 | // todo, fix this extreme laziness
548 | {
549 | int i, pcm;
550 | int16_t *lp = fm->lowpassed;
551 | int16_t *r = fm->result;
552 | for (i = 0; i < fm->lp_len; i += 2) {
553 | // hypot uses floats but won't overflow
554 | //r[i/2] = (int16_t)hypot(lp[i], lp[i+1]);
555 | pcm = lp[i] * lp[i];
556 | pcm += lp[i+1] * lp[i+1];
557 | r[i/2] = (int16_t)sqrt(pcm) * fm->output_scale;
558 | }
559 | fm->result_len = fm->lp_len/2;
560 | // lowpass? (3khz) highpass? (dc)
561 | }
562 |
563 | void usb_demod(struct demod_state *fm)
564 | {
565 | int i, pcm;
566 | int16_t *lp = fm->lowpassed;
567 | int16_t *r = fm->result;
568 | for (i = 0; i < fm->lp_len; i += 2) {
569 | pcm = lp[i] + lp[i+1];
570 | r[i/2] = (int16_t)pcm * fm->output_scale;
571 | }
572 | fm->result_len = fm->lp_len/2;
573 | }
574 |
575 | void lsb_demod(struct demod_state *fm)
576 | {
577 | int i, pcm;
578 | int16_t *lp = fm->lowpassed;
579 | int16_t *r = fm->result;
580 | for (i = 0; i < fm->lp_len; i += 2) {
581 | pcm = lp[i] - lp[i+1];
582 | r[i/2] = (int16_t)pcm * fm->output_scale;
583 | }
584 | fm->result_len = fm->lp_len/2;
585 | }
586 |
587 | void raw_demod(struct demod_state *fm)
588 | {
589 | int i;
590 | for (i = 0; i < fm->lp_len; i++) {
591 | fm->result[i] = (int16_t)fm->lowpassed[i];
592 | }
593 | fm->result_len = fm->lp_len;
594 | }
595 |
596 | void deemph_filter(struct demod_state *fm)
597 | {
598 | static int avg; // cheating...
599 | int i, d;
600 | // de-emph IIR
601 | // avg = avg * (1 - alpha) + sample * alpha;
602 | for (i = 0; i < fm->result_len; i++) {
603 | d = fm->result[i] - avg;
604 | if (d > 0) {
605 | avg += (d + fm->deemph_a/2) / fm->deemph_a;
606 | } else {
607 | avg += (d - fm->deemph_a/2) / fm->deemph_a;
608 | }
609 | fm->result[i] = (int16_t)avg;
610 | }
611 | }
612 |
613 | void dc_block_filter(struct demod_state *fm)
614 | {
615 | int i, avg;
616 | int64_t sum = 0;
617 | for (i=0; i < fm->result_len; i++) {
618 | sum += fm->result[i];
619 | }
620 | avg = sum / fm->result_len;
621 | avg = (avg + fm->dc_avg * 9) / 10;
622 | for (i=0; i < fm->result_len; i++) {
623 | fm->result[i] -= avg;
624 | }
625 | fm->dc_avg = avg;
626 | }
627 |
628 | int mad(int16_t *samples, int len, int step)
629 | /* mean average deviation */
630 | {
631 | int i=0, sum=0, ave=0;
632 | if (len == 0)
633 | {return 0;}
634 | for (i=0; i len2) {
678 | tick -= len2;
679 | i++;
680 | }
681 | if (i >= len1) {
682 | i = len1 - 1;
683 | tick = len2;
684 | }
685 | }
686 | }
687 |
688 | void arbitrary_downsample(int16_t *buf1, int16_t *buf2, int len1, int len2)
689 | /* fractional boxcar lowpass, len1 > len2 */
690 | {
691 | int i = 1;
692 | int j = 0;
693 | int tick = 0;
694 | double remainder = 0;
695 | double frac; // use integers...
696 | buf2[0] = 0;
697 | while (j < len2) {
698 | frac = 1.0;
699 | if ((tick + len2) > len1) {
700 | frac = (double)(len1 - tick) / (double)len2;}
701 | buf2[j] += (int16_t)((double)buf1[i] * frac + remainder);
702 | remainder = (double)buf1[i] * (1.0-frac);
703 | tick += len2;
704 | i++;
705 | if (tick > len1) {
706 | j++;
707 | buf2[j] = 0;
708 | tick -= len1;
709 | }
710 | if (i >= len1) {
711 | i = len1 - 1;
712 | tick = len1;
713 | }
714 | }
715 | for (j=0; jdownsample_passes;
735 | if (ds_p) {
736 | for (i=0; i < ds_p; i++) {
737 | fifth_order(d->lowpassed, (d->lp_len >> i), d->lp_i_hist[i]);
738 | fifth_order(d->lowpassed+1, (d->lp_len >> i) - 1, d->lp_q_hist[i]);
739 | }
740 | d->lp_len = d->lp_len >> ds_p;
741 | /* droop compensation */
742 | if (d->comp_fir_size == 9 && ds_p <= CIC_TABLE_MAX) {
743 | generic_fir(d->lowpassed, d->lp_len,
744 | cic_9_tables[ds_p], d->droop_i_hist);
745 | generic_fir(d->lowpassed+1, d->lp_len-1,
746 | cic_9_tables[ds_p], d->droop_q_hist);
747 | }
748 | } else {
749 | low_pass(d);
750 | }
751 | /* power squelch */
752 | if (d->squelch_level) {
753 | sr = rms(d->lowpassed, d->lp_len, 1);
754 | if (sr < d->squelch_level) {
755 | d->squelch_hits++;
756 | for (i=0; ilp_len; i++) {
757 | d->lowpassed[i] = 0;
758 | }
759 | } else {
760 | d->squelch_hits = 0;}
761 | }
762 | d->mode_demod(d); /* lowpassed -> result */
763 | if (d->mode_demod == &raw_demod) {
764 | return;
765 | }
766 | /* todo, fm noise squelch */
767 | // use nicer filter here too?
768 | if (d->post_downsample > 1) {
769 | d->result_len = low_pass_simple(d->result, d->result_len, d->post_downsample);}
770 | if (d->deemph) {
771 | deemph_filter(d);}
772 | if (d->dc_block) {
773 | dc_block_filter(d);}
774 | if (d->rate_out2 > 0) {
775 | low_pass_real(d);
776 | //arbitrary_resample(d->result, d->result, d->result_len, d->result_len * d->rate_out2 / d->rate_out);
777 | }
778 | }
779 |
780 | static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx)
781 | {
782 | int i;
783 | struct dongle_state *s = ctx;
784 | struct demod_state *d = s->demod_target;
785 |
786 | if (do_exit) {
787 | return;}
788 | if (!ctx) {
789 | return;}
790 | if (s->mute) {
791 | for (i=0; imute; i++) {
792 | buf[i] = 127;}
793 | s->mute = 0;
794 | }
795 | if (!s->offset_tuning) {
796 | rotate_90(buf, len);}
797 | for (i=0; i<(int)len; i++) {
798 | s->buf16[i] = (int16_t)buf[i] - 127;}
799 | pthread_rwlock_wrlock(&d->rw);
800 | memcpy(d->lowpassed, s->buf16, 2*len);
801 | d->lp_len = len;
802 | pthread_rwlock_unlock(&d->rw);
803 | safe_cond_signal(&d->ready, &d->ready_m);
804 | }
805 |
806 | static void *dongle_thread_fn(void *arg)
807 | {
808 | struct dongle_state *s = arg;
809 | rtlsdr_read_async(s->dev, rtlsdr_callback, s, 0, s->buf_len);
810 | return 0;
811 | }
812 |
813 | static void *demod_thread_fn(void *arg)
814 | {
815 | struct demod_state *d = arg;
816 | struct output_state *o = d->output_target;
817 | while (!do_exit) {
818 | safe_cond_wait(&d->ready, &d->ready_m);
819 | pthread_rwlock_wrlock(&d->rw);
820 | full_demod(d);
821 | pthread_rwlock_unlock(&d->rw);
822 | if (d->exit_flag) {
823 | do_exit = 1;
824 | }
825 | if (d->squelch_level && d->squelch_hits > d->conseq_squelch) {
826 | d->squelch_hits = d->conseq_squelch + 1; /* hair trigger */
827 | safe_cond_signal(&controller.hop, &controller.hop_m);
828 | continue;
829 | }
830 | pthread_rwlock_wrlock(&o->rw);
831 | memcpy(o->result, d->result, 2*d->result_len);
832 | o->result_len = d->result_len;
833 | pthread_rwlock_unlock(&o->rw);
834 | safe_cond_signal(&o->ready, &o->ready_m);
835 | }
836 | return 0;
837 | }
838 |
839 | static void *output_thread_fn(void *arg)
840 | {
841 | struct output_state *s = arg;
842 | while (!do_exit) {
843 | // use timedwait and pad out under runs
844 | safe_cond_wait(&s->ready, &s->ready_m);
845 | pthread_rwlock_rdlock(&s->rw);
846 | fwrite(s->result, 2, s->result_len, s->file);
847 | pthread_rwlock_unlock(&s->rw);
848 | }
849 | return 0;
850 | }
851 |
852 | static void optimal_settings(int freq, int rate)
853 | {
854 | // giant ball of hacks
855 | // seems unable to do a single pass, 2:1
856 | int capture_freq, capture_rate;
857 | struct dongle_state *d = &dongle;
858 | struct demod_state *dm = &demod;
859 | struct controller_state *cs = &controller;
860 | dm->downsample = (1000000 / dm->rate_in) + 1;
861 | if (dm->downsample_passes) {
862 | dm->downsample_passes = (int)log2(dm->downsample) + 1;
863 | dm->downsample = 1 << dm->downsample_passes;
864 | }
865 | capture_freq = freq;
866 | capture_rate = dm->downsample * dm->rate_in;
867 | if (!d->offset_tuning) {
868 | capture_freq = freq + capture_rate/4;}
869 | capture_freq += cs->edge * dm->rate_in / 2;
870 | dm->output_scale = (1<<15) / (128 * dm->downsample);
871 | if (dm->output_scale < 1) {
872 | dm->output_scale = 1;}
873 | if (dm->mode_demod == &fm_demod) {
874 | dm->output_scale = 1;}
875 | d->freq = (uint32_t)capture_freq;
876 | d->rate = (uint32_t)capture_rate;
877 | }
878 |
879 | static void *controller_thread_fn(void *arg)
880 | {
881 | // thoughts for multiple dongles
882 | // might be no good using a controller thread if retune/rate blocks
883 | int i;
884 | struct controller_state *s = arg;
885 |
886 | if (s->wb_mode) {
887 | for (i=0; i < s->freq_len; i++) {
888 | s->freqs[i] += 16000;}
889 | }
890 |
891 | /* set up primary channel */
892 | optimal_settings(s->freqs[0], demod.rate_in);
893 | if (dongle.direct_sampling) {
894 | verbose_direct_sampling(dongle.dev, 1);}
895 | if (dongle.offset_tuning) {
896 | verbose_offset_tuning(dongle.dev);}
897 |
898 | /* Set the frequency */
899 | verbose_set_frequency(dongle.dev, dongle.freq);
900 | fprintf(stderr, "Oversampling input by: %ix.\n", demod.downsample);
901 | fprintf(stderr, "Oversampling output by: %ix.\n", demod.post_downsample);
902 | fprintf(stderr, "Buffer size: %0.2fms\n",
903 | 1000 * 0.5 * (float)ACTUAL_BUF_LENGTH / (float)dongle.rate);
904 |
905 | /* Set the sample rate */
906 | verbose_set_sample_rate(dongle.dev, dongle.rate);
907 | fprintf(stderr, "Output at %u Hz.\n", demod.rate_in/demod.post_downsample);
908 |
909 | while (!do_exit) {
910 | safe_cond_wait(&s->hop, &s->hop_m);
911 | if (s->freq_len <= 1) {
912 | continue;}
913 | /* hacky hopping */
914 | s->freq_now = (s->freq_now + 1) % s->freq_len;
915 | optimal_settings(s->freqs[s->freq_now], demod.rate_in);
916 | rtlsdr_set_center_freq(dongle.dev, dongle.freq);
917 | dongle.mute = BUFFER_DUMP;
918 | }
919 | return 0;
920 | }
921 |
922 | void frequency_range(struct controller_state *s, char *arg)
923 | {
924 | char *start, *stop, *step;
925 | int i;
926 | start = arg;
927 | stop = strchr(start, ':') + 1;
928 | stop[-1] = '\0';
929 | step = strchr(stop, ':') + 1;
930 | step[-1] = '\0';
931 | for(i=(int)atofs(start); i<=(int)atofs(stop); i+=(int)atofs(step))
932 | {
933 | s->freqs[s->freq_len] = (uint32_t)i;
934 | s->freq_len++;
935 | if (s->freq_len >= FREQUENCIES_LIMIT) {
936 | break;}
937 | }
938 | stop[-1] = ':';
939 | step[-1] = ':';
940 | }
941 |
942 | void dongle_init(struct dongle_state *s)
943 | {
944 | s->rate = DEFAULT_SAMPLE_RATE;
945 | s->gain = AUTO_GAIN; // tenths of a dB
946 | s->mute = 0;
947 | s->direct_sampling = 0;
948 | s->offset_tuning = 0;
949 | s->demod_target = &demod;
950 | }
951 |
952 | void demod_init(struct demod_state *s)
953 | {
954 | s->rate_in = DEFAULT_SAMPLE_RATE;
955 | s->rate_out = DEFAULT_SAMPLE_RATE;
956 | s->squelch_level = 0;
957 | s->conseq_squelch = 10;
958 | s->terminate_on_squelch = 0;
959 | s->squelch_hits = 11;
960 | s->downsample_passes = 0;
961 | s->comp_fir_size = 0;
962 | s->prev_index = 0;
963 | s->post_downsample = 1; // once this works, default = 4
964 | s->custom_atan = 0;
965 | s->deemph = 0;
966 | s->rate_out2 = -1; // flag for disabled
967 | s->mode_demod = &fm_demod;
968 | s->pre_j = s->pre_r = s->now_r = s->now_j = 0;
969 | s->prev_lpr_index = 0;
970 | s->deemph_a = 0;
971 | s->now_lpr = 0;
972 | s->dc_block = 0;
973 | s->dc_avg = 0;
974 | pthread_rwlock_init(&s->rw, NULL);
975 | pthread_cond_init(&s->ready, NULL);
976 | pthread_mutex_init(&s->ready_m, NULL);
977 | s->output_target = &output;
978 | }
979 |
980 | void demod_cleanup(struct demod_state *s)
981 | {
982 | pthread_rwlock_destroy(&s->rw);
983 | pthread_cond_destroy(&s->ready);
984 | pthread_mutex_destroy(&s->ready_m);
985 | }
986 |
987 | void output_init(struct output_state *s)
988 | {
989 | s->rate = DEFAULT_SAMPLE_RATE;
990 | pthread_rwlock_init(&s->rw, NULL);
991 | pthread_cond_init(&s->ready, NULL);
992 | pthread_mutex_init(&s->ready_m, NULL);
993 | }
994 |
995 | void output_cleanup(struct output_state *s)
996 | {
997 | pthread_rwlock_destroy(&s->rw);
998 | pthread_cond_destroy(&s->ready);
999 | pthread_mutex_destroy(&s->ready_m);
1000 | }
1001 |
1002 | void controller_init(struct controller_state *s)
1003 | {
1004 | s->freqs[0] = 100000000;
1005 | s->freq_len = 0;
1006 | s->edge = 0;
1007 | s->wb_mode = 0;
1008 | pthread_cond_init(&s->hop, NULL);
1009 | pthread_mutex_init(&s->hop_m, NULL);
1010 | }
1011 |
1012 | void controller_cleanup(struct controller_state *s)
1013 | {
1014 | pthread_cond_destroy(&s->hop);
1015 | pthread_mutex_destroy(&s->hop_m);
1016 | }
1017 |
1018 | void sanity_checks(void)
1019 | {
1020 | if (controller.freq_len == 0) {
1021 | fprintf(stderr, "Please specify a frequency.\n");
1022 | exit(1);
1023 | }
1024 |
1025 | if (controller.freq_len >= FREQUENCIES_LIMIT) {
1026 | fprintf(stderr, "Too many channels, maximum %i.\n", FREQUENCIES_LIMIT);
1027 | exit(1);
1028 | }
1029 |
1030 | if (controller.freq_len > 1 && demod.squelch_level == 0) {
1031 | fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n");
1032 | exit(1);
1033 | }
1034 |
1035 | }
1036 |
1037 | int main(int argc, char **argv)
1038 | {
1039 | #ifndef _WIN32
1040 | struct sigaction sigact;
1041 | #endif
1042 | int r, opt;
1043 | int dev_given = 0;
1044 | int custom_ppm = 0;
1045 | dongle_init(&dongle);
1046 | demod_init(&demod);
1047 | output_init(&output);
1048 | controller_init(&controller);
1049 |
1050 | while ((opt = getopt(argc, argv, "d:f:g:s:b:l:o:t:r:p:E:F:A:M:h")) != -1) {
1051 | switch (opt) {
1052 | case 'd':
1053 | dongle.dev_index = verbose_device_search(optarg);
1054 | dev_given = 1;
1055 | break;
1056 | case 'f':
1057 | if (controller.freq_len >= FREQUENCIES_LIMIT) {
1058 | break;}
1059 | if (strchr(optarg, ':'))
1060 | {frequency_range(&controller, optarg);}
1061 | else
1062 | {
1063 | controller.freqs[controller.freq_len] = (uint32_t)atofs(optarg);
1064 | controller.freq_len++;
1065 | }
1066 | break;
1067 | case 'g':
1068 | dongle.gain = (int)(atof(optarg) * 10);
1069 | break;
1070 | case 'l':
1071 | demod.squelch_level = (int)atof(optarg);
1072 | break;
1073 | case 's':
1074 | demod.rate_in = (uint32_t)atofs(optarg);
1075 | demod.rate_out = (uint32_t)atofs(optarg);
1076 | break;
1077 | case 'r':
1078 | output.rate = (int)atofs(optarg);
1079 | demod.rate_out2 = (int)atofs(optarg);
1080 | break;
1081 | case 'o':
1082 | fprintf(stderr, "Warning: -o is very buggy\n");
1083 | demod.post_downsample = (int)atof(optarg);
1084 | if (demod.post_downsample < 1 || demod.post_downsample > MAXIMUM_OVERSAMPLE) {
1085 | fprintf(stderr, "Oversample must be between 1 and %i\n", MAXIMUM_OVERSAMPLE);}
1086 | break;
1087 | case 't':
1088 | demod.conseq_squelch = (int)atof(optarg);
1089 | if (demod.conseq_squelch < 0) {
1090 | demod.conseq_squelch = -demod.conseq_squelch;
1091 | demod.terminate_on_squelch = 1;
1092 | }
1093 | break;
1094 | case 'p':
1095 | dongle.ppm_error = atoi(optarg);
1096 | custom_ppm = 1;
1097 | break;
1098 | case 'E':
1099 | if (strcmp("edge", optarg) == 0) {
1100 | controller.edge = 1;}
1101 | if (strcmp("dc", optarg) == 0) {
1102 | demod.dc_block = 1;}
1103 | if (strcmp("deemp", optarg) == 0) {
1104 | demod.deemph = 1;}
1105 | if (strcmp("direct", optarg) == 0) {
1106 | dongle.direct_sampling = 1;}
1107 | if (strcmp("offset", optarg) == 0) {
1108 | dongle.offset_tuning = 1;}
1109 | break;
1110 | case 'F':
1111 | demod.downsample_passes = 1; /* truthy placeholder */
1112 | demod.comp_fir_size = atoi(optarg);
1113 | break;
1114 | case 'A':
1115 | if (strcmp("std", optarg) == 0) {
1116 | demod.custom_atan = 0;}
1117 | if (strcmp("fast", optarg) == 0) {
1118 | demod.custom_atan = 1;}
1119 | if (strcmp("lut", optarg) == 0) {
1120 | atan_lut_init();
1121 | demod.custom_atan = 2;}
1122 | break;
1123 | case 'M':
1124 | if (strcmp("fm", optarg) == 0) {
1125 | demod.mode_demod = &fm_demod;}
1126 | if (strcmp("raw", optarg) == 0) {
1127 | demod.mode_demod = &raw_demod;}
1128 | if (strcmp("am", optarg) == 0) {
1129 | demod.mode_demod = &am_demod;}
1130 | if (strcmp("usb", optarg) == 0) {
1131 | demod.mode_demod = &usb_demod;}
1132 | if (strcmp("lsb", optarg) == 0) {
1133 | demod.mode_demod = &lsb_demod;}
1134 | if (strcmp("wbfm", optarg) == 0) {
1135 | controller.wb_mode = 1;
1136 | demod.mode_demod = &fm_demod;
1137 | demod.rate_in = 170000;
1138 | demod.rate_out = 170000;
1139 | demod.rate_out2 = 32000;
1140 | demod.custom_atan = 1;
1141 | //demod.post_downsample = 4;
1142 | demod.deemph = 1;
1143 | demod.squelch_level = 0;}
1144 | break;
1145 | case 'h':
1146 | default:
1147 | usage();
1148 | break;
1149 | }
1150 | }
1151 |
1152 | /* quadruple sample_rate to limit to Δθ to ±π/2 */
1153 | demod.rate_in *= demod.post_downsample;
1154 |
1155 | if (!output.rate) {
1156 | output.rate = demod.rate_out;}
1157 |
1158 | sanity_checks();
1159 |
1160 | if (controller.freq_len > 1) {
1161 | demod.terminate_on_squelch = 0;}
1162 |
1163 | if (argc <= optind) {
1164 | output.filename = "-";
1165 | } else {
1166 | output.filename = argv[optind];
1167 | }
1168 |
1169 | ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH;
1170 |
1171 | if (!dev_given) {
1172 | dongle.dev_index = verbose_device_search("0");
1173 | }
1174 |
1175 | if (dongle.dev_index < 0) {
1176 | exit(1);
1177 | }
1178 |
1179 | r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index);
1180 | if (r < 0) {
1181 | fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dongle.dev_index);
1182 | exit(1);
1183 | }
1184 | #ifndef _WIN32
1185 | sigact.sa_handler = sighandler;
1186 | sigemptyset(&sigact.sa_mask);
1187 | sigact.sa_flags = 0;
1188 | sigaction(SIGINT, &sigact, NULL);
1189 | sigaction(SIGTERM, &sigact, NULL);
1190 | sigaction(SIGQUIT, &sigact, NULL);
1191 | sigaction(SIGPIPE, &sigact, NULL);
1192 | #else
1193 | SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
1194 | #endif
1195 |
1196 | if (demod.deemph) {
1197 | demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6)))));
1198 | }
1199 |
1200 | /* Set the tuner gain */
1201 | if (dongle.gain == AUTO_GAIN) {
1202 | verbose_auto_gain(dongle.dev);
1203 | } else {
1204 | dongle.gain = nearest_gain(dongle.dev, dongle.gain);
1205 | verbose_gain_set(dongle.dev, dongle.gain);
1206 | }
1207 |
1208 | verbose_ppm_set(dongle.dev, dongle.ppm_error);
1209 |
1210 | if (strcmp(output.filename, "-") == 0) { /* Write samples to stdout */
1211 | output.file = stdout;
1212 | #ifdef _WIN32
1213 | _setmode(_fileno(output.file), _O_BINARY);
1214 | #endif
1215 | } else {
1216 | output.file = fopen(output.filename, "wb");
1217 | if (!output.file) {
1218 | fprintf(stderr, "Failed to open %s\n", output.filename);
1219 | exit(1);
1220 | }
1221 | }
1222 |
1223 | //r = rtlsdr_set_testmode(dongle.dev, 1);
1224 |
1225 | /* Reset endpoint before we start reading from it (mandatory) */
1226 | verbose_reset_buffer(dongle.dev);
1227 |
1228 | pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
1229 | usleep(100000);
1230 | pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
1231 | pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
1232 | pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
1233 |
1234 | while (!do_exit) {
1235 | usleep(100000);
1236 | }
1237 |
1238 | if (do_exit) {
1239 | fprintf(stderr, "\nUser cancel, exiting...\n");}
1240 | else {
1241 | fprintf(stderr, "\nLibrary error %d, exiting...\n", r);}
1242 |
1243 | rtlsdr_cancel_async(dongle.dev);
1244 | pthread_join(dongle.thread, NULL);
1245 | safe_cond_signal(&demod.ready, &demod.ready_m);
1246 | pthread_join(demod.thread, NULL);
1247 | safe_cond_signal(&output.ready, &output.ready_m);
1248 | pthread_join(output.thread, NULL);
1249 | safe_cond_signal(&controller.hop, &controller.hop_m);
1250 | pthread_join(controller.thread, NULL);
1251 |
1252 | //dongle_cleanup(&dongle);
1253 | demod_cleanup(&demod);
1254 | output_cleanup(&output);
1255 | controller_cleanup(&controller);
1256 |
1257 | if (output.file != stdout) {
1258 | fclose(output.file);}
1259 |
1260 | rtlsdr_close(dongle.dev);
1261 | return r >= 0 ? r : -r;
1262 | }
1263 |
1264 | // vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab
1265 |
--------------------------------------------------------------------------------
/rtl_fm_python.c:
--------------------------------------------------------------------------------
1 | #include "rtl_fm.c"
2 | #include "getopt/getopt.h"
3 | #include "rtl-sdr.h"
4 | #include "convenience/convenience.h"
5 |
6 | #ifndef _WIN32
7 | struct sigaction sigact;
8 | #endif
9 | int r, opt;
10 | int dev_given = 0;
11 | int custom_ppm = 0;
12 |
13 |
14 |
15 | void lib_loop(){
16 | while (!do_exit) {
17 | usleep(100000);
18 | }
19 | }
20 |
21 |
22 | void lib_init_first()
23 | {
24 |
25 | dongle_init(&dongle);
26 | demod_init(&demod);
27 | output_init(&output);
28 | controller_init(&controller);
29 |
30 | }
31 |
32 | void no_exit_usage(void)
33 | {
34 | fprintf(stderr,
35 | "rtl_fm, a simple narrow band FM demodulator for RTL2832 based DVB-T receivers\n\n"
36 | "Use:\trtl_fm -f freq [-options] [filename]\n"
37 | "\t-f frequency_to_tune_to [Hz]\n"
38 | "\t use multiple -f for scanning (requires squelch)\n"
39 | "\t ranges supported, -f 118M:137M:25k\n"
40 | "\t[-M modulation (default: fm)]\n"
41 | "\t fm, wbfm, raw, am, usb, lsb\n"
42 | "\t wbfm == -M fm -s 170k -o 4 -A fast -r 32k -l 0 -E deemp\n"
43 | "\t raw mode outputs 2x16 bit IQ pairs\n"
44 | "\t[-s sample_rate (default: 24k)]\n"
45 | "\t[-d device_index (default: 0)]\n"
46 | "\t[-g tuner_gain (default: automatic)]\n"
47 | "\t[-l squelch_level (default: 0/off)]\n"
48 | //"\t for fm squelch is inverted\n"
49 | //"\t[-o oversampling (default: 1, 4 recommended)]\n"
50 | "\t[-p ppm_error (default: 0)]\n"
51 | "\t[-E enable_option (default: none)]\n"
52 | "\t use multiple -E to enable multiple options\n"
53 | "\t edge: enable lower edge tuning\n"
54 | "\t dc: enable dc blocking filter\n"
55 | "\t deemp: enable de-emphasis filter\n"
56 | "\t direct: enable direct sampling\n"
57 | "\t offset: enable offset tuning\n"
58 | "\tfilename ('-' means stdout)\n"
59 | "\t omitting the filename also uses stdout\n\n"
60 | "Experimental options:\n"
61 | "\t[-r resample_rate (default: none / same as -s)]\n"
62 | "\t[-t squelch_delay (default: 10)]\n"
63 | "\t +values will mute/scan, -values will exit\n"
64 | "\t[-F fir_size (default: off)]\n"
65 | "\t enables low-leakage downsample filter\n"
66 | "\t size can be 0 or 9. 0 has bad roll off\n"
67 | "\t[-A std/fast/lut choose atan math (default: std)]\n"
68 | //"\t[-C clip_path (default: off)\n"
69 | //"\t (create time stamped raw clips, requires squelch)\n"
70 | //"\t (path must have '\%s' and will expand to date_time_freq)\n"
71 | //"\t[-H hop_fifo (default: off)\n"
72 | //"\t (fifo will contain the active frequency)\n"
73 | "\n"
74 | "Produces signed 16 bit ints, use Sox or aplay to hear them.\n"
75 | "\trtl_fm ... | play -t raw -r 24k -es -b 16 -c 1 -V1 -\n"
76 | "\t | aplay -r 24k -f S16_LE -t raw -c 1\n"
77 | "\t -M wbfm | play -r 32k ... \n"
78 | "\t -s 22050 | multimon -t raw /dev/stdin\n\n");
79 | }
80 |
81 | void no_exit_sanity_checks(void)
82 | {
83 | if (controller.freq_len == 0) {
84 | fprintf(stderr, "Please specify a frequency.\n");
85 | }
86 |
87 | if (controller.freq_len >= FREQUENCIES_LIMIT) {
88 | fprintf(stderr, "Too many channels, maximum %i.\n", FREQUENCIES_LIMIT);
89 | }
90 |
91 | if (controller.freq_len > 1 && demod.squelch_level == 0) {
92 | fprintf(stderr, "Please specify a squelch level. Required for scanning multiple frequencies.\n");
93 | }
94 |
95 | }
96 |
97 |
98 |
99 | void lib_process_args(int argc, char **argv)
100 | {
101 |
102 |
103 | while ((opt = getopt(argc, argv, "d:f:g:s:b:l:o:t:r:p:E:F:A:M:h")) != -1) {
104 | switch (opt) {
105 | case 'd':
106 | dongle.dev_index = verbose_device_search(optarg);
107 | dev_given = 1;
108 | break;
109 | case 'f':
110 | if (controller.freq_len >= FREQUENCIES_LIMIT) {
111 | break;}
112 | if (strchr(optarg, ':'))
113 | {frequency_range(&controller, optarg);}
114 | else
115 | {
116 | controller.freqs[controller.freq_len] = (uint32_t)atofs(optarg);
117 | controller.freq_len++;
118 | }
119 | break;
120 | case 'g':
121 | dongle.gain = (int)(atof(optarg) * 10);
122 | break;
123 | case 'l':
124 | demod.squelch_level = (int)atof(optarg);
125 | break;
126 | case 's':
127 | demod.rate_in = (uint32_t)atofs(optarg);
128 | demod.rate_out = (uint32_t)atofs(optarg);
129 | break;
130 | case 'r':
131 | output.rate = (int)atofs(optarg);
132 | demod.rate_out2 = (int)atofs(optarg);
133 | break;
134 | case 'o':
135 | fprintf(stderr, "Warning: -o is very buggy\n");
136 | demod.post_downsample = (int)atof(optarg);
137 | if (demod.post_downsample < 1 || demod.post_downsample > MAXIMUM_OVERSAMPLE) {
138 | fprintf(stderr, "Oversample must be between 1 and %i\n", MAXIMUM_OVERSAMPLE);}
139 | break;
140 | case 't':
141 | demod.conseq_squelch = (int)atof(optarg);
142 | if (demod.conseq_squelch < 0) {
143 | demod.conseq_squelch = -demod.conseq_squelch;
144 | demod.terminate_on_squelch = 1;
145 | }
146 | break;
147 | case 'p':
148 | dongle.ppm_error = atoi(optarg);
149 | custom_ppm = 1;
150 | break;
151 | case 'E':
152 | if (strcmp("edge", optarg) == 0) {
153 | controller.edge = 1;}
154 | if (strcmp("dc", optarg) == 0) {
155 | demod.dc_block = 1;}
156 | if (strcmp("deemp", optarg) == 0) {
157 | demod.deemph = 1;}
158 | if (strcmp("direct", optarg) == 0) {
159 | dongle.direct_sampling = 1;}
160 | if (strcmp("offset", optarg) == 0) {
161 | dongle.offset_tuning = 1;}
162 | break;
163 | case 'F':
164 | demod.downsample_passes = 1; /* truthy placeholder */
165 | demod.comp_fir_size = atoi(optarg);
166 | break;
167 | case 'A':
168 | if (strcmp("std", optarg) == 0) {
169 | demod.custom_atan = 0;}
170 | if (strcmp("fast", optarg) == 0) {
171 | demod.custom_atan = 1;}
172 | if (strcmp("lut", optarg) == 0) {
173 | atan_lut_init();
174 | demod.custom_atan = 2;}
175 | break;
176 | case 'M':
177 | if (strcmp("fm", optarg) == 0) {
178 | demod.mode_demod = &fm_demod;}
179 | if (strcmp("raw", optarg) == 0) {
180 | demod.mode_demod = &raw_demod;}
181 | if (strcmp("am", optarg) == 0) {
182 | demod.mode_demod = &am_demod;}
183 | if (strcmp("usb", optarg) == 0) {
184 | demod.mode_demod = &usb_demod;}
185 | if (strcmp("lsb", optarg) == 0) {
186 | demod.mode_demod = &lsb_demod;}
187 | if (strcmp("wbfm", optarg) == 0) {
188 | controller.wb_mode = 1;
189 | demod.mode_demod = &fm_demod;
190 | demod.rate_in = 170000;
191 | demod.rate_out = 170000;
192 | demod.rate_out2 = 32000;
193 | demod.custom_atan = 1;
194 | //demod.post_downsample = 4;
195 | demod.deemph = 1;
196 | demod.squelch_level = 0;}
197 | break;
198 | case 'h':
199 | default:
200 | no_exit_usage();
201 | break;
202 | }
203 | }
204 |
205 |
206 | /* quadruple sample_rate to limit to Δθ to ±π/2 */
207 | demod.rate_in *= demod.post_downsample;
208 |
209 | if (!output.rate) {
210 | output.rate = demod.rate_out;}
211 |
212 | no_exit_sanity_checks();
213 |
214 | if (controller.freq_len > 1) {
215 | demod.terminate_on_squelch = 0;}
216 |
217 | if (argc <= optind) {
218 | output.filename = "-";
219 | } else {
220 | output.filename = argv[optind];
221 | }
222 |
223 | ACTUAL_BUF_LENGTH = lcm_post[demod.post_downsample] * DEFAULT_BUF_LENGTH;
224 |
225 | if (!dev_given) {
226 | dongle.dev_index = verbose_device_search("0");
227 | }
228 |
229 | if (dongle.dev_index < 0) {
230 | fprintf(stderr, "Device index less than zero.\n");
231 | }
232 |
233 |
234 | }
235 |
236 | void lib_output_open()
237 | {
238 | if (strcmp(output.filename, "-") == 0) { /* Write samples to stdout */
239 | output.file = stdout;
240 | #ifdef _WIN32
241 | _setmode(_fileno(output.file), _O_BINARY);
242 | #endif
243 | } else {
244 | output.file = fopen(output.filename, "wb");
245 | if (!output.file) {
246 | fprintf(stderr, "Failed to open %s\n", output.filename);
247 | }
248 | }
249 | }
250 |
251 | void lib_input_open()
252 | {
253 | r = rtlsdr_open(&dongle.dev, (uint32_t)dongle.dev_index);
254 | if (r < 0) {
255 | fprintf(stderr, "Failed to open rtlsdr device #%d.\n", dongle.dev_index);
256 | }
257 |
258 | }
259 |
260 | void lib_register_signal_handler(){
261 | #ifndef _WIN32
262 | sigact.sa_handler = sighandler;
263 | sigemptyset(&sigact.sa_mask);
264 | sigact.sa_flags = 0;
265 | sigaction(SIGINT, &sigact, NULL);
266 | sigaction(SIGTERM, &sigact, NULL);
267 | sigaction(SIGQUIT, &sigact, NULL);
268 | sigaction(SIGPIPE, &sigact, NULL);
269 | #else
270 | SetConsoleCtrlHandler( (PHANDLER_ROUTINE) sighandler, TRUE );
271 | #endif
272 |
273 | }
274 |
275 | void lib_go()
276 | {
277 | if (demod.deemph) {
278 | demod.deemph_a = (int)round(1.0/((1.0-exp(-1.0/(demod.rate_out * 75e-6)))));
279 | }
280 | if (dongle.gain == AUTO_GAIN) {
281 | verbose_auto_gain(dongle.dev);
282 | } else {
283 | dongle.gain = nearest_gain(dongle.dev, dongle.gain);
284 | verbose_gain_set(dongle.dev, dongle.gain);
285 | }
286 | verbose_ppm_set(dongle.dev, dongle.ppm_error);
287 | verbose_reset_buffer(dongle.dev);
288 | pthread_create(&controller.thread, NULL, controller_thread_fn, (void *)(&controller));
289 | usleep(100000);
290 | pthread_create(&output.thread, NULL, output_thread_fn, (void *)(&output));
291 | pthread_create(&demod.thread, NULL, demod_thread_fn, (void *)(&demod));
292 | pthread_create(&dongle.thread, NULL, dongle_thread_fn, (void *)(&dongle));
293 |
294 | }
295 |
296 | void lib_print_frequency(){
297 | fprintf(stderr,"Frequency: %i.\n",controller.freqs[controller.freq_now]);
298 | }
299 |
300 | uint32_t lib_get_frequency(){
301 | return controller.freqs[controller.freq_now];
302 | }
303 |
304 | int lib_get_s_level(){
305 | int s = rms(demod.lowpassed, demod.lp_len, 1);
306 | if (s > 0) {
307 | return s;
308 | } else {
309 | return 0;
310 | }
311 | }
312 |
313 | void lib_set_frequency(uint32_t new_frequency){
314 | optimal_settings(new_frequency, demod.rate_in);
315 | controller.freqs[controller.freq_now] = new_frequency;
316 | rtlsdr_set_center_freq(dongle.dev, dongle.freq);
317 | }
318 |
319 | void lib_set_squelch_level(int l) {
320 | demod.squelch_level = l;
321 | }
322 |
323 | void util_set_wbfm_options(){
324 | controller.wb_mode = 1;
325 | demod.custom_atan = 1;
326 | demod.deemph = 1;
327 | demod.squelch_level = 0;
328 | }
329 |
330 | void util_set_other_demod_options(){
331 | controller.wb_mode = 0;
332 | demod.custom_atan = 0;
333 | demod.deemph = 0;
334 | demod.squelch_level = 0;
335 | }
336 |
337 | void lib_set_demod_wbfm(){
338 | util_set_wbfm_options();
339 | demod.mode_demod = &fm_demod;
340 | }
341 |
342 | void lib_set_demod_fm(){
343 | util_set_other_demod_options();
344 | demod.mode_demod = &fm_demod;
345 | }
346 |
347 | void lib_set_demod_am(){
348 | util_set_other_demod_options();
349 | demod.mode_demod = &am_demod;
350 | }
351 |
352 | void lib_set_demod_lsb(){
353 | util_set_other_demod_options();
354 | demod.mode_demod = &lsb_demod;
355 | }
356 |
357 | void lib_set_demod_usb(){
358 | util_set_other_demod_options();
359 | demod.mode_demod = &usb_demod;
360 | }
361 |
362 | void lib_set_demod_raw(){
363 | util_set_other_demod_options();
364 | demod.mode_demod = &raw_demod;
365 | }
366 |
367 |
368 | char lib_get_demod_mode(){
369 | if (demod.mode_demod==&fm_demod){
370 | if (controller.wb_mode==1){
371 | return 'w';
372 | } else {
373 | return 'f';
374 | }
375 | }
376 | if (demod.mode_demod==&am_demod){
377 | return 'a';
378 | }
379 | if (demod.mode_demod==&lsb_demod){
380 | return 'l';
381 | }
382 | if (demod.mode_demod==&usb_demod){
383 | return 'u';
384 | }
385 | if (demod.mode_demod==&raw_demod){
386 | return 'r';
387 | }
388 | }
389 |
390 | void lib_set_auto_gain(){
391 | r = rtlsdr_set_tuner_gain_mode(dongle.dev, 0);
392 | dongle.gain = -100;
393 | }
394 |
395 | void lib_set_gain(int g){
396 | if (g >= 0){
397 | int ng;
398 | rtlsdr_set_tuner_gain_mode(dongle.dev, 1);
399 | ng = nearest_gain(dongle.dev,g*10);
400 | rtlsdr_set_tuner_gain(dongle.dev, ng);
401 | dongle.gain=ng;
402 | }
403 | }
404 |
405 | void lib_set_real_gain(int g){
406 | if (g > -100){
407 | rtlsdr_set_tuner_gain_mode(dongle.dev, 1);
408 | rtlsdr_set_tuner_gain(dongle.dev, g);
409 | dongle.gain=g;
410 | } else {
411 | lib_set_auto_gain();
412 | }
413 | }
414 |
415 | int lib_get_tuner_gains_count(){
416 | int count,r;
417 | rtlsdr_set_tuner_gain_mode(dongle.dev, 1);
418 | return rtlsdr_get_tuner_gains(dongle.dev, NULL);
419 | }
420 |
421 | void lib_get_tuner_gains(int *gains){
422 | int count,r;
423 | r = rtlsdr_set_tuner_gain_mode(dongle.dev, 1);
424 | count = rtlsdr_get_tuner_gains(dongle.dev, gains);
425 | }
426 |
427 | int lib_get_gain(){
428 | if (dongle.gain == -100){
429 | return -100;
430 | } else {
431 | return rtlsdr_get_tuner_gain(dongle.dev);
432 | }
433 | }
434 |
435 |
436 | uint32_t lib_frequency_convert(char *s){
437 | return (uint32_t)atofs(s);
438 | }
439 |
440 |
441 | void lib_stop()
442 | {
443 | sighandler(0);
444 | rtlsdr_cancel_async(dongle.dev);
445 | pthread_join(dongle.thread, NULL);
446 | safe_cond_signal(&demod.ready, &demod.ready_m);
447 | pthread_join(demod.thread, NULL);
448 | safe_cond_signal(&output.ready, &output.ready_m);
449 | safe_cond_signal(&controller.hop, &controller.hop_m);
450 | pthread_join(controller.thread, NULL);
451 | demod_cleanup(&demod);
452 | output_cleanup(&output);
453 | controller_cleanup(&controller);
454 |
455 | }
456 |
457 | void lib_output_close()
458 | {
459 | if (output.file != stdout) {
460 | fclose(output.file);}
461 | }
462 |
463 |
--------------------------------------------------------------------------------
/rtl_fm_python_common.py:
--------------------------------------------------------------------------------
1 | # rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
2 | # Copyright (C) 2012 by Steve Markgraf
3 | # Copyright (C) 2012 by Hoernchen
4 | # Copyright (C) 2012 by Kyle Keen
5 | # Copyright (C) 2013 by Elias Oenal
6 | # Copyright (C) 2014 by Thomas Winningham
7 | #
8 | # This program is free software: you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation, either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program. If not, see .
20 |
21 | from __future__ import print_function
22 | import ctypes, sys
23 | from time import sleep
24 | from threading import Thread
25 |
26 | printstderr = lambda x : print(x,file=sys.stderr)
27 |
28 |
29 | fm = ctypes.CDLL('./rtl_fm_python.so')
30 | get_s_level = fm.lib_get_s_level
31 | get_frequency = fm.lib_get_frequency
32 | set_demod_fm = fm.lib_set_demod_fm
33 | set_demod_wbfm = fm.lib_set_demod_wbfm
34 | set_demod_am = fm.lib_set_demod_am
35 | set_demod_lsb = fm.lib_set_demod_lsb
36 | set_demod_usb = fm.lib_set_demod_usb
37 | set_demod_raw = fm.lib_set_demod_raw
38 | set_frequency = lambda f : fm.lib_set_frequency(ctypes.c_uint32(f))
39 | set_squelch = lambda l : fm.lib_set_squelch_level(ctypes.c_int(l))
40 | get_demod = lambda : chr(fm.lib_get_demod_mode())
41 | str_to_freq = fm.lib_frequency_convert
42 |
43 | def process_args(l):
44 | c=len(l)+1
45 | argc=ctypes.c_int(c)
46 | argv_var=ctypes.c_char_p *c
47 | argu = ['rtl_fm'] + l
48 | argv=argv_var(*argu)
49 | return (argc,argv)
50 |
51 | def mag(value, e, suffix):
52 | t = float('1e%s' % e)
53 | i = int(value / float(t))
54 | r = int(value % float(t))
55 | r = str(r).rjust(e,'0')
56 | s = "%s.%s" % (i,r)
57 | while (s[-1]=="0" or s[-1]=="."):
58 | if s[-1]==".":
59 | s=s[:-1]
60 | break
61 | s=s[:-1]
62 | return s + suffix
63 |
64 | def freq_to_str(f):
65 | if f >= 1000000000:
66 | return mag(f,9,'G')
67 | if f >= 1000000:
68 | return mag(f,6,'M')
69 | if f >= 1000:
70 | return mag(f,3,'K')
71 | return str(f)
72 |
73 | set_freq_human = lambda f : set_frequency(str_to_freq(f))
74 | get_freq_human = lambda : freq_to_str(get_frequency())
75 |
76 | def set_demod(c):
77 | if c=='w' : set_demod_wbfm()
78 | if c=='f' : set_demod_fm()
79 | if c=='a' : set_demod_am()
80 | if c=='l' : set_demod_lsb()
81 | if c=='u' : set_demod_usb()
82 | if c=='r' : set_demod_raw()
83 |
84 | def get_gains():
85 | c=fm.lib_get_tuner_gains_count()
86 | b=(ctypes.c_int * c)()
87 | fm.lib_get_tuner_gains(b)
88 | return list(b)
89 |
90 | def set_gain(g):
91 | fm.lib_set_real_gain(g)
92 |
93 | def get_gain():
94 | return fm.lib_get_gain()
95 |
96 | def get_auto_gain():
97 | return get_gain()==-100
98 |
99 | def set_gain_human(g):
100 | fm.lib_set_gain(g)
101 |
102 | def set_auto_gain():
103 | fm.lib_set_auto_gain()
104 |
105 |
106 |
107 | def rtl_fm(args=["--help"]):
108 | fm.main(*process_args(args))
109 |
110 | def rtl_fm_setup_and_go(args):
111 | fm.lib_init_first()
112 | fm.lib_process_args(*process_args(args))
113 | fm.lib_input_open()
114 | fm.lib_output_open()
115 | fm.lib_go()
116 |
117 | def rtl_fm_loop():
118 | fm.lib_loop()
119 |
120 | def rtl_fm_finish():
121 | fm.lib_stop()
122 | fm.lib_output_close()
123 |
124 |
125 | def rtl_fm_wrapped(args=["--help"]):
126 | rtl_fm_setup_and_go(args)
127 | try:
128 | while True:
129 | sleep(0.01)
130 | except KeyboardInterrupt:
131 | rtl_fm_finish()
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/rtl_fm_python_thread.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python -i
2 |
3 | # rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
4 | # Copyright (C) 2012 by Steve Markgraf
5 | # Copyright (C) 2012 by Hoernchen
6 | # Copyright (C) 2012 by Kyle Keen
7 | # Copyright (C) 2013 by Elias Oenal
8 | # Copyright (C) 2014 by Thomas Winningham
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 2 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with this program. If not, see .
22 |
23 |
24 | from rtl_fm_python_common import *
25 |
26 | running = True
27 | queue=[]
28 |
29 | def rtl_fm_thread(args):
30 | rtl_fm_setup_and_go(args)
31 | while running==True:
32 | if len(queue) > 0:
33 | queue.pop()()
34 | sleep(1)
35 | rtl_fm_finish()
36 |
37 | def make_rtl_fm_thread(args=sys.argv[1:],block=False):
38 | t = Thread(target=rtl_fm_thread,args=(args,))
39 | t.daemon=True
40 | t.start()
41 | if block:
42 | try:
43 | while True:
44 | sleep(1)
45 | except KeyboardInterrupt:
46 | running = False
47 |
48 | def stop_thread():
49 | running = False
50 |
51 |
52 | if __name__ == "__main__":
53 | make_rtl_fm_thread(block=False)
54 |
55 |
--------------------------------------------------------------------------------
/rtl_fm_python_web.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # rtl-sdr, turns your Realtek RTL2832 based DVB dongle into a SDR receiver
3 | # Copyright (C) 2012 by Steve Markgraf
4 | # Copyright (C) 2012 by Hoernchen
5 | # Copyright (C) 2012 by Kyle Keen
6 | # Copyright (C) 2013 by Elias Oenal
7 | # Copyright (C) 2014 by Thomas Winningham
8 | #
9 | # This program is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # This program is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with this program. If not, see .
21 |
22 | from flask import Flask, jsonify, url_for, redirect
23 | from rtl_fm_python_thread import *
24 |
25 | make_rtl_fm_thread(block=False)
26 |
27 | app = Flask(__name__)
28 |
29 | @app.route('/')
30 | def web_root():
31 | return redirect(url_for('static', filename='index.html'))
32 |
33 | @app.route('/state')
34 | def web_state():
35 | return jsonify(
36 | {
37 | 's_level' : get_s_level(),
38 | 'freq_s' : get_freq_human(),
39 | 'freq_i' : get_frequency(),
40 | 'mod' : get_demod(),
41 | 'gain' : get_gain(),
42 | 'autogain' : get_auto_gain()
43 | })
44 |
45 | @app.route('/frequency/')
46 | def web_set_frequency(f):
47 | set_frequency(f)
48 | return web_state()
49 |
50 | @app.route('/frequency/human/')
51 | def web_set_human_frequency(f):
52 | set_freq_human(str(f))
53 | return web_state()
54 |
55 | @app.route('/demod/')
56 | def web_set_demod(c):
57 | set_demod(str(c))
58 | return web_state()
59 |
60 | @app.route('/gain/')
61 | def web_set_gain(g):
62 | gain = int(str(g))
63 | set_gain(gain)
64 | return web_state()
65 |
66 | @app.route('/gain/human/')
67 | def web_set_gain_human(g):
68 | gain = int(str(g))
69 | set_gain_human(gain)
70 | return web_state()
71 |
72 | @app.route('/gain/auto')
73 | def web_set_auto_gain():
74 | set_auto_gain()
75 | return web_state()
76 |
77 | @app.route('/gain/list')
78 | def web_get_gain_list():
79 | l=get_gains()
80 | return jsonify({'gains':l})
81 |
82 | if __name__ == '__main__':
83 | app.run(host='0.0.0.0',port=10100)
84 | stop_thread()
85 |
86 |
--------------------------------------------------------------------------------
/start_web.sh:
--------------------------------------------------------------------------------
1 | ./rtl_fm_python_web.py -M wbfm -f 101.1M - |aplay -r 32000 -f S16_LE -t raw -c 1
2 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/static/rtl_fm_python.js:
--------------------------------------------------------------------------------
1 | /** @jsx React.DOM */
2 |
3 | var ValuePair = React.createClass({
4 | render: function(){
5 | return (
6 |