From 1b2d8e3bc4192bbf728929584fe7a30252922957 Mon Sep 17 00:00:00 2001 From: pabr Date: Fri, 2 Sep 2016 23:43:09 +0200 Subject: [PATCH] Move to git. --- ChangeLog.md | 13 + LICENSE.txt | 674 ++++++++++++++++++++++++++++++++++++++++ README.coding.md | 17 + README.md | 56 ++++ src/apps/Makefile | 13 + src/apps/leandvb.cc | 450 +++++++++++++++++++++++++++ src/apps/leandvb_tui.sh | 21 ++ src/apps/leansdrcat.cc | 71 +++++ src/apps/leansdrscan.cc | 273 ++++++++++++++++ src/leansdr/dsp.h | 197 ++++++++++++ src/leansdr/dvb.h | 550 ++++++++++++++++++++++++++++++++ src/leansdr/framework.h | 217 +++++++++++++ src/leansdr/generic.h | 122 ++++++++ src/leansdr/gui.h | 471 ++++++++++++++++++++++++++++ src/leansdr/rs.h | 264 ++++++++++++++++ src/leansdr/sdr.h | 480 ++++++++++++++++++++++++++++ 16 files changed, 3889 insertions(+) create mode 100644 ChangeLog.md create mode 100644 LICENSE.txt create mode 100644 README.coding.md create mode 100644 README.md create mode 100644 src/apps/Makefile create mode 100644 src/apps/leandvb.cc create mode 100755 src/apps/leandvb_tui.sh create mode 100644 src/apps/leansdrcat.cc create mode 100644 src/apps/leansdrscan.cc create mode 100644 src/leansdr/dsp.h create mode 100644 src/leansdr/dvb.h create mode 100644 src/leansdr/framework.h create mode 100644 src/leansdr/generic.h create mode 100644 src/leansdr/gui.h create mode 100644 src/leansdr/rs.h create mode 100644 src/leansdr/sdr.h diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..f1210ca --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,13 @@ +HEAD + * leandvb is now distributed as part of leansdr. + * Support for all DVB-S code rates. + * Status output for third-party UIs: lock, MER, frequency offset. + * Software AGC is always enabled. rtl-sdr HW AGC is not recommended. + * stderr is quiet by default. Use -v -d for troubleshooting. + * Deconvolution is now algebraic (instead of look-up table). + * Added leansdrscan for cycling through DVB settings. + * Added leansdrcat for debugging real-time behaviour. + +2016-02-29 Preview release of leandvb + * Code rate 1/2 only. + * Hard-decision deconvolution (without Viterbi). diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.coding.md b/README.coding.md new file mode 100644 index 0000000..cc6b1b6 --- /dev/null +++ b/README.coding.md @@ -0,0 +1,17 @@ +leansdr uses C++ for namespaces and type-safe polymorphism. +No attempt is made to follow popular object-oriented practices. + +* Member variables are not prefixed with "m_". + +* Destructors are not implemented and memory management is minimal. + In practice, after the signal processing flow graph is instantiated, + no allocation/deallocation is expected until exit. + +* There are no unnecessary getter/setter methods. + +* Dependencies are kept to a minimum (no STL, no iostream). + +Other notes: + +* The code is not thread-safe. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..71363d4 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +leansdr: Lightweight, portable software-defined radio. +Copyright (C) 2016 + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + + +**leansdr** consists of: +* A simple data-flow framework for signal processing +* A library of software-defined radio functions +* Applications built on top of the above. + +Currently the main application is **leandvb**. + +# leandvb + +**leandvb** is a DVB-S demodulator designed for speed rather +than sensitivity. See http://www.pabr.org/radio/leandvb . + +## Quick start guide + +``` +git clone http://github.com/pabr/leansdr.git +cd leansdr/src/apps +make +``` + +### Receiving DATV transmissions from the ISS with a RTL-SDR dongle: + +``` +rtl_sdr -f $DOWNCONVERTED_FREQ -s 2400000 capture.iq +./leandvb -f 2400e3 --sr 2000e3 --cr 1/2 < /tmp/capture.iq > /tmp/capture.ts +mplayer capture.ts +``` + +### Troubleshooting + +``` +./leandvb_gui --gui -v -d -f 2400e3 --sr 2000e3 --cr 1/2 < /tmp/capture.iq > /tmp/capture.ts +``` + +#### Live receiver with auto-detection of symbol rate and code rate: + +``` +rtl_sdr -f $DOWNCONVERTED_FREQ -s 2400000 - | ./leansdrscan -v ./leandvb_gui --gui -f 2400e3 --sr 2000e3,1000e3,500e3,250e3 --cr 1/2,2/3,3/4,5/6,7/8 - | mplayer -cache 128 - +``` diff --git a/src/apps/Makefile b/src/apps/Makefile new file mode 100644 index 0000000..1a8cbeb --- /dev/null +++ b/src/apps/Makefile @@ -0,0 +1,13 @@ +APPS = leandvb leansdrscan leansdrcat leandvb_gui + +all: $(APPS) + +DEPS = ../leansdr/*.h + +CXXFLAGS = -O3 -I.. -Wall -Wno-sign-compare -Wno-array-bounds -Wno-unused-variable + +%: %.cc $(DEPS) + g++ $(CXXFLAGS) $< -o $@ + +%_gui: %.cc $(DEPS) + g++ $(CXXFLAGS) -DGUI $< -lX11 -o $@ diff --git a/src/apps/leandvb.cc b/src/apps/leandvb.cc new file mode 100644 index 0000000..9fd202d --- /dev/null +++ b/src/apps/leandvb.cc @@ -0,0 +1,450 @@ +// leandvb.cc copyright (c) 2016 pabr@pabr.org +// http://www.pabr.org/radio/leandvb + +#include +#include +#include +#include +#include +#include + +#include "leansdr/framework.h" +#include "leansdr/generic.h" +#include "leansdr/dsp.h" +#include "leansdr/sdr.h" +#include "leansdr/dvb.h" +#include "leansdr/rs.h" +#include "leansdr/gui.h" + +using namespace leansdr; + +// Main loop + +struct config { + bool verbose, debug; + enum { INPUT_U8, INPUT_F32 } input_format; + bool loop_input; + float Fs; // Sampling frequency (Hz) + float Fderot; // Shift the signal (Hz). Note: Ftune is faster + int anf; // Number of auto notch filters + int fd_pp; // FD for preprocessed data, or -1 + float awgn; // Standard deviation of noise + + float Fm; // QPSK symbol rate (Hz) + code_rate fec; + float Ftune; // Bias frequency for the QPSK demodulator (Hz) + + bool gui; // Plot stuff + float duration; // Horizontal span of timeline GUI (s) + bool linger; // Keep GUI running after EOF + int fd_info; // FD for status information in text format, or -1 + + config() + : verbose(false), + debug(false), + input_format(INPUT_U8), + loop_input(false), + Fs(2.4e6), + Fderot(0), + anf(1), + fd_pp(-1), + awgn(0), + Fm(2e6), + fec(FEC12), + Ftune(0), + gui(false), + duration(60), + linger(false), + fd_info(-1) { + } +}; + +int run(config &cfg) { + + int w_timeline = 512, h_timeline = 256; + int w_fft = 1024, h_fft = 256; + int wh_const = 256; + + scheduler sch; + sch.verbose = cfg.verbose; + sch.debug = cfg.debug; + + int x0 = 100, y0 = 100; + + window_placement window_hints[] = { + { "rawiq (iq)", x0, y0, wh_const,wh_const }, + { "rawiq (spectrum)", x0+300, y0, w_fft, h_fft }, + { "preprocessed (iq)", x0, y0+300, wh_const, wh_const }, + { "preprocessed (spectrum)", x0+300, y0+300, w_fft, h_fft }, + { "PSK symbols", x0, y0+600, wh_const, wh_const }, + { "timeline", x0+300, y0+600, w_timeline, h_timeline }, + { NULL, } + }; + sch.windows = window_hints; + + int BUF_OVERSIZE = 4; + // Min buffer size for baseband data + // scopes: 1024 + // ss_estimator: 1024 + // anf: 4096 + // qpsk_sampler: omega+2 (negligible) + unsigned long BUF_BASEBAND = 4096 * BUF_OVERSIZE; + // Need (1+204*(scan_syncs-1)+1)*8 = 4912 bits for deconvol+sync + unsigned long BUF_SYNC = 4912 * BUF_OVERSIZE; + // Need 17*11*12+204 = 2448 bytes for deinterleaver + unsigned long BUF_DEINTERLEAVE = 2448 * BUF_OVERSIZE; + unsigned long BUF_PACKETS = BUF_OVERSIZE; + unsigned long BUF_SLOW = BUF_OVERSIZE; + + // INPUT + + pipebuf p_rawiq(&sch, "rawiq", BUF_BASEBAND); + + if ( cfg.input_format == config::INPUT_U8 ) { + pipebuf *p_stdin = + new pipebuf(&sch, "stdin", BUF_BASEBAND); + file_reader *r_stdin = + new file_reader(&sch, 0, *p_stdin); + r_stdin->loop = cfg.loop_input; + cconverter *r_convert = + new cconverter(&sch, *p_stdin, p_rawiq); + } + if ( cfg.input_format == config::INPUT_F32 ) { +#if 0 // TBD + file_reader *r_stdin = + new file_reader(&sch, 0, p_rawiq); + r_stdin->loop = cfg.loop_input; +#else + fprintf(stderr, "TBD SCALING GAIN F32\n"); + pipebuf *p_stdin = + new pipebuf(&sch, "stdin", BUF_BASEBAND); + file_reader *r_stdin = + new file_reader(&sch, 0, *p_stdin); + r_stdin->loop = cfg.loop_input; + cconverter *r_convert = + new cconverter(&sch, *p_stdin, p_rawiq); +#endif + } + +#ifdef GUI + float amp = 128; + + if ( cfg.gui ) { + cscope *r_cscope_raw = + new cscope(&sch, p_rawiq, -amp, amp, "rawiq (iq)"); + spectrumscope *r_fft_raw = + new spectrumscope(&sch, p_rawiq, amp, "rawiq (spectrum)"); + r_fft_raw->amax *= 0.25; + } +#endif + + pipebuf *p_preprocessed = &p_rawiq; + + // NOISE + + if ( cfg.awgn ) { + if ( cfg.verbose ) + fprintf(stderr, "Adding noise with stddev %f\n", cfg.awgn); + pipebuf *p_noise = + new pipebuf(&sch, "noise", BUF_BASEBAND); + wgn_c *r_noise = + new wgn_c(&sch, *p_noise); + r_noise->stddev = cfg.awgn; + pipebuf *p_noisy = + new pipebuf(&sch, "noisy", BUF_BASEBAND); + adder *r_addnoise = + new adder(&sch, *p_preprocessed, *p_noise, *p_noisy); + p_preprocessed = p_noisy; + } + + // NOTCH FILTER + + if ( cfg.anf ) { + pipebuf *p_autonotched = + new pipebuf(&sch, "autonotched", BUF_BASEBAND); + auto_notch *r_auto_notch = + new auto_notch(&sch, *p_preprocessed, *p_autonotched, + cfg.anf, 0); + p_preprocessed = p_autonotched; + } else { + if ( cfg.verbose ) + fprintf(stderr, "ANF is disabled (requires a clean signal).\n"); + } + + // FREQUENCY CORRECTION + + if ( cfg.Fderot ) { + if ( cfg.verbose ) + fprintf(stderr, "Derotating from %.3f kHz\n", cfg.Fderot/1e3); + pipebuf *p_derot = + new pipebuf(&sch, "derotated", BUF_BASEBAND); + rotator *r_derot = + new rotator(&sch, *p_preprocessed, *p_derot, -cfg.Fderot/cfg.Fs); + p_preprocessed = p_derot; + } + +#if 0 + // LOW-PASS FILTERING + + int decim = cfg.Fs / cfg.Fm / 2; + if ( decim > 1 ) { + if ( cfg.verbose ) + fprintf(stderr, "Inserting filter %d\n", decim); + pipebuf *p_lowpass = + new pipebuf(&sch, "lowpass", BUF_BASEBAND); + naive_lowpass *r_lowpass = + new naive_lowpass(&sch, *p_preprocessed, *p_lowpass, decim); + p_preprocessed = p_lowpass; + } +#endif + +#ifdef GUI + if ( cfg.gui ) { + cscope *r_cscope_pp = + new cscope(&sch, *p_preprocessed, -amp, amp, "preprocessed (iq)"); + spectrumscope *r_fft_pp = + new spectrumscope(&sch, *p_preprocessed, amp, + "preprocessed (spectrum)"); + r_fft_pp->amax *= 0.25; + } +#endif + + // OUTPUT PREPROCESSED DATA + + if ( cfg.fd_pp >= 0 ) { + if ( cfg.verbose ) + fprintf(stderr, "Writing preprocessed data to FD %d\n", cfg.fd_pp); + file_writer *r_ppout = + new file_writer(&sch, *p_preprocessed, cfg.fd_pp); + } + + // QPSK + + pipebuf p_symbols(&sch, "PSK soft-symbols", BUF_SYNC); + pipebuf p_freq(&sch, "freq", BUF_SLOW); + pipebuf p_ss(&sch, "SS", BUF_SLOW); + pipebuf p_mer(&sch, "MER", BUF_SLOW); + pipebuf p_sampled(&sch, "PSK symbols", BUF_BASEBAND); + // TBD retype preprocess as unsigned char + cstln_receiver demod(&sch, *p_preprocessed, p_symbols, + &p_freq, &p_ss, &p_mer, &p_sampled); + cstln_lut<256> qpsk(cstln_lut<256>::QPSK); + demod.cstln = &qpsk; + demod.set_omega(cfg.Fs/cfg.Fm); + if ( cfg.Ftune ) { + if ( cfg.verbose ) + fprintf(stderr, "Biasing receiver to %.3f kHz\n", cfg.Ftune/1e3); + demod.set_freq(cfg.Ftune/cfg.Fs); + } + demod.meas_decimation = 128*1024; + +#ifdef GUI + if ( cfg.gui ) { + cscope *r_scope_symbols = + new cscope(&sch, p_sampled, -amp,amp); + r_scope_symbols->decimation = 1; + } +#endif + + // NOT VITERBI (deconvolution only) + // SYNCHRONIZATION + + // pipebuf p_bits(&sch, "bits", BUF_DEINTERLEAVE*8); + // EN 300 421, section 4.4.3, table 2 Punctured code, G1=0171, G2=0133 + // deconvol r_deconv(&sch, p_symbols, p_bits, 0171, 0133, FEC78); + // deconvol_sync r_deconv(&sch, p_symbols, p_bits, FEC12); + + pipebuf p_bytes(&sch, "bytes", BUF_DEINTERLEAVE); + pipebuf p_lock(&sch, "lock", BUF_SLOW); + + deconvol_sync_simple r_deconv = + make_deconvol_sync_simple(&sch, p_symbols, p_bytes, cfg.fec); + + pipebuf p_mpegbytes(&sch, "mpegbytes", BUF_DEINTERLEAVE); + mpeg_sync r_sync(&sch, p_bytes, p_mpegbytes, &r_deconv, &p_lock); + + // DEINTERLEAVING + + pipebuf< rspacket > p_rspackets(&sch, "RS-enc packets", BUF_PACKETS); + deinterleaver r_deinter(&sch, p_mpegbytes, p_rspackets); + + // REED-SOLOMON + + pipebuf p_rtspackets(&sch, "rand TS packets", BUF_PACKETS); + rs_decoder r_rsdec(&sch, p_rspackets, p_rtspackets); + + // DERANDOMIZATION + + pipebuf p_tspackets(&sch, "TS packets", BUF_PACKETS); + derandomizer r_derand(&sch, p_rtspackets, p_tspackets); + + // OUTPUT + + file_writer r_stdout(&sch, p_tspackets, 1); + + // AUX OUTPUT + + if ( cfg.fd_info >= 0 ) { + file_printer *r_printfreq = + new file_printer(&sch, "FREQ %.0f\n", p_freq, cfg.fd_info); + r_printfreq->scale = cfg.Fs; + new file_printer(&sch, "SS %f\n", p_ss, cfg.fd_info); + new file_printer(&sch, "MER %.1f\n", p_mer, cfg.fd_info); + new file_printer(&sch, "LOCK %d\n", p_lock, cfg.fd_info); + // Output constants immediately + FILE *f = fdopen(cfg.fd_info, "w"); + static const char *fec_names[] = { "1/2", "2/3", "3/4", "5/6", "7/8" }; + fprintf(f, "CR %s\n", fec_names[cfg.fec]); + fprintf(f, "SR %f\n", cfg.Fm); + fflush(f); + } + + // TIMELINE SCOPE + +#ifdef GUI + pipebuf p_tscount(&sch, "packet counter", BUF_PACKETS*100); + itemcounter r_tscounter(&sch, p_tspackets, p_tscount); + float max_packet_rate = cfg.Fm / 8 / 204; + float pixel_rate = cfg.Fs / demod.meas_decimation; + float max_packets_per_pixel = max_packet_rate / pixel_rate; + + slowmultiscope::chanspec chans[] = { + { &p_freq, "estimated frequency", "%3.0f kHz", {0,255,255}, + cfg.Fs*1e-3f, + (cfg.Ftune-cfg.Fs/4)*1e-3f, (cfg.Ftune+cfg.Fs/4)*1e-3f, + slowmultiscope::chanspec::DEFAULT }, + { &p_ss, "signal strength", "%3.0f", {255,0,0}, + 1, 0,128, + slowmultiscope::chanspec::DEFAULT }, + { &p_mer, "MER", "%5.1f dB", {255,0,255}, + 1, -30,30, + slowmultiscope::chanspec::DEFAULT }, + { &p_tscount, "TS recovery", "%3.0f %%", {255,255,0}, + 110/max_packets_per_pixel, 0, 101, + (slowmultiscope::chanspec::flag) + (slowmultiscope::chanspec::ASYNC | + slowmultiscope::chanspec::SUM) }, + }; + + if ( cfg.gui ) { + slowmultiscope *r_scope_timeline = + new slowmultiscope(&sch, chans, sizeof(chans)/sizeof(chans[0]), + "timeline"); + r_scope_timeline->sample_freq = cfg.Fs / demod.meas_decimation; + unsigned long nsamples = cfg.duration * cfg.Fs / demod.meas_decimation; + r_scope_timeline->samples_per_pixel = (nsamples+w_timeline)/w_timeline; + } +#endif // GUI + + if ( cfg.verbose ) + fprintf(stderr, + "Output:\n" + " '_': packet received without errors\n" + " '.': error-corrected packet\n" + " '!': packet with remaining errors\n"); + + sch.run(); + + if ( cfg.verbose ) sch.dump(); + + if ( cfg.gui && cfg.linger ) while ( 1 ) { sch.run(); usleep(10000); } + + return 0; +} + +// Command-line + +void usage(const char *name, FILE *f, int c) { + fprintf(f, "Usage: %s [options] < IQ > TS\n", name); + fprintf(f, "Demodulate DVB-S I/Q on stdin, output MPEG packets on stdout\n"); + fprintf(f, + "\nInput options:\n" + " --u8 Input format is 8-bit unsigned (rtl_sdr, default)\n" + " --f32 Input format is 32-bit float (gqrx)\n" + " -f HZ Input sample rate (default: 2.4e6)\n" + " --loop Repeat (stdin must be a file)\n"); + fprintf(f, + "\nPreprocessing options:\n" + " --anf N Number of birdies to remove (default: 1)\n" + " --derotate HZ For use with --fd-pp, otherwise use --tune\n" + " --fd-pp NUM Dump preprocessed IQ data to file descriptor\n" + ); + fprintf(f, + "\nDVB-S options:\n" + " --sr HZ Symbol rate (default: 2e6)\n" + " --tune HZ Bias frequency for demodulation\n" + " --cr N/D Code rate 1/2 .. 7/8 (default: 1/2)\n" + ); + fprintf(f, + "\nUI options:\n" + " -h Print this message\n" + " -v Output info during startup\n" + " -d Output debugging info\n" + " --fd-info NUM Print demodulator status to file descriptor\n" + ); +#ifdef GUI + fprintf(f, + " --gui Show constellation and spectrum\n" + " --duration S Width of timeline plot (default: 60)\n" + " --linger Keep GUI running after EOF\n" + ); +#endif + fprintf(f, "\nTesting options:\n" + " --awgn STDDEV Add white gaussian noise (slow)\n" + ); + exit(c); +} + +int main(int argc, const char *argv[]) { + config cfg; + + for ( int i=1; i&1 1>&4 | + ( while read tag val; do + case "$tag" in + LOCK) + case "$val" in + 0) lock="[SEARCH]" ;; + 1) lock="[LOCKED]" ;; + esac ;; + MER) mer=$(printf "[MER %4.1f dB]" "$val") ;; + SS) ss=$(printf "[SS %3.0f]" "$val") ;; + FREQ) freq=$(printf "[Offset %+5.0f Hz]" "$val") ;; + CR) cr=$(printf "[FEC %s]" "$val") ;; + SR) sr=$(printf "[SR %7.0f Hz]" "$val") ;; + *) echo -e "\n$tag $val" 1>&2 ;; + esac + echo -ne "\r$ss $freq $mer $lock $sr $cr" 1>&2 + done) +) 4>&1 +echo 1>&2 \ No newline at end of file diff --git a/src/apps/leansdrcat.cc b/src/apps/leansdrcat.cc new file mode 100644 index 0000000..b81735b --- /dev/null +++ b/src/apps/leansdrcat.cc @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include + +void fatal(const char *s) { perror(s); exit(1); } + +void usage(const char *name, FILE *f, int c) { + fprintf(f, "Usage: %s [--cbr bits_per_sec]\n", name); + fprintf(f, "Forward from stdin to stdout at constant rate.\n"); + exit(c); +} + +int main(int argc, const char *argv[]) { + int bytespersec = 2400000 * 2; + + for ( int i=1; i 1000000 ) us = 1000000; + usleep(us); + } else { + want = blocksize; + unsigned char buf[want]; + ssize_t nr = read(0, buf, want); + if ( nr < 0 ) fatal("read"); + if ( ! nr ) return 0; + current += nr; + ssize_t nw = write(1, buf, nr); + if ( nw < 0 ) { + if ( errno == EWOULDBLOCK ) fprintf(stderr, "#"); + else fatal("write"); + } else if ( ! nw ) fatal("write: EOF"); + else if ( nw < nr ) fprintf(stderr, "#"); + } + } + +} diff --git a/src/apps/leansdrscan.cc b/src/apps/leansdrscan.cc new file mode 100644 index 0000000..e2695b6 --- /dev/null +++ b/src/apps/leansdrscan.cc @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void fatal(const char *s) { perror(s); exit(1); } + +struct field { + int nvalues; + char **values; + int current; + struct field *next; + field(char *s) { + int nsep = 0; + for ( unsigned int i=0; iiterate(); + return false; + } + return true; + } +}; + +struct config { + bool verbose; + size_t maxsend; + float timeout; + bool rewind; + field *fields; + int nfields; + config() : + verbose(false), maxsend(16<<20), timeout(1.0), + rewind(false), fields(NULL), nfields(0) { } +}; + +int do_write(int fd, char *buf, int count) { + while ( count ) { + int nw = write(fd, buf, count); + if ( nw < 0 ) return nw; + if ( ! nw ) fatal("eof"); + buf += nw; + count -= nw; + } + return 0; +} + +int run_program(config &cfg, char *const argv[]) { + int fd0[2], fd1[2]; + if ( pipe(fd0) ) fatal("pipe"); + if ( pipe(fd1) ) fatal("pipe"); + pid_t child = fork(); + if ( ! child ) { + // Child + close(fd0[1]); + close(fd1[0]); + dup2(fd0[0], 0); + dup2(fd1[1], 1); + execvp(argv[0], argv); + perror("execvp"); + exit(errno); + } + + // Parent + close(fd0[0]); + close(fd1[1]); + int nreceived = 0; + size_t chunk = 65536; + struct timeval latest; + if ( gettimeofday(&latest, NULL) ) fatal("gettimeofday"); + + size_t maxsend = cfg.maxsend; + + while ( true ) { + fd_set fds; + FD_ZERO(&fds); + if ( !cfg.rewind || maxsend ) + FD_SET(0, &fds); + FD_SET(fd1[0], &fds); + struct timeval tv = { (int)cfg.timeout, (int)(cfg.timeout*1e6)%1000000 }; + int ns = select(fd1[0]+1, &fds, NULL, NULL, &tv); + if ( ns < 0 ) fatal("select"); + + // Timeout + struct timeval now; + if ( gettimeofday(&now, NULL) ) fatal("gettimeofday"); + float time_silent = + (now.tv_sec -latest.tv_sec ) + (now.tv_usec-latest.tv_usec)*1e-6; + if ( time_silent >= cfg.timeout ) { + if ( cfg.verbose ) fprintf(stderr, "No output from child\n"); + break; + } + + // Input data from our stdin + + if ( FD_ISSET(0, &fds) ) { + char buf[chunk]; + if ( ! cfg.rewind ) { + // Reading from live stream + size_t nr = read(0, buf, sizeof(buf)); + if ( nr < 0 ) fatal("read"); + if ( ! nr ) { + if ( cfg.verbose ) fprintf(stderr, "End of stream, exiting\n"); + exit(0); + } + if ( do_write(fd0[1], buf, nr) ) + // Broken pipe, child has exited + break; + } else { + // Reading from file + size_t maxread = maxsend; + if ( maxread > sizeof(buf) ) maxread = sizeof(buf); + ssize_t nr = read(0, buf, maxread); + if ( nr < 0 ) fatal("read"); + if ( ! nr ) { + if ( cfg.verbose ) fprintf(stderr, "Sending EOF\n"); + close(fd0[1]); + maxsend = 0; + } + if ( do_write(fd0[1], buf, nr) ) + // Broken pipe, child has exited + break; + maxsend -= nr; + } + } + + // Output data from stdout of child + + if ( FD_ISSET(fd1[0], &fds) ) { + char buf[chunk]; + ssize_t nr = read(fd1[0], buf, sizeof(buf)); + if ( ! nr ) break; + if ( nr < 0 ) fatal("read(child)"); + if ( ! cfg.rewind ) + // Live streaming + if ( do_write(1, buf, nr) ) fatal("write"); + nreceived += nr; + latest = now; + } + } + + close(fd0[1]); + close(fd1[0]); + kill(child, SIGKILL); + int status; + waitpid(child, &status, 0); + return nreceived; +} + + +void print_command(char *argv[]) { + for ( ; *argv; ++argv ) fprintf(stderr, " %s", *argv); + fprintf(stderr, "\n"); +} + +int run(config &cfg) { + // Don't die when child processes terminate + signal(SIGPIPE, SIG_IGN); + + do { + do { + // Try the current combination of settings + char *argv[cfg.nfields+1]; + int i = 0; + for ( field *f=cfg.fields; f; ++i,f=f->next ) + argv[i] = f->values[f->current]; + argv[i] = NULL; + if ( cfg.verbose ) { + fprintf(stderr, "Trying command:"); + print_command(argv); + } + int nreceived = run_program(cfg, argv); + // Seek to beginning of input file if not in live streaming mode + if ( cfg.rewind ) + if ( lseek(0, 0, SEEK_SET) ) fatal("lseek"); + if ( nreceived ) { + if ( cfg.verbose ) { + fprintf(stderr, "Got %d with command:", nreceived); + print_command(argv); + } + if ( cfg.rewind ) { + if ( cfg.verbose ) fprintf(stderr, "Now processing whole file.\n"); + execvp(argv[0], argv); + exit(1); + } + } + // Next combination of setting + } while ( cfg.fields->iterate() ); + // Loop if in live streaming mode + } while ( ! cfg.rewind ); + return 0; +} + +// CLI + +void usage(const char *name, FILE *f, int c) { + fprintf(f, "Usage: %s [options] [program settings]\n", name); + fprintf(f, "Run , cycling through combinations of settings.\n"); + fprintf(f, "Example: '%s -v cat -n,-e' will feed stdin through" + " 'cat -n' and 'cat -e' alternatively.\n", name); + + fprintf(f, + "\nOptions:\n" + " -h Print this message\n" + " -v Verbose\n" + " --timeout N Next settings if no output within N seconds\n" + " --rewind Rewind input (stdin must be a file)\n" + " --probesize N Forward only N bytes (with --rewind)\n" + ); + exit(c); +} + +int main(int argc, const char *argv[]) { + config cfg; + + int i; + for ( i=1; inext = NULL; + *plast = f; + plast = &f->next; + ++cfg.nfields; + } + + if ( ! cfg.fields ) usage(argv[0], stderr, 1); + + if ( cfg.verbose ) { + fprintf(stderr, "Fields:"); + for ( field *f=cfg.fields; f; f=f->next ) { + fprintf(stderr, " "); + if ( f->nvalues > 1 ) fprintf(stderr, "{"); + fprintf(stderr, "%s", f->values[0]); + for ( int i=1; invalues; ++i ) + fprintf(stderr, "|%s", f->values[i]); + if ( f->nvalues > 1 ) fprintf(stderr, "}"); + } + fprintf(stderr, "\n"); + } + + return run(cfg); +} diff --git a/src/leansdr/dsp.h b/src/leansdr/dsp.h new file mode 100644 index 0000000..e513d0b --- /dev/null +++ b/src/leansdr/dsp.h @@ -0,0 +1,197 @@ +#ifndef LEANSDR_DSP_H +#define LEANSDR_DSP_H + +#include + +namespace leansdr { + + ////////////////////////////////////////////////////////////////////// + // DSP blocks + ////////////////////////////////////////////////////////////////////// + + template + T min(const T &x, const T &y) { return (x + struct complex { + T re, im; + complex() { } + complex(T x) : re(x), im(0) { } + complex(T x, T y) : re(x), im(y) { } + }; + + template + complex operator +(const complex &a, const complex &b) { + return complex(a.re+b.re, a.im+b.im); + } + + template + complex operator *(const complex &a, const T &k) { + return complex(a.re*k, a.im*k); + } + + // [cconverter] converts complex streams between numric types, + // with optionnal ofsetting and rational scaling. + template + struct cconverter : runnable { + pipereader< complex > in; + pipewriter< complex > out; + cconverter(scheduler *sch, pipebuf< complex > &_in, + pipebuf< complex > &_out) + : runnable(sch, "cconverter"), + in(_in), out(_out) { + } + void run() { + unsigned long count = min(in.readable(), out.writable()); + complex *pin=in.rd(), *pend=pin+count; + complex *pout = out.wr(); + for ( ; pinre = Zout + ((Tout)pin->re-(Tout)Zin)*Gn/Gd; + pout->im = Zout + ((Tout)pin->im-(Tout)Zin)*Gn/Gd; + } + in.read(count); + out.written(count); + } + }; + + template + struct cfft_engine { + const int n; + cfft_engine(int _n) : n(_n), invsqrtn(1/sqrt(n)) { + // Compute log2(n) + logn = 0; + for ( int t=n; t>1; t>>=1 ) ++logn; + // Bit reversal + bitrev = new int[n]; + for ( int i=0; i>b)&1); + } + // Float constants + omega = new complex[n]; + omega_rev = new complex[n]; + for ( int i=0; i *data, bool reverse=false) { + // Bit-reversal permutation + for ( int i=0; i tmp=data[i]; data[i]=data[r]; data[r]=tmp; } + } + complex *om = reverse ? omega_rev : omega; + // Danielson-Lanczos + for ( int i=0; i &w = om[k*dom]; + complex &dqk = data[q+k]; + complex x(w.re*dqk.re - w.im*dqk.im, + w.re*dqk.im + w.im*dqk.re); + data[q+k].re = data[p+k].re - x.re; + data[q+k].im = data[p+k].im - x.im; + data[p+k].re = data[p+k].re + x.re; + data[p+k].im = data[p+k].im + x.im; + } + } + } + float invn = 1.0 / n; + for ( int i=0; i *omega, *omega_rev; + float invsqrtn; + }; + + template + struct adder : runnable { + adder(scheduler *sch, + pipebuf &_in1, pipebuf &_in2, pipebuf &_out) + : runnable(sch, "adder"), + in1(_in1), in2(_in2), out(_out) { + } + void run() { + int n = out.writable(); + if ( in1.readable() < n ) n = in1.readable(); + if ( in2.readable() < n ) n = in2.readable(); + T *pin1=in1.rd(), *pin2=in2.rd(), *pout=out.wr(), *pend=pout+n; + while ( pout < pend ) *pout++ = *pin1++ + *pin2++; + in1.read(n); + in2.read(n); + out.written(n); + } + private: + pipereader in1, in2; + pipewriter out; + }; + + // [awgb_c] generates complex white gaussian noise. + + template + struct wgn_c : runnable { + wgn_c(scheduler *sch, pipebuf< complex > &_out) + : runnable(sch, "awgn"), stddev(1.0), out(_out) { + } + void run() { + int n = out.writable(); + complex *pout=out.wr(), *pend=pout+n; + while ( pout < pend ) { + float x, y, r2; + do { + x = 2*drand48() - 1; + y = 2*drand48() - 1; + r2 = x*x + y*y; + } while ( r2==0 || r2>=1 ); + float k = sqrtf(-2*log(r2)/r2) * stddev; + pout->re = k*x; + pout->im = k*y; + ++pout; + } + out.written(n); + } + float stddev; + private: + pipewriter< complex > out; + }; + + template + struct naive_lowpass : runnable { + naive_lowpass(scheduler *sch, pipebuf &_in, pipebuf &_out, int _w) + : runnable(sch, "lowpass"), in(_in), out(_out), w(_w) { + } + + void run() { + if ( in.readable() < w ) return; + unsigned long count = min(in.readable()-w, out.writable()); + T *pin=in.rd(), *pend=pin+count; + T *pout = out.wr(); + float k = 1.0 / w; + for ( ; pin in; + pipewriter out; + }; + +} // namespace + +#endif // LEANSDR_DSP_H diff --git a/src/leansdr/dvb.h b/src/leansdr/dvb.h new file mode 100644 index 0000000..04e2890 --- /dev/null +++ b/src/leansdr/dvb.h @@ -0,0 +1,550 @@ +#ifndef LEANSDR_DVB_H +#define LEANSDR_DVB_H + +namespace leansdr { + + static const int SIZE_RSPACKET = 204; + static const int MPEG_SYNC = 0x47; + static const int MPEG_SYNC_INV = (MPEG_SYNC^0xff); + static const int MPEG_SYNC_CORRUPTED = 0x55; + + // Generic deconvolution + + enum code_rate { FEC12, FEC23, FEC34, FEC56, FEC78 }; + + static const int DVBS_G1 = 0171; + static const int DVBS_G2 = 0133; + +// G1 = 0b1111001 +// G2 = 0b1011011 +// +// G1 = [ 1 1 1 1 0 0 1 ] +// G2 = [ 1 0 1 1 0 1 1 ] +// +// C = [ G2 ; +// G1 ; +// 0 G2 ; +// 0 G1 ; +// 0 0 G2 ; +// 0 0 G1 ] +// +// C = [ 1 0 1 1 0 1 1 0 0 0 0 0 0 ; +// 1 1 1 1 0 0 1 0 0 0 0 0 0 ; +// 0 1 0 1 1 0 1 1 0 0 0 0 0 ; +// 0 1 1 1 1 0 0 1 0 0 0 0 0 ; +// 0 0 1 0 1 1 0 1 1 0 0 0 0 ; +// 0 0 1 1 1 1 0 0 1 0 0 0 0 ; +// 0 0 0 1 0 1 1 0 1 1 0 0 0 ; +// 0 0 0 1 1 1 1 0 0 1 0 0 0 ; +// 0 0 0 0 1 0 1 1 0 1 1 0 0 ; +// 0 0 0 0 1 1 1 1 0 0 1 0 0 ; +// 0 0 0 0 0 1 0 1 1 0 1 1 0 ; +// 0 0 0 0 0 1 1 1 1 0 0 1 0 ; +// 0 0 0 0 0 0 1 0 1 1 0 1 1 ; +// 0 0 0 0 0 0 1 1 1 1 0 0 1 ] +// +// IQ = [ Q1; I1; ... Q10; I10 ] = C * S +// +// D * C == [ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 ] +// +// D = [ 0 1 0 1 1 1 0 1 1 1 0 0 0 0] +// D = 0x3ba + + template + struct deconvol_sync : runnable { + deconvol_sync(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + unsigned long gX, unsigned long gY, + unsigned long pX, unsigned long pY) + : runnable(sch, "deconvol_sync"), + in(_in), out(_out,SIZE_RSPACKET), + skip(0) { + conv = new unsigned long[2]; + conv[0] = gX; + conv[1] = gY; + nG = 2; + punct = new unsigned long[2]; + punct[0] = pX; + punct[1] = pY; + punctperiod = 0; + punctweight = 0; + for ( int i=0; i<2; ++i ) { + int nbits = log2(punct[i]) + 1; + if ( nbits > punctperiod ) punctperiod = nbits; + punctweight += hamming(punct[i]); + } + if ( sch->verbose ) + fprintf(stderr, "puncturing %d/%d\n", punctperiod, punctweight); + deconv = new iq_t[punctperiod]; + inverse_convolution(); + init_syncs(); + locked = &syncs[0]; + } + + unsigned char hamming(unsigned long x) { + int h = 0; + for ( ; x; x>>=1 ) h += x&1; + return h; + } + unsigned char parity(unsigned long x) { + unsigned char parity = 0; + for ( ; x; x>>=1 ) parity ^= x&1; + return parity; + } + unsigned char parity(unsigned long long x) { + unsigned char parity = 0; + for ( ; x; x>>=1 ) parity ^= x&1; + return parity; + } + + typedef unsigned long long signal_t; + typedef unsigned long long iq_t; + + static int log2(unsigned long long x) { + int n = -1; + for ( ; x; ++n,x>>=1 ) ; + return n; + } + + iq_t convolve(signal_t s) { + int sbits = log2(s) + 1; + iq_t iq = 0; + unsigned char state = 0; + for ( int b=sbits-1; b>=0; --b ) { // Feed into convolver, MSB first + unsigned char bit = (s>>b) & 1; + state = (state>>1) | (bit<<6); // Shift register + for ( int j=0; j *best ) return; + if ( nprefix > sizeof(prefix)*8 ) return; + int solved = 1; + for ( int b=0; b>b)&1) ) { + // Current candidate does not solve this column. + if ( (response[b]>>nprefix) == 0 ) + // No more bits to trace back. + return; + solved = 0; + } + } + if ( solved ) { *best = prefix; return; } + solve_rec(prefix, nprefix+1, exp, best); + solve_rec(prefix|((iq_t)1< traceback ) + fail("traceback exceeds limit"); + } + } + + static const int NSYNCS = 8; + + struct sync_t { + u8 lut[2][2]; // lut[(re>0)?1:0][(im>0)?1:0] = 0b000000IQ + iq_t in; + int n_in; + signal_t out; + int n_out; + } syncs[NSYNCS]; + + void init_syncs() { + // EN 300 421, section 4.5, Figure 5 QPSK constellation + // Four rotations * two conjugations. + for ( int sync_id=0; sync_id 1 byte + // 2/3 12 symbols -> 2 bytes + // 3/4 16 symbols -> 3 bytes + // 5/6 24 symbols -> 5 bytes + // 7/8 32 symbols -> 7 bytes + + inline Tbyte readbyte(sync_t *s, softsymbol *&p) { + while ( s->n_out < 8 ) { + while ( s->n_in < traceback ) { + u8 iq = s->lut[(p->symbol&2)?1:0][p->symbol&1]; + ++p; + s->in = (s->in<<2) | iq; + s->n_in += 2; + } + iq_t iq = s->in >> (s->n_in-40); + for ( int b=punctperiod-1; b>=0; --b ) { + u8 bit = parity(iq&deconv[b]); + s->out = (s->out<<1) | bit; + } + s->n_out += punctperiod; + s->n_in -= punctweight; + } + Tbyte res = (s->out >> (s->n_out-8)) & 255; + s->n_out -= 8; + return res; + } + + void run_decoding() { + in.read(skip); + skip = 0; + + // 8 byte margin to fill the deconvolver + int maxrd = (in.readable()-64) / (punctweight/2) * punctperiod / 8; + int maxwr = out.writable(); + int n = (maxrd in; + pipewriter out; + // DECONVOL + int nG; + unsigned long *conv; // [nG] Convolution polynomials; MSB is newest + unsigned long *punct; // [nG] Puncturing pattern + int punctperiod, punctweight; + iq_t *deconv; // [punctperiod] Deconvolution polynomials + sync_t *locked; + int skip; + + }; + + typedef deconvol_sync deconvol_sync_simple; + + deconvol_sync_simple make_deconvol_sync_simple(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + enum code_rate rate) { + unsigned long pX, pY; + switch ( rate ) { + case FEC12: + pX = 0x1; // 1 + pY = 0x1; // 1 + break; + case FEC23: + pX = 0x2; // 10 + pY = 0x3; // 11 + break; + case FEC34: + pX = 0x5; // 101 + pY = 0x6; // 110 + break; + case FEC56: + pX = 0x15; // 10101 + pY = 0x1a; // 11010 + break; + case FEC78: + pX = 0x45; // 1000101 + pY = 0x7a; // 1111010 + break; + default: + fail("Code rate not implemented"); + } + return deconvol_sync_simple(sch, _in, _out, DVBS_G1, DVBS_G2, pX, pY); + } + + template + struct mpeg_sync : runnable { + int scan_syncs, want_syncs; + unsigned long lock_timeout; + + mpeg_sync(scheduler *sch, + pipebuf &_in, + pipebuf &_out, + deconvol_sync *_deconv, + pipebuf *_state_out=NULL) + : runnable(sch, "sync_detect"), + scan_syncs(4), want_syncs(2), + lock_timeout(4), + in(_in), out(_out, SIZE_RSPACKET*(scan_syncs+1)), + deconv(_deconv), + bitphase(0), synchronized(false), + report_state(true) { + state_out = _state_out ? new pipewriter(*_state_out) : NULL; + } + + void run() { + if ( report_state && state_out && state_out->writable()>=1 ) { + *state_out->wr() = 0; + state_out->written(1); + report_state = false; + } + if ( ! synchronized ) run_searching(); else run_decoding(); + } + + void run_searching() { + int chunk = SIZE_RSPACKET * (scan_syncs+1); + while ( in.readable() >= chunk+1 && + out.writable() >= chunk && + ( !state_out || state_out->writable()>=1 ) ) { + Tbyte *pin = in.rd(), *pend = pin+chunk; + Tbyte *pout = out.wr(); + for ( ; pin> bitphase; + } + for ( int i=0; i=want_syncs && phase8>=0 ) { + if ( sch->debug ) fprintf(stderr, "Locked\n"); + if ( ! i ) { // Avoid fixpoint detection + i = SIZE_RSPACKET; + phase8 = (phase8+1) & 7; + } + in.read(i); // Skip until beginning + synchronized = true; + lock_timeleft = lock_timeout; + if ( state_out ) { + *state_out->wr() = 1; + state_out->written(1); + } + return; + } + } + in.read(chunk); + ++bitphase; + if ( bitphase == 8 ) { + bitphase = 0; + deconv->next_sync(); + } + } + } + + void run_decoding() { + while ( in.readable() >= SIZE_RSPACKET+1 && + out.writable() >= SIZE_RSPACKET && + ( !state_out || state_out->writable()>=1 ) ) { + Tbyte *pin = in.rd(), *pend = pin+SIZE_RSPACKET; + Tbyte *pout = out.wr(); + for ( ; pin> bitphase; + } + in.read(SIZE_RSPACKET); + Tbyte syncbyte = *out.wr(); + out.written(SIZE_RSPACKET); + // Reset timer if sync byte is correct + Tbyte expected = phase8 ? MPEG_SYNC : MPEG_SYNC_INV; + if ( syncbyte == expected ) lock_timeleft = lock_timeout; + phase8 = (phase8+1) & 7; + --lock_timeleft; + if ( ! lock_timeleft ) { + if ( sch->debug ) fprintf(stderr, "Unlocked\n"); + synchronized = false; + if ( state_out ) { + *state_out->wr() = 0; + state_out->written(1); + } + return; + } + } + } + + private: + pipereader in; + pipewriter out; + deconvol_sync *deconv; + int bitphase; + bool synchronized; + int phase8; + unsigned long lock_timeleft; + pipewriter *state_out; + bool report_state; + }; + + // DEINTERLEAVING + + template + struct rspacket { Tbyte data[SIZE_RSPACKET]; }; + + template + struct deinterleaver : runnable { + deinterleaver(scheduler *sch, pipebuf &_in, + pipebuf< rspacket > &_out) + : runnable(sch, "deinterleaver"), + in(_in), out(_out) { + } + void run() { + while ( in.readable() >= 17*11*12+SIZE_RSPACKET && + out.writable() >= 1 ) { + Tbyte *pin = in.rd()+17*11*12, *pend=pin+SIZE_RSPACKET; + Tbyte *pout= out.wr()->data; + for ( int delay=17*11; pin in; + pipewriter< rspacket > out; + }; + + static const int SIZE_TSPACKET = 188; + struct tspacket { u8 data[SIZE_TSPACKET]; }; + + // DERANDOMIZATION + + struct derandomizer : runnable { + derandomizer(scheduler *sch, pipebuf &_in, pipebuf &_out) + : runnable(sch, "derandomizer"), + in(_in), out(_out) { + precompute_pattern(); + pos = pattern; + pattern_end = pattern + sizeof(pattern)/sizeof(pattern[0]); + } + void precompute_pattern() { + // EN 300 421, section 4.4.1 Transport multiplex adaptation + pattern[0] = 0xff; // Restore the inverted sync byte + unsigned short st = 000251; // 0b 000 000 010 101 001 (Fig 2 reversed) + for ( int i=1; i<188*8; ++i ) { + u8 out = 0; + for ( int n=8; n--; ) { + int bit = ((st>>13) ^ (st>>14)) & 1; // Taps + out = (out<<1) | bit; // MSB first + st = (st<<1) | bit; // Feedback + } + pattern[i] = (i%188) ? out : 0; // Inhibit on sync bytes + } + } + void run() { + while ( in.readable()>=1 && out.writable()>=1 ) { + u8 *pin = in.rd()->data, *pend = pin+SIZE_TSPACKET; + u8 *pout= out.wr()->data; + if ( pin[0] == MPEG_SYNC_INV || + pin[0] == (MPEG_SYNC_INV^MPEG_SYNC_CORRUPTED) ) { + if ( pos != pattern ) { + if ( sch->debug ) + fprintf(stderr, "derandomizer: resynchronizing\n"); + pos = pattern; + } + } + for ( ; pindata[0]; + if ( sync == MPEG_SYNC ) { + out.written(1); + } else { + if ( sync != (MPEG_SYNC^MPEG_SYNC_CORRUPTED) ) + if ( sch->debug ) fprintf(stderr, "(%02x)", sync); + out.wr()->data[1] |= 0x80; // Set the Transport Error Indicator bit + // We could output corrupted packets here, in case the + // MPEG decoder can use them somehow. + //out.written(1); + } + } + } + private: + u8 pattern[188*8], *pattern_end, *pos; + pipereader in; + pipewriter out; + }; + +} // namespace + +#endif // LEANSDR_DVB_H diff --git a/src/leansdr/framework.h b/src/leansdr/framework.h new file mode 100644 index 0000000..8f2f3e4 --- /dev/null +++ b/src/leansdr/framework.h @@ -0,0 +1,217 @@ +#ifndef LEANSDR_FRAMEWORK_H +#define LEANSDR_FRAMEWORK_H + +#include +#include +#include +#include + +namespace leansdr { + + void fatal(const char *s) { perror(s); exit(1); } + void fail(const char *s) { fprintf(stderr, "** %s\n", s); exit(1); } + + ////////////////////////////////////////////////////////////////////// + // DSP framework + ////////////////////////////////////////////////////////////////////// + + // [pipebuf] is a FIFO buffer with multiple readers. + // [pipewriter] is a client-side hook for writing into a [pipebuf]. + // [pipereader] is a client-side hook reading from a [pipebuf]. + // [runnable] is anything that moves data between [pipebufs]. + // [scheduler] is a global context which invokes [runnables] until fixpoint. + + static const int MAX_PIPES = 64; + static const int MAX_RUNNABLES = 64; + static const int MAX_READERS = 8; + + struct pipebuf_common { + virtual int sizeofT() { return 0; } + virtual long long hash() { return 0; } + virtual void dump(size_t *total_bufs) { } + const char *name; + pipebuf_common(const char *_name) : name(_name) { } + }; + + struct runnable_common { + const char *name; + runnable_common(const char *_name) : name(_name) { } + virtual void run() { } + }; + + struct window_placement { + const char *name; // NULL to terminate + int x, y, w, h; + }; + + struct scheduler { + pipebuf_common *pipes[MAX_PIPES]; + int npipes; + runnable_common *runnables[MAX_RUNNABLES]; + int nrunnables; + window_placement *windows; + bool verbose, debug; + + scheduler() + : npipes(0), nrunnables(0), windows(NULL), + verbose(false), debug(false) { + } + void add_pipe(pipebuf_common *p) { + if ( npipes == MAX_PIPES ) fail("MAX_PIPES"); + pipes[npipes++] = p; + } + void add_runnable(runnable_common *r) { + if ( nrunnables == MAX_RUNNABLES ) fail("MAX_RUNNABLES"); + runnables[nrunnables++] = r; + } + void step() { + for ( int i=0; irun(); + } + void run() { + unsigned long long prev_hash = 0; + while ( 1 ) { + step(); + unsigned long long h = hash(); + if ( h == prev_hash ) break; + prev_hash = h; + } + } + + unsigned long long hash() { + unsigned long long h = 0; + for ( int i=0; ihash(); + return h; + } + + void dump() { + fprintf(stderr, "\n"); + size_t total_bufs = 0; + for ( int i=0; idump(&total_bufs); + fprintf(stderr, "Total buffer memory: %ld KiB\n", + (unsigned long)total_bufs/1024); + } + }; + + struct runnable : runnable_common { + runnable(scheduler *_sch, const char *name) + : runnable_common(name), sch(_sch) { + sch->add_runnable(this); + } + protected: + scheduler *sch; + }; + + template + struct pipebuf : pipebuf_common { + T *buf; + T *rds[MAX_READERS]; + int nrd; + T *wr; + T *end; + int sizeofT() { return sizeof(T); } + pipebuf(scheduler *sch, const char *name, unsigned long size) + : pipebuf_common(name), + buf(new T[size]), nrd(0), wr(buf), end(buf+size), + min_write(1), + total_written(0), total_read(0) { + sch->add_pipe(this); + } + int add_reader() { + if ( nrd == MAX_READERS ) fail("too many readers"); + rds[nrd] = wr; + return nrd++; + } + void pack() { + T *rd = wr; + for ( int i=0; i + struct pipewriter { + pipebuf &buf; + pipewriter(pipebuf &_buf, unsigned long min_write=1) + : buf(_buf) { + if ( min_write > buf.min_write ) buf.min_write = min_write; + } + // Return number of items writable at this->wr, 0 if full. + unsigned long writable() { + if ( buf.end-buf.wr < buf.min_write ) buf.pack(); + return buf.end - buf.wr; + } + T *wr() { return buf.wr; } + void written(unsigned long n) { + if ( buf.wr+n > buf.end ) fail("Bug: overflow"); + buf.wr += n; + buf.total_written += n; + } + }; + + template + struct pipereader { + pipebuf &buf; + int id; + pipereader(pipebuf &_buf) : buf(_buf), id(_buf.add_reader()) { } + unsigned long readable() { return buf.wr - buf.rds[id]; } + T *rd() { return buf.rds[id]; } + void read(unsigned long n) { + if ( buf.rds[id]+n > buf.wr ) fail("Bug: underflow"); + buf.rds[id] += n; + buf.total_read += n; + } + }; + + // Math functions for templates + + template T gen_sqrt(T x); + inline float gen_sqrt(float x) { return sqrtf(x); } + inline unsigned int gen_sqrt(unsigned int x) { return sqrtl(x); } + inline long double gen_sqrt(long double x) { return sqrtl(x); } + + template T gen_abs(T x); + inline float gen_abs(float x) { return fabsf(x); } + inline int gen_abs(int x) { return abs(x); } + inline long int gen_abs(long int x) { return labs(x); } + + template T gen_hypot(T x, T y); + inline float gen_hypot(float x, float y) { return hypotf(x,y); } + inline long double gen_hypot(long double x, long double y) + { return hypotl(x,y); } + + template T gen_atan2(T y, T x); + inline float gen_atan2(float y, float x) { return atan2f(y,x); } + inline long double gen_atan2(long double y, long double x) + { return atan2l(y,x); } + +} // namespace + +#endif // LEANSDR_FRAMEWORK_H diff --git a/src/leansdr/generic.h b/src/leansdr/generic.h new file mode 100644 index 0000000..f23de61 --- /dev/null +++ b/src/leansdr/generic.h @@ -0,0 +1,122 @@ +#ifndef LEANSDR_GENERIC_H +#define LEANSDR_GENERIC_H + +#include +#include + +namespace leansdr { + +////////////////////////////////////////////////////////////////////// +// Simple blocks +////////////////////////////////////////////////////////////////////// + +// [file_reader] reads raw data from a file descriptor into a [pipebuf]. +// If the file descriptor is seekable, data can be looped. + +template +struct file_reader : runnable { + file_reader(scheduler *sch, int _fdin, pipebuf &_out) + : runnable(sch, _out.name), + loop(false), + fdin(_fdin), out(_out), + pos(0) { + } + void run() { + size_t size = out.writable() * sizeof(T); + if ( ! size ) return; + again: + ssize_t nr = read(fdin, out.wr(), size); + if ( nr < 0 ) fatal("read"); + if ( !nr && !loop ) return; + if ( ! nr ) { + if ( sch->debug ) fprintf(stderr, "%s looping\n", name); + off_t res = lseek(fdin, 0, SEEK_SET); + if ( res == (off_t)-1 ) fatal("lseek"); + goto again; + } + if ( nr % sizeof(T) ) fatal("partial read"); + out.written(nr / sizeof(T)); + } + bool loop; +private: + int fdin; + pipewriter out; + off_t pos; +}; + +// [file_writer] writes raw data from a [pipebuf] to a file descriptor. + +template +struct file_writer : runnable { + file_writer(scheduler *sch, pipebuf &_in, int _fdout) : + runnable(sch, _in.name), + in(_in), fdout(_fdout) { + } + void run() { + int size = in.readable() * sizeof(T); + if ( ! size ) return; + int nw = write(fdout, in.rd(), size); + if ( ! nw ) fatal("pipe"); + if ( nw < 0 ) fatal("write"); + if ( nw % sizeof(T) ) fatal("partial write"); + in.read(nw/sizeof(T)); + } +private: + pipereader in; + int fdout; +}; + +// [file_printer] writes data from a [pipebuf] to a file descriptor, +// with printf-style formatting and optional scaling. + +template +struct file_printer : runnable { + file_printer(scheduler *sch, const char *_format, + pipebuf &_in, int _fdout) : + runnable(sch, _in.name), + scale(1), in(_in), format(_format), fdout(_fdout) { + } + void run() { + int n = in.readable(); + T *pin=in.rd(), *pend=pin+n; + for ( ; pin in; + const char *format; + int fdout; +}; + +// [itemcounter] writes the number of input items to the output [pipebuf]. +// [Tout] must be a numeric type. + +template +struct itemcounter : runnable { + itemcounter(scheduler *sch, pipebuf &_in, pipebuf &_out) + : runnable(sch, "itemcounter"), + in(_in), out(_out) { + } + void run() { + if ( out.writable() < 1 ) return; + unsigned long count = in.readable(); + if ( ! count ) return; + *out.wr() = count; + in.read(count); + out.written(1); + } +private: + pipereader in; + pipewriter out; +}; + +} // namespace + +#endif // LEANSDR_GENERIC_H diff --git a/src/leansdr/gui.h b/src/leansdr/gui.h new file mode 100644 index 0000000..9b20256 --- /dev/null +++ b/src/leansdr/gui.h @@ -0,0 +1,471 @@ +#ifndef LEANSDR_GUI_H +#define LEANSDR_GUI_H + +#include + +#include "framework.h" + +namespace leansdr { + + ////////////////////////////////////////////////////////////////////// + // GUI blocks + ////////////////////////////////////////////////////////////////////// + +#ifdef GUI + +#include +#include +#include + + static const int DEFAULT_GUI_DECIMATION = 64; + + struct gfx { + Display *display; + int screen; + int w, h; + Window window; + GC gc; + Pixmap dbuf; + gfx(scheduler *sch, const char *name) { + window_placement *wp; + for ( wp=sch->windows; wp->name; ++wp ) + if ( ! strcmp(wp->name, name) ) break; + if ( wp->name ) + init(wp->name, wp->x, wp->y, wp->w, wp->h); + else { + fprintf(stderr, "No placement hints for window '%s'\n", name); + init(name, -1, -1, 320, 240); + } + } + gfx(const char *name, int _x, int _y, int _w, int _h) { + init(name, _x, _y, _w, _h); + } + void init(const char *name, int _x, int _y, int _w, int _h) { + buttons = 0; + clicks = 0; + mmoved = false; + w = _w; + h = _h; + display = XOpenDisplay(getenv("DISPLAY")); + if ( ! display ) fatal("display"); + screen = DefaultScreen(display); + XSetWindowAttributes xswa; + xswa.event_mask = (ExposureMask| + StructureNotifyMask| + ButtonPressMask| + ButtonReleaseMask| + KeyPressMask| + KeyReleaseMask| + PointerMotionMask); + xswa.background_pixel = BlackPixel(display, screen); + window = XCreateWindow(display, DefaultRootWindow(display), + 100,100, w,h, 10, CopyFromParent,InputOutput, + CopyFromParent, CWEventMask|CWBackPixel, + &xswa); + if ( !window ) fatal("window"); + XStoreName(display, window, name); + XMapWindow(display, window); + if ( _x>=0 && _y>=0 ) + XMoveWindow(display, window, _x, _y); + dbuf = XCreatePixmap(display, window, w, h, DefaultDepth(display,screen)); + gc = XCreateGC(display, dbuf, 0, NULL); + if ( ! gc ) fatal("gc"); + } + void clear() { + setfg(0, 0, 0); + XFillRectangle(display, dbuf, gc, 0, 0, w, h); + } + void show() { + XCopyArea(display, dbuf, window, gc, 0, 0, w, h, 0, 0); + } + void sync() { + XSync(display, False); + } + void events() { + XEvent ev; + while ( XCheckWindowEvent(display, window, -1, &ev) ) { + switch ( ev.type ) { + case ButtonPress: { + int b = ev.xbutton.button; + buttons |= 1< + struct cscope : runnable { + T xymin, xymax; + unsigned long decimation; + unsigned long pixels_per_frame; + cscope(scheduler *sch, pipebuf< complex > &_in, T _xymin, T _xymax, + const char *_name=NULL) + : runnable(sch, _name?_name:_in.name), + xymin(_xymin), xymax(_xymax), + decimation(DEFAULT_GUI_DECIMATION), pixels_per_frame(1024), + in(_in), phase(0), g(sch, name) { + } + void run() { + while ( in.readable() >= pixels_per_frame ) { + if ( ! phase ) { + draw_begin(); + g.setfg(0, 255, 0); + complex *p = in.rd(), *pend = p+pixels_per_frame; + for ( ; pre-xymin)/(xymax-xymin), + g.h - g.h*(p->im-xymin)/(xymax-xymin)); + g.show(); + g.sync(); + } + in.read(pixels_per_frame); + if ( ++phase >= decimation ) phase = 0; + } + } + //private: + pipereader< complex > in; + unsigned long phase; + gfx g; + void draw_begin() { + g.clear(); + g.setfg(0, 255, 0); + g.line(g.w/2,0, g.w/2, g.h); + g.line(0,g.h/2, g.w,g.h/2); + } + }; + + template + struct wavescope : runnable { + T ymin, ymax; + unsigned long decimation; + wavescope(scheduler *sch, pipebuf &_in, + T _ymin, T _ymax, const char *_name=NULL) + : runnable(sch, _name?_name:_in.name), + in(_in), ymin(_ymin), ymax(_ymax), + decimation(DEFAULT_GUI_DECIMATION), + g(sch, name), phase(0), + x(0) { + g.clear(); + } + void run() { + while ( in.readable() >= g.w ) { + if ( ! phase ) plot(in.rd(), g.w); + in.read(g.w); + if ( ++phase >= decimation ) phase = 0; + } + } + void plot(T *p, int count) { + T *pend = p + count; + g.clear(); + g.setfg(0, 255, 0); + for ( int x=0; p in; + int phase; + gfx g; + int x; + }; + + template + struct slowmultiscope : runnable { + struct chanspec { + pipebuf *in; + const char *name, *format; + unsigned char rgb[3]; + float scale; + float ymin, ymax; + enum flag { + DEFAULT = 0, + ASYNC = 1, // Read whatever is available + COUNT = 2, // Display number of items read instead of value + SUM = 4, // Display sum of values + LINE = 8, // Connect points + } flags; + }; + unsigned long samples_per_pixel; + float sample_freq; // Sample rate in Hz (used for cursor operations) + slowmultiscope(scheduler *sch, const chanspec *specs, int _nchans, + const char *_name) + : runnable(sch, _name?_name:"slowmultiscope"), + samples_per_pixel(1), sample_freq(1), + nchans(_nchans), + g(sch, name), t(0), x(0), total_samples(0) { + chans = new channel[nchans]; + for ( int i=0; i(*specs[i].in); + chans[i].accum = 0; + } + g.clear(); + } + void run() { + // Read up to one pixel worth of data + unsigned long count = samples_per_pixel; + for ( channel *c=chans; cspec.flags&chanspec::ASYNC) ) + count = min(count, c->in->readable()); + for ( int n=count; n--; ) { + for ( channel *c=chans; cspec.flags & chanspec::ASYNC ) + // For async channels, read any and all available data. + nr = c->in->readable(); + else + nr = 1; + g.setfg(c->spec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); + int y = -1; + while ( nr-- ) { + float v = *c->in->rd() * c->spec.scale; + if ( c->spec.flags & chanspec::COUNT ) + ++c->accum; + else if ( c->spec.flags & chanspec::SUM ) + c->accum += v; + else { + c->print_val = v; + y = g.h - g.h*(v-c->spec.ymin)/(c->spec.ymax-c->spec.ymin); + } + c->in->read(1); + } + // Display count/sum channels only when the cursor is about to move. + if ( (c->spec.flags&(chanspec::COUNT|chanspec::SUM)) && + t+1 >= samples_per_pixel ) { + T v = c->accum; + y = g.h-1 - g.h*(v-c->spec.ymin)/(c->spec.ymax-c->spec.ymin); + c->accum = 0; + c->print_val = v; + } + if ( y >= 0 ) { + if ( c->spec.flags & chanspec::LINE ) { + if ( x ) g.line(x-1, c->prev_y, x, y); + c->prev_y = y; + } else + g.point(x, y); + } + } + g.show(); + // Print instantatenous values as text + for ( int i=0; ispec.rgb[0], c->spec.rgb[1], c->spec.rgb[2]); + char text[256]; + sprintf(text, c->spec.format, c->print_val); + g.transient_text(5, 20+16*i, text); + } + run_gui(); + if ( ++t >= samples_per_pixel ) { + t = 0; + ++x; + if ( x >= g.w ) x = 0; + g.setfg(0, 0, 0); + g.line(x, 0, x, g.h-1); + } + run_gui(); + g.sync(); + } + total_samples += count; + } + void run_gui() { + g.events(); + // Print cursor time + float ct = g.mx * samples_per_pixel / sample_freq; + float tt = total_samples / sample_freq; + char text[256]; + sprintf(text, "%.3f / %.3f s", ct, tt); + g.setfg(255, 255, 255); + g.transient_text(g.w*3/4, 20, text); + } + private: + int nchans; + struct channel { + chanspec spec; + pipereader *in; + float accum; + int prev_y; + float print_val; + } *chans; + gfx g; + unsigned long t; + int x; + int total_samples; + }; + + template + struct spectrumscope : runnable { + T ymax; + float amax; + unsigned long size; + unsigned long decimation; + spectrumscope(scheduler *sch, pipebuf< complex > & _in, + T _max, const char *_name=NULL) + : runnable(sch, _name?_name:_in.name), + ymax(_max), amax(_max), + size(4096), decimation(DEFAULT_GUI_DECIMATION), + in(_in), phase(0), g(sch, name), fft(NULL) { + } + void run() { + while ( in.readable() >= size ) { + if ( ! phase ) do_fft(in.rd()); + in.read(size); + if ( ++phase >= decimation ) phase = 0; + } + } + private: + pipereader< complex > in; + int phase; + gfx g; + cfft_engine *fft; + void do_fft(complex *input) { + draw_begin(); + if ( !fft || fft->n!=size ) { + if ( fft ) delete fft; + fft = new cfft_engine(size); + } + complex *pin=input, *pend=pin+size; + complex data[size], *pout=data; + g.setfg(255, 0, 0); + for ( int x=0; pinre = (float)pin->re; + pout->im = (float)pin->im; + // g.point(x, g.h/2-pout->re*g.h/2/ymax); + } + fft->inplace(data, true); + g.setfg(0, 255, 0); + for ( int i=0; i v = data[i];; + float y = hypot(v.re, v.im); + g.line(x, g.h-1, x, g.h-1-y*g.h/amax); + } + if ( g.buttons ) { + char s[256]; + float f = 2.4e6 * (g.mx-g.w/2) / g.w; + sprintf(s, "%f", f); + g.text(16, 16, s); + } + g.show(); + g.sync(); + } + void draw_begin() { + g.clear(); + g.setfg(255, 255, 255); + g.line(g.w/2,0, g.w/2,g.h); + } + }; + + template + struct genscope : runnable { + struct render { + int x, y; + char dir; // 'h'orizontal or 'v'ertical + }; + struct chanspec { + pipebuf *in; // NULL if disabled + render r; + }; + genscope(scheduler *sch, chanspec *specs, int _nchans, + const char *_name=NULL) + : runnable(sch, _name?_name:"genscope"), + nchans(_nchans), + g(sch, name) { + chans = new channel[nchans]; + for ( int i=0; i(*specs[i].in); + } + } + g.clear(); + gettimeofday(&tv, NULL); + } + struct channel { + chanspec spec; + pipereader *in; + } *chans; + int nchans; + struct timeval tv; + void run() { + g.setfg(0, 255, 0); + for ( channel *pc=chans; pcin ) continue; + int n = pc->in->readable(); + T last = pc->in->rd()[n-1]; + pc->in->read(n); + int dx = pc->spec.r.dir=='h' ? last : 0; + int dy = pc->spec.r.dir=='v' ? last : 0; + dx /= 3; + dy /= 3; + g.line(pc->spec.r.x-dx, pc->spec.r.y-dy, + pc->spec.r.x+dx, pc->spec.r.y+dy); + char txt[16]; + sprintf(txt, "%d", (int)last); + g.text(pc->spec.r.x+5, pc->spec.r.y-2, txt); + } + struct timeval newtv; + gettimeofday(&newtv, NULL); + int dt = (newtv.tv_sec-tv.tv_sec)*1000 + (newtv.tv_usec-tv.tv_usec)/1000; + if ( dt > 100 ) { + fprintf(stderr, "#"); + g.show(); + g.sync(); + g.clear(); + tv = newtv; + } + } + private: + gfx g; + }; + +#endif // GUI + +} // namespace + +#endif // LEANSDR_GUI_H diff --git a/src/leansdr/rs.h b/src/leansdr/rs.h new file mode 100644 index 0000000..ca46559 --- /dev/null +++ b/src/leansdr/rs.h @@ -0,0 +1,264 @@ +#ifndef LEANSDR_RS_H +#define LEANSDR_RS_H + +namespace leansdr { + + // Finite group GF(2^N). + + // GF(2) is the ring ({0,1},+,*). + // GF(2)[X] is the ring of polynomials with coefficients in GF(2). + // P(X) is an irreducible polynomial of GF(2)[X]. + // N is the degree of P(x). + // P is the bitfield representation of P(X), with degree 0 at LSB. + // GF(2)[X]/(P) is GF(2)[X] modulo P(X). + // (GF(2)[X]/(P), +) is a group with 2^N elements. + // (GF(2)[X]/(P)*, *) is a group with 2^N-1 elements. + // (GF(2)[X]/(P), +, *) is a field with 2^N elements, noted GF(2^N). + // Te is a C++ integer type for representing elements of GF(2^N). + // "0" is 0 + // "1" is 1 + // "2" is X + // "3" is X+1 + // "4" is X^2 + // Tp is a C++ integer type for representing P(X) (1 bit larger than Te). + // ALPHA is a primitive element of GF(2^N). Usually "2"=[X] is chosen. + + template + struct gf2x_p { + gf2x_p() { + if ( ALPHA != 2 ) fail("alpha!=2 not implemented"); + // Precompute log and exp tables. + Tp alpha_i = 1; + for ( Tp i=0; i<(1< gf; + + // RS-encoded messages are interpreted as coefficients in + // GF(256) of a polynomial P. + // The syndromes are synd[i] = P(alpha^i). + // By convention coefficients are listed by decreasing degree here, + // so we can evaluate syndromes of the shortened code without + // prepending with 51 zeroes. + bool syndromes(const u8 *poly, u8 *synd) { + bool corrupted = false; + for ( int i=0; i<16; ++i ) { + synd[i] = eval_poly_rev(poly, 204, gf.exp(i)); + if ( synd[i] ) corrupted = true; + } + return corrupted; + } + u8 eval_poly_rev(const u8 *poly, int n, u8 x) { + // poly[0]*x^(n-1) + .. + poly[n-1]*x^0 with Hörner method. + u8 acc = 0; + for ( int i=0; i=0; --deg ) acc = gf.add(gf.mul(acc,x), poly[deg]); + return acc; + } + + // Try to fix errors in pout[]. + // If pin[] is provided, errors will be fixed in the original + // message too and syndromes will be updated. + +#define DEBUG_RS 0 + + bool correct(u8 synd[16], u8 pout[188], u8 pin[204]=NULL) { + // Berlekamp - Massey + // http://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm#Code_sample + u8 C[16] = { 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; // Max degree is L + u8 B[16] = { 1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; + int L = 0; + int m = 1; + u8 b = 1; + for ( int n=0; n<16; ++n ) { + u8 d = synd[n]; + for ( int i=1; i<=L; ++i ) d ^= gf.mul(C[i], synd[n-i]); + if ( ! d ) { + ++m; + } else if ( 2*L <= n ) { + u8 T[16]; + memcpy(T, C, sizeof(T)); + for ( int i=0; i<16-m; ++i ) + C[m+i] ^= gf.mul(d, gf.mul(gf.inv(b),B[i])); + L = n + 1 - L; + memcpy(B, T, sizeof(B)); + b = d; + m = 1; + } else { + for ( int i=0; i<16-m; ++i ) + C[m+i] ^= gf.mul(d, gf.mul(gf.inv(b),B[i])); + ++m; + } + } + // L is the number of errors + // C of degree L is now the error locator polynomial (Lambda) +#if DEBUG_RS + fprintf(stderr, "[L=%d C=",L); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", C[i]); + fprintf(stderr, "]\n"); + fprintf(stderr, "[S="); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", synd[i]); + fprintf(stderr, "]\n"); +#endif + + // Forney + // http://en.wikipedia.org/wiki/Forney_algorithm (2t=16) + + // Compute Omega + u8 omega[16]; + memset(omega, 0, sizeof(omega)); + // TODO loops + for ( int i=0; i<16; ++i ) + for ( int j=0; j<16; ++j ) + if ( i+j < 16 ) omega[i+j] ^= gf.mul(synd[i], C[j]); +#if DEBUG_RS + fprintf(stderr, "omega="); + for ( int i=0; i<16; ++i ) fprintf(stderr, " %d", omega[i]); + fprintf(stderr, "\n"); +#endif + + // Compute Lambda' + u8 Cprime[15]; + for ( int i=0; i<15; ++i ) + Cprime[i] = (i&1) ? 0 : C[i+1]; +#if DEBUG_RS + fprintf(stderr, "Cprime="); + for ( int i=0; i<15; ++i ) fprintf(stderr, " %d", Cprime[i]); + fprintf(stderr, "\n"); +#endif + + // Find zeroes of C by exhaustive search? + // TODO Chien method + int roots_found = 0; + for ( int i=0; i<255; ++i ) { + u8 r = gf.exp(i); // Candidate root alpha^0..alpha^254 + u8 v = eval_poly(C, L, r); + if ( ! v ) { + // r is a root X_k^-1 of the error locator polynomial. + u8 xk = gf.inv(r); + int loc = (255-i) % 255; // == log(xk) +#if DEBUG_RS + fprintf(stderr, "found root=%d, inv=%d, loc=%d\n", r, xk, loc); +#endif + if ( loc < 204 ) { + // Evaluate e_k + u8 num = gf.mul(xk, eval_poly(omega, L, r)); + u8 den = eval_poly(Cprime, 14, r); + u8 e = gf.div(num, den); + // Subtract e from coefficient of degree loc. + // Note: Coeffients listed by decreasing degree in pin[] and pout[]. + if ( loc >= 16 ) pout[203-loc] ^= e; + if ( pin ) pin[203-loc] ^= e; + } + if ( ++roots_found == L ) break; + } + } + if ( pin ) + return syndromes(pin, synd); + else + return false; + } + + }; + + template + struct rs_decoder : runnable { + rs_engine rs; + rs_decoder(scheduler *sch, + pipebuf< rspacket > &_in, + pipebuf &_out) + : runnable(sch, "RS decoder"), + in(_in), out(_out) { + } + void run() { + while ( in.readable()>=1 && out.writable()>=1 ) { + Tbyte *pin = in.rd()->data; + u8 *pout = out.wr()->data; + + // The message is the first 188 bytes. + if ( sizeof(Tbyte) == 1 ) + memcpy(pout, pin, SIZE_TSPACKET); + else + fail("Erasures not implemented"); + + u8 synd[16]; + bool corrupted = rs.syndromes(pin, synd); + +#if 0 + if ( ! corrupted ) { + // Test BM + fprintf(stderr, "Simulating errors\n"); + pin[203] ^= 42; + pin[202] ^= 99; + corrupted = rs.syndromes(pin, synd); + } +#endif + if ( ! corrupted ) { + if ( sch->debug ) + fprintf(stderr, "_"); // Packet received without errors. + } else { + corrupted = rs.correct(synd, pout, pin); + if ( sch->debug ) { + if ( ! corrupted ) + fprintf(stderr, "."); // Errors were corrected. + else + fprintf(stderr, "!"); // Packet still corrupted. + } + } + + in.read(1); + + // Output corrupted packets (with a special mark) + // otherwise the derandomizer will lose synchronization. + if ( corrupted ) pout[0] ^= MPEG_SYNC_CORRUPTED; + out.written(1); + } + } + private: + pipereader< rspacket > in; + pipewriter out; + }; + +} // namespace + +#endif // LEANSDR_RS_H diff --git a/src/leansdr/sdr.h b/src/leansdr/sdr.h new file mode 100644 index 0000000..f59e671 --- /dev/null +++ b/src/leansdr/sdr.h @@ -0,0 +1,480 @@ +#ifndef LEANSDR_SDR_H +#define LEANSDR_SDR_H + +namespace leansdr { + + ////////////////////////////////////////////////////////////////////// + // SDR blocks + ////////////////////////////////////////////////////////////////////// + + typedef unsigned char u8; + typedef unsigned short u16; + typedef signed char s8; + typedef float f32; + + typedef complex cf32; + typedef complex iqsymbol; + typedef complex cu8; + typedef complex cs8; + + template + struct auto_notch : runnable { + int decimation; + float k; + auto_notch(scheduler *sch, pipebuf< complex > &_in, + pipebuf< complex > &_out, int _nslots, + T _agc_rms_setpoint) + : runnable(sch, "auto_notch"), + decimation(1024), k(0.002), // k(0.01) + fft(4096), + in(_in), out(_out,fft.n), + nslots(_nslots), slots(new slot[nslots]), + phase(0), gain(1), agc_rms_setpoint(_agc_rms_setpoint) { + for ( int s=0; s[fft.n]; + } + } + void run() { + while ( in.readable()>=fft.n && out.writable()>=fft.n ) { + if ( ! phase ) detect(); + if ( ++phase >= decimation ) phase = 0; + process(); + in.read(fft.n); + out.written(fft.n); + } + } + void detect() { + complex *pin = in.rd(); + complex data[fft.n]; + float m0=0, m2=0; + for ( int i=0; i m0 ) m0 = gen_abs(pin[i].re); + if ( gen_abs(pin[i].im) > m0 ) m0 = gen_abs(pin[i].im); + } + if ( agc_rms_setpoint && m2 ) { + float rms = gen_sqrt(m2/fft.n); + if ( sch->debug ) fprintf(stderr, "(pow %f max %f)", rms, m0); + float new_gain = agc_rms_setpoint / rms; + gain = gain*0.9 + new_gain*0.1; + } + fft.inplace(data, true); + float amp[fft.n]; + for ( int i=0; i amp[iamax] ) iamax=i; + if ( iamax != s->i ) { + if ( sch->debug ) + fprintf(stderr, "%s: slot %d new peak %d -> %d\n", + name, (int)(s-slots), s->i, iamax); + s->i = iamax; + s->estim.re = 0; + s->estim.im = 0; + s->estt = 0; + for ( int i=0; ii * i / fft.n; + s->expj[i].re = cosf(a); + s->expj[i].im = sinf(a); + } + } + amp[iamax] = 0; + if ( iamax-1 >= 0 ) amp[iamax-1] = 0; + if ( iamax+1 < fft.n ) amp[iamax+1] = 0; + } + } + void process() { + complex *pin=in.rd(), *pend=pin+fft.n, *pout=out.wr(); + for ( slot *s=slots; sej = s->expj; + for ( ; pin out = *pin; + // TODO Optimize for nslots==1 ? + for ( slot *s=slots; sej,++s ) { + complex bb(pin->re*s->ej->re + pin->im*s->ej->im, + -pin->re*s->ej->im + pin->im*s->ej->re); + s->estim.re = bb.re*k + s->estim.re*(1-k); + s->estim.im = bb.im*k + s->estim.im*(1-k); + complex sub(s->estim.re*s->ej->re - s->estim.im*s->ej->im, + s->estim.re*s->ej->im + s->estim.im*s->ej->re); + out.re -= sub.re; + out.im -= sub.im; + } + pout->re = gain * out.re; + pout->im = gain * out.im; + } + } + + private: + cfft_engine fft; + pipereader< complex > in; + pipewriter< complex > out; + int nslots; + struct slot { + int i; + complex estim; + complex *expj, *ej; + int estt; + } *slots; + int phase; + float gain; + T agc_rms_setpoint; + }; + + template + struct ss_estimator : runnable { + unsigned long window_size; // Samples per estimation + unsigned long decimation; // Output rate + ss_estimator(scheduler *sch, pipebuf< complex > &_in, pipebuf &_out) + : runnable(sch, "SS estimator"), + window_size(1024), decimation(1), + in(_in), out(_out), + phase(0) { + } + void run() { + while ( in.readable()>=window_size && out.writable()>=1 ) { + if ( ! phase ) { + complex *p=in.rd(), *pend=p+window_size; + float s = 0; + for ( ; pre*p->re + (float)p->im*p->im; + *out.wr() = sqrtf(s/window_size); + out.written(1); + } + in.read(window_size); + if ( ++phase >= decimation ) phase = 0; + } + } + private: + pipereader< complex > in; + pipewriter out; + unsigned long phase; + }; + + typedef unsigned short u_angle; // [0,2PI[ in 65536 steps + typedef signed short s_angle; // [-PI,PI[ in 65536 steps + + // GENERIC CONSTELLATION DECODING BY LOOK-UP TABLE. + // R must be a power of 2. + // Up to 256 symbols. + + struct softsymbol { + unsigned char symbol; // 000000IQ for QPSK + unsigned char metric; + }; + + const float cstln_amp = 64; + + template + struct cstln_lut { + complex *symbols; + enum predef { QPSK }; + cstln_lut(predef type) { + signed char a; + switch ( type ) { + case QPSK: + a = cstln_amp * sqrtf(2)/2; + symbols = new complex[4]; + symbols[0].re = a; symbols[0].im = a; + symbols[1].re = a; symbols[1].im = -a; + symbols[2].re = -a; symbols[2].im = a; + symbols[3].re = -a; symbols[3].im = -a; + make_lut_from_symbols(4); + break; + // TBD: BPSK, 8PSK, 16QAM, 16APSK, 32APSK + default: + fail("Constellation not implemented"); + } + } + struct result { + struct softsymbol ss; + s_angle phase_error; + }; + inline result *lookup(int I, int Q) { + return &lut[(unsigned char)I][(unsigned char)Q]; + } + private: + result lut[R][R]; + void make_lut_from_symbols(int M) { + for ( int I=-R/2; I 255 ) fail("dmin overflow"); + pr->ss.symbol = smin; + pr->ss.metric = dmin; + //pr->ss.metric = 255 * (int)dmin / dmin2; + pr->phase_error = ph_err * 65536 / (2*M_PI); + } + } + }; + + // CONSTELLATION RECEIVER + + template + struct cstln_receiver : runnable { + cstln_lut<256> *cstln; + unsigned long meas_decimation; // Measurement rate + float omega, min_omega, max_omega; // Samples per symbol + u_angle freqw, min_freqw, max_freqw; // Freq offset in angle per sample + static const unsigned int chunk_size = 128; + float kest; + + cstln_receiver(scheduler *sch, + pipebuf< complex > &_in, + pipebuf &_out, + pipebuf *_freq_out=NULL, + pipebuf *_ss_out=NULL, + pipebuf *_mer_out=NULL, + pipebuf *_cstln_out=NULL) + : runnable(sch, "Constellation receiver"), + cstln(NULL), + meas_decimation(1048576), + kest(0.01), + in(_in), out(_out, chunk_size), + est_insp(cstln_amp*cstln_amp), agc_gain(1), + mu(0), phase(0), + est_sp(0), est_ep(0), + meas_count(0) { + set_omega(1); + set_freq(0); + freq_out = _freq_out ? new pipewriter(*_freq_out) : NULL; + ss_out = _ss_out ? new pipewriter(*_ss_out) : NULL; + mer_out = _mer_out ? new pipewriter(*_mer_out) : NULL; + cstln_out = _cstln_out ? new pipewriter(*_cstln_out) : NULL; + memset(hist, 0, sizeof(hist)); + init_trig_tables(); + } + + void set_omega(float _omega, float tol=10e-6) { + omega = _omega; + min_omega = omega * (1-tol); + max_omega = omega * (1+tol); + } + + void set_freq(float freq) { + freqw = freq * 65536; + min_freqw = freqw - 65536/8; + max_freqw = freqw + 65536/8; + } + + void run() { + if ( ! cstln ) fail("constellation not set"); + + // Magic constants that work with the qa recordings. + const signed long freq_alpha = 0.04 * 65536; + const signed long freq_beta = 0.001 * 65536; + float gain_mu = 0.02 / (cstln_amp*cstln_amp) * 2; + + int max_meas = chunk_size/meas_decimation + 1; + while ( in.readable() >= chunk_size && + out.writable() >= chunk_size/min_omega+1 && + ( !freq_out || freq_out ->writable()>=max_meas ) && + ( !ss_out || ss_out ->writable()>=max_meas ) && + ( !mer_out || mer_out ->writable()>=max_meas ) && + ( !cstln_out || cstln_out->writable()>=max_meas ) ) { + + complex *pin=in.rd(), *pin0=pin, *pend=pin+chunk_size; + softsymbol *pout=out.wr(), *pout0=pout; + + // These are scoped outside the loop for SS and MER estimation. + complex s; // For MER estimation and constellation viewer + complex *cstln_point = NULL; + + while ( pin < pend ) { + // Here mu is the time of the next symbol counted from 0 at pin. + if ( mu < 1 ) { + // Here 0<=mu<1 is the fractional time of the next symbol + // between pin and pin+1. + + // Derotate pin[0] and pin[1] + float cosph, sinph; + cosph = fastcos(-phase); + sinph = fastsin(-phase); + complex s0(pin[0].re*cosph - pin[0].im*sinph, + pin[0].re*sinph + pin[0].im*cosph); + cosph = fastcos(-(phase+freqw)); + sinph = fastsin(-(phase+freqw)); + complex s1(pin[1].re*cosph - pin[1].im*sinph, + pin[1].re*sinph + pin[1].im*cosph); + + // Interpolate linearly + float cmu = 1 - mu; + s.re = (s0.re*cmu + s1.re*mu) * agc_gain; + s.im = (s0.im*cmu + s1.im*mu) * agc_gain; + + // Constellation look-up + cstln_lut<256>::result *cr = cstln->lookup(s.re, s.im); + *pout = cr->ss; + ++pout; + + // PLL +#if 0 + signed short c1 = (cr->phase_error * freq_alpha + 1) >> 16; + signed short c2 = (cr->phase_error * freq_alpha) / 65536; + // if ( c1 != c2 ) fprintf(stderr, "\n### %d %d %d\n", cr->phase_error, c1, c2); + phase += (cr->phase_error * freq_alpha) / 65536; + freqw += (cr->phase_error * freq_beta) / 65536; +#else + phase += (cr->phase_error * freq_alpha + 32768) >> 16; + freqw += (cr->phase_error * freq_beta + 32768) >> 16; +#endif + + // Modified Mueller and Müller + // mu[k]=real(c[k]-c[k-2])*conj(p[k-1])-(p[k]-p[k-2])*conj(c[k-1])) + // =dot(c[k]-c[k-2],p[k-1]) - dot(p[k]-p[k-2],c[k-1]) + // p = received signals + // c = decisions (constellation points) + hist[2] = hist[1]; + hist[1] = hist[0]; + hist[0].p.re = s.re; + hist[0].p.im = s.im; + cstln_point = &cstln->symbols[cr->ss.symbol]; + hist[0].c.re = cstln_point->re; + hist[0].c.im = cstln_point->im; + float muerr = + ( (hist[0].p.re-hist[2].p.re)*hist[1].c.re + + (hist[0].p.im-hist[2].p.im)*hist[1].c.im ) - + ( (hist[0].c.re-hist[2].c.re)*hist[1].p.re + + (hist[0].c.im-hist[2].c.im)*hist[1].p.im ); + float mucorr = muerr * gain_mu; + const float max_mucorr = 0.1; + // TBD Optimize out statically + if ( mucorr < -max_mucorr ) mucorr = -max_mucorr; + if ( mucorr > max_mucorr ) mucorr = max_mucorr; + mu += mucorr; + mu += omega; // Next symbol time; + } // mu<1 + + // Next sample + ++pin; + --mu; + phase += freqw; + } // chunk_size + + in.read(pin-pin0); + out.written(pout-pout0); + + // Output the last interpolated PSK symbol, once per chunk_size + if ( cstln_out ) { + *cstln_out->wr() = s; + cstln_out->written(1); + } + + // AGC + float insp = pin0->re*pin0->re + pin0->im*pin0->im; + est_insp = insp*kest + est_insp*(1-kest); + if ( est_insp ) + agc_gain = cstln_amp / gen_sqrt(est_insp); + + // SS and MER + float sig_power = s.re*s.re+s.im*s.im; + est_sp = sig_power*kest + est_sp*(1-kest); + if ( ! cstln_point ) fatal("No sample"); + complex errvect(s.re-cstln_point->re, s.im-cstln_point->im); + float errvect_power = errvect.re*errvect.re + errvect.im*errvect.im; + est_ep = errvect_power*kest + est_ep*(1-kest); + + // This is best done periodically ouside the inner loop, + // but will cause non-deterministic output. + + if ( (signed short)(freqw-min_freqw)<0 || + (signed short)(max_freqw-freqw)<0 ) + freqw = min_freqw + (unsigned short)(max_freqw-min_freqw) / 2; + + // Output measurements + + meas_count += pin-pin0; + while ( meas_count >= meas_decimation ) { + meas_count -= meas_decimation; + if ( freq_out ) { + *freq_out->wr() = (float)(signed short)freqw / 65536; + freq_out->written(1); + } + if ( ss_out ) { + *ss_out->wr() = sqrtf(est_sp); + ss_out->written(1); + } + if ( mer_out ) { + float mer = est_ep ? 10*logf(est_sp/est_ep)/logf(10) : 0; + *mer_out->wr() = mer; + mer_out->written(1); + } + + } + + } // Work to do + } + + private: + struct { + complex p; // Received symbol + complex c; // Matched constellation point + } hist[3]; + pipereader< complex > in; + pipewriter out; + float est_insp, agc_gain; + float mu; // PSK time expressed in clock ticks + u_angle phase; + float est_sp; // Estimated RMS signal power + float est_ep; // Estimated RMS error vector power + unsigned long meas_count; + pipewriter *freq_out, *ss_out, *mer_out; + pipewriter *cstln_out; + + float lut_cos[65536]; + float fastcos(u_angle a) { return lut_cos[a]; } + float fastsin(u_angle a) { return lut_cos[(u_angle)(a-16384)]; } + + void init_trig_tables() { + for ( int a=0; a<65536; ++a ) + lut_cos[a] = cosf(a*2*M_PI/65536); + } + }; + + // FREQUENCY SHIFTER + // Resolution is sample_freq/65536 + + template + struct rotator : runnable { + rotator(scheduler *sch, pipebuf< complex > &_in, + pipebuf< complex > &_out, float freq) + : runnable(sch, "rotator"), + in(_in), out(_out), index(0) { + int ifreq = freq * 65536; + for ( int i=0; i<65536; ++i ) + lut_cos[i] = cosf(2*M_PI * i * ifreq / 65536); + } + void run() { + unsigned long count = min(in.readable(), out.writable()); + complex *pin = in.rd(), *pend = pin+count; + complex *pout = out.wr(); + for ( ; pinre = pin->re*c - pin->im*s; + pout->im = pin->re*s + pin->im*c; + } + in.read(count); + out.written(count); + } + private: + pipereader< complex > in; + pipewriter< complex > out; + float lut_cos[65536]; + unsigned short index; // Current phase + }; + +} // namespace + +#endif // LEANSDR_SDR_H