Opendigitalradio-ODR-DabMod-f7eedef/000077500000000000000000000000001475762153200174215ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/.gitignore000066400000000000000000000004671475762153200214200ustar00rootroot00000000000000 autom4te.cache *.o stamp-h1 .deps .dirstamp lib/kiss_fft129 **/Makefile Makefile.in aclocal.m4 build-aux configure config.log config.h config.h.in config.status odr-dabmod *~ *.iq __pycache__/ *.py[cod] *$py.class *.so .Python python/gui/logs/access.log python/gui/logs/error.log python/gui/static/dpd/*png Opendigitalradio-ODR-DabMod-f7eedef/.travis.yml000066400000000000000000000042061475762153200215340ustar00rootroot00000000000000language: cpp dist: focal jobs: include: # Clang on OSX - env: MATRIX_EVAL="" CONF="--disable-output-uhd" os: osx osx_image: xcode12.2 compiler: clang - env: MATRIX_EVAL="" CONF="" os: osx osx_image: xcode12.2 compiler: clang # GCC builds on Linux - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--disable-zeromq --disable-output-uhd" os: linux arch: amd64 compiler: gcc addons: &linuxaddons apt: packages: &packages - libzmq3-dev - libzmq5 - automake - libtool - libcurl4-openssl-dev - libfftw3-dev # libuhd-dev is not allowed - g++-10 - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--disable-output-uhd --enable-output-raw" os: linux arch: amd64 compiler: gcc addons: *linuxaddons - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--disable-output-uhd --disable-native" os: linux arch: amd64 compiler: gcc addons: *linuxaddons - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--disable-output-uhd --enable-trace" os: linux arch: amd64 compiler: gcc addons: *linuxaddons # ARM64 builds - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--disable-output-uhd" os: linux arch: arm64 compiler: gcc addons: *linuxaddons - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--disable-output-uhd --disable-native" os: linux arch: arm64 compiler: gcc addons: *linuxaddons # For EasyDAB, which should be an armhf build instead - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--enable-easydabv3 --disable-output-uhd --enable-fast-math --disable-native" os: linux arch: amd64 compiler: gcc addons: *linuxaddons before_install: - eval "${MATRIX_EVAL}" - | if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update brew install automake || true brew install zeromq || true brew install fftw || true brew install uhd || true fi script: - | ./bootstrap.sh ./configure $CONF make Opendigitalradio-ODR-DabMod-f7eedef/AUTHORS000066400000000000000000000015241475762153200204730ustar00rootroot00000000000000Pascal Charest for the Communications Research Centre, Ottawa, Canada, 2003-2010: All files. Matthias P. Braendli - Configuration file support - UHD output integration - Timestamping support required for SFN - FIR filter - Improvements in logging (log to file, to syslog) - ZeroMQ ETI input - Telnet remote-control - ZeroMQ I/Q output - I/Q conversion to signed 8-bit - ARM support - GPSDO monitoring on USRPs - TII - SoapySDR integration - CFR - EDI input Jörgen Scott - ZeroMQ remote control - Static delay offset Sergiy - Improvements for TII insertion Andreas Steger - Digital Predistortion Computation Engine Evariste F5OEO - LimeSDR integration Steven Rossel - BladeRF integration Opendigitalradio-ODR-DabMod-f7eedef/COPYING000066400000000000000000001045131475762153200204600ustar00rootroot00000000000000 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 . Opendigitalradio-ODR-DabMod-f7eedef/ChangeLog000066400000000000000000000330451475762153200212000ustar00rootroot00000000000000This file contains information about the changes done to ODR-DabMod in this repository 2025-02-26: Matthias P. Braendli (v3.0.0): Add support for the PrecisionWave DEXTER Modulator. Improve timestamp decoding and handling. Fix TII carrier levels. Add `showjson` command to RC to support structured output, and add useful metrics to the RC for monitoring, e.g. some Ensemble information from FIC. Remove EasyDABv3 support. Remove ZeroMQ input. Require C++17. v2.7.0 retracted. 2022-04-20: Matthias P. Braendli (v2.6.0): Add timestamps to log output. 2022-02-02: Matthias P. Braendli (v2.5.0): Add support for BladeRF devices. Improve EDI input. Improve compilation on arm devices. Avoid stalling the input when FFTW planner takes too much time. 2021-02-22: Matthias P. Braendli (v2.4.2): Fix some compilation issues for GCC 10 and due to missing includes. Fix external PPS input sync for B200. Properly handle and edge value of TIST. Update common ODR-mmbTools code. 2020-04-06: Matthias P. Braendli (v2.4.1): Fix handling of Ctrl-C. Fix unaligned memory access in ZeroMQ input. Fix incorrect display of "EDI AF Packet sequence error". 2020-03-16: Matthias P. Braendli (v2.4.0): Put more code in common with the other ODR-mmbTools. Rework EDI input, which is now always compiled-in and preferred to ZMQ input (for standards compliance). Add native Lime support. 2019-07-08: Matthias P. Braendli (v2.3.1): Add analog bandwidth setting for UHD and SoapySDR outputs. Implement 10s check for ZMQ input, resetting the socket if no data arrives. Fix compilation against UHD 3.14. Add EDI-over-TCP input. Other minor fixes for GUI and ODR-DabMod. 2019-01-23: Matthias P. Braendli (v2.3.0): Many improvements on GUI and DPDCE: It is now possible to control the predistortion entirely through the web GUI. See `python/README.md` for more. Add SDR temperature to RC and munin. Fix I/Q s16 output format. SoapySDR output: do not set master clock rate if it is 0. Fix warning about feedback server. Fix digital gain being forgotten after an input stream interruption. Handle negative tist offset settings. 2018-08-07: Matthias P. Braendli (v2.2.0): Fix bug in CFR implementation that was introduced in v2.1.0. Make the ZeroMQ list command return JSON. Add a few GPSDO stats to munin. Make sure changes applied through RC stay after an internal modulator reset. Remove the boost dependency. 2018-05-29: Matthias P. Braendli (v2.1.0): Add s16 output format. Add compilation target for the EasyDABv3, which doesn't have boost and requires constellation points output. This required changing the configuration file parser. Avoid RC crash when setting invalid values. 2018-03-21: Matthias P. Braendli (v2.0.0): Big rework of the SDR device handling, putting in common more logic between UHD and SoapySDR. Goal is to enable SoapySDR devices to be used for SFN and for DPD. (Not yet possible) Replace the old timestamp path that went around the process flowgraph by a new metadata path that goes through the flowgraph, to avoid the issues with misalignment of frame and timestamps. Do not mix different versions when using timestamps, and be careful when mixing ODR-DabMod with other transmitters in SFNs, as the timestamp interpretation can change! Fix TII carrier selection. Add OFDM symbol windowing for improved shoulder performance. Add a timeout to the ZeroMQ input to quit on SIGINT (Ctrl-C). Optionally show the flowgraph metadata in the file output for test purposes. Add antenna selection to UHD and SoapySDR outputs. Make EDI input optional (it's not well tested, unfinished and slows compile time) Improve portability for non-Linux POSIX systems. Default to TM 1, do not read from ETI anymore. Add support for UHD 3.11. Implement auto-reconnect for the TCP input. 2017-11-18: Matthias P. Braendli (v1.1.0): Fix TII insertion power level. Fix bug in parsing timestamps appearing in rare conditions. Add Crest Factor Reduction prototype. Cleanup console output of flowgraph statistics. Add prototype for Digital Predistortion. Enable some additional compiler warnings. Add some useful UHD statistics to RC. Add configure option to disable march=native. 2017-06-05: Matthias P. Braendli (v1.0.1): Move GainControl into a separate thread to make better use of multiprocessor systems. Give explicit names to GainControl modes (fix, max, var) in configuration. Add u8 IQ output format. Add a normalised complexf IQ output format. Make resampler unity-gain, i.e. remove digital_gain - sample rate dependency. Read UHD async messages in separate thread to lessen influence on the time-critical transmit thread. Pre-fill UHD IQ buffer before starting to stream data, to ensure it is properly filled and reduce underruns. 2017-03-03: Matthias P. Braendli (v1.0.0): * odr-dabmod: Add ETI-over-TCP input. Add EDI input. Add SoapySDR output, than can drive HackRF, LimeSDR and other boards. Add LO offset to UHD output. Make gainmode VAR the default and improve config syntax. Fix TII insertion. Display FCT and TIST in remote control. Add most commonly used FIRFilter taps into binary and enable the filter by default. Add fast math configure option that can be used to improve performance on ARM platforms as it allows the compiler to use NEON. Improve logging. Refactor internals (flowgraph, main loop, C++11 modernisation and more) 2016-07-30: Matthias P. Braendli (v0.6.0): * odr-dabmod: Remove the broken KISS FFT support. Replace FCT discontinuity check by verification of the timestamps. Change DSP thread priorities to realtime. Quite some internal refactoring. Fix segfault on quit due to incorrect teardown of FFTW. Add Travis CI build instructions. 2015-11-27: Matthias P. Braendli (v0.5.5): * odr-dabmod: Fix compile errors with recent C++ standard library. Correct command line options when using UHD and ZMQ input. Enable ZeroMQ be default in ./configure 2015-09-18: Matthias P. Braendli (v0.5.4) * odr-dabmod: Better GPSDO handling, both for Ettus GPSDO and ODR LEA-M8F board. Add (experimental) TII support. Improve thread priority definition. Simplify handling of TIST offset, and add to RC. Switch project to C++11. 2015-07-24: Matthias P. Braendli (v0.5.3) * odr-dabmod: Replace zmq.hpp to support version 4.1.x 2015-04-10: Matthias P. Braendli (v0.5.2) * odr-dabmod: Merge static delay parameter and ZeroMQ remote control. Add max_frames_queued option for ZeroMQ input. Restart modulator on FCT discontinuity or ZeroMQ input buffer overrun. Improve error messages and documentation. Add ZeroMQ output REP socket type for interconnection with GNURadio. Fix license text in usage view. 2015-01-24: Matthias P. Braendli (v0.5.1) * odr-dabmod: Security: force Telnet RC to listen only on localhost. Add raspine's ZeroMQ RC and UHD staticdelay setting Add I/Q format converter (signed 8-bit) 2014-12-09: Matthias P. Braendli (v0.5.0) * odr-dabmod: Add support for ARM processors. Use FFTW instead of KISS FFT by default. Add a ZeroMQ I/Q output. Support more fine-grained UHD TX gain. Make the -C flag optional to simplify the usage. 2014-09-26: Matthias P. Braendli (v0.4.5) * odr-dabmod: Change UHD normalisation to reduce risk of nonlinearities. Add digital gain setting to RC. Fix handling of timestamps when using ZMQ input, for the SFN scenario. 2014-05-20: Matthias P. Braendli (v0.4.4) * odr-dabmod: Merge patch for USRP1 subdevice setting. Merge patch to correct ZMQ input command line argument. * generate-firfilter.py: Get FIRFilter generator script to work with newer GNURadio versions. 2014-04-04: Matthias P. Braendli (v0.4.3) * odr-dabmod: Various UHD improvements, esp. related to external clock input handling. ZeroMQ input protocol format updated to avoid frame phase alignment errors (not backward compatible) Various cleanup and fixes 2014-03-07: Matthias P. Braendli (v0.4.2) * odr-dabmod: Fix frequency/channel choice Improvements in ZMQ input reliability Use correct syslog identification 2014-02-11: Matthias P. Braendli (v0.4.1) * odr-dabmod: Include zmq.hpp and prefer it over the one provided by the system. Add the channel option to the configuration file for the UHD output. Replace fprintf calls by correct usage of the logging subsystem. Fix some compilation warnings. 2014-02-07: Matthias P. Braendli (v0.4.0) * odr-dabmod: Renamed CRC-DABMOD to ODR-DabMod Fixed version generation Fixed missing files in tarball 2014-01-31: Matthias P. Braendli (tag r11) * crc-dabmod: Minor improvements in logging, remote control, zmq input and autotools 2013-12-14: Matthias P. Braendli (tag r10) * autotools: Remove autogenerated files and add bootstrap Fix faulty logic for --enable-input-zeromq 2013-11-21: Matthias P. Braendli (tag r9) * crc-dabmod: Make generate-filter compatible with newer GNURadio Add the FIRFilter tap generation tool Remove references to mercurial in ChangeLog 2013-11-10: Matthias P. Braendli (tag r8): * crc-dabmod: Added secondary ETI input support. Added ZeroMQ ETI input. Fixed muting issue on missing timestamps, and Added related configuration entry. ZeroMQ dependency added in configure.ac Versioning changed to make hg revision visible Completed READMEs and INSTALLs 2012-09-13: Matthias P. Braendli (tag r7): * crc-dabmod/*: Logging implementation cleaned up, and several related bugs have been solved. Added Boost version check into autoconf * crc-dabmod/src/OutputUHD.*: Use non-deprecated way of setting clock and time for the USRP. 2012-08-26: Matthias P. Braendli (tag r6): * crc-dabmod: RemoteControl compatibility issue 2012-08-23: Matthias P. Braendli (tag r5): * crc-dabmod/*: Minor fixes to FIRFilter and file configuration 2012-08-17: Matthias P. Braendli (tag r4): * crc-dabmod/*: FIRFilter can not be remote-controlled, and supports on-the-fly change of the filter taps. 2012-08-16: Matthias P. Braendli (tag r3): * crc-dabmod/*: Implemented basic telnet remote control that can change some UHD settings, and a syslog-compatible logging system. 2012-08-14: Matthias P. Braendli (tag r2): * crc-dabmod/*: Added .INI configuration file parser. 2012-08-01: Matthias P. Braendli (tag r1): * crc-dabmod/*: It is now possible to run dabmod with OutputUHD even when no external 10MHz reference nor 1PPS are used. When using FIRFilter, the TimestampDecoder keeps one delay value to compensate for the increased pipeline latency of the FIRFilter. As a consequence, it should be possible to synchronise two DABMODs, even if only one uses FIRFilter. 2012-06-04 Pascal Charest * BlockPartitioner.cpp BlockPartitioner.h DabModulator.cpp EtiReader.cpp EtiReader.h: Solved CIF count synchronisation bug. 2012-02-07 Pascal Charest * DabMod.cpp DabModulator.cpp DabModulator.h: Added gain factor to support UHD scale. Added manual DAB mode control. * GainControl.cpp GainControl.h: Added gain factor to support UHD scale. * Makefile.am kiss_fft129.tar.gz: Updated Kiss FFT library to 1.2.9. 2011-12-15 Pascal Charest * configure.ac: Added Wall option to debug compilation. Added mm_malloc macro. * src/BlockPartitioner.h: Changed from codec to mux. * src/Buffer.cpp src/Buffer.h: New buffer management class. * src/DabMod.cpp: Changed from mm_malloc to memalign for Efence support. Replaced all fixed memory size with dynamic Buffer class. Removed FFTW api. * src/DabModulator.cpp src/DabModulator.h: Solved integer overflow bug for CicEqualizer initialisation. * src/GainControl.cpp: Added handling of null symbol when gain would be infinity. * src/InputMemory.h: Solved debug wrong file name issue. * src/kiss_fftsimd.c src/kiss_fftsimd.h: Generic function to pack data for SIMD operations (currently not used). 2011-12-09 Pascal Charest * *: Added support for DAB mode I, III and IV. 2010-07-14 Pascal Charest * configure.ac: Added enable-prof and enable-fft-simd options. * src/GainControl.cpp: Changed to system defined variables. * src/Makefile.am: Removed code about FFTW and KISS_FFT the only FFT library. * src/OfdmGenerator.cpp src/OfdmGenerator.h: Added support for KISS_FFT simd operations. * src/PuncturingEncoder.cpp: Solved a bug for last bits (tail) encoding. * src/Resampler.cpp src/Resampler.h: Added support for KISS_FFT simd operations. Removed unused code. * src/crc-dwap.py: Adapted to latest Gnuradio version. 2010-02-12 Pascal Charest * *: Initial public release Opendigitalradio-ODR-DabMod-f7eedef/INSTALL.md000066400000000000000000000107271475762153200210600ustar00rootroot00000000000000You have 3 ways to install odr-dabmod on your host: # Using your linux distribution packaging system `odr-dabmod` is available on the official repositories of several debian-based distributions, such as Debian (from Debian 12), Ubuntu (from 24.10), Opensuse and Arch. If you are using Debian 12 (Bookworm), you will need to [add the backports repository](https://backports.debian.org/Instructions/) **Notice**: this package does not include the web-based GUI and Digital Predistortion Computation engine # Using installation scripts If your linux distribution is debian-based, you can install odr-dabmod as well as the other main components of the mmbTools set with the [Opendigitalradio dab-scripts](https://github.com/opendigitalradio/dab-scripts.git) # Compiling manually Unlike the 2 previous options, this one allows you to compile odr-dabmod with the features you really need. ## Dependencies ### Debian Bullseye-based OS: ``` # Required packages ## C++11 compiler sudo apt-get install --yes build-essential automake libtool ## FFTW 3.x sudo apt-get install --yes libfftw3-dev # optional packages ## ZeroMQ http://www.zeromq.org sudo apt-get install --yes libzmq3-dev libzmq5 ## UHD for USRP sudo apt-get install --yes libuhd-dev ## LimeSuite for LimeSDR support sudo apt-get install --yes liblimesuite-dev ## SoapySDR (see below) sudo apt-get install --yes libsoapysdr-dev ## bladerf (see below) sudo apt-get install --yes libbladerf-dev ``` ## Compilation 1. Clone this repository: ``` # stable version: git clone https://github.com/Opendigitalradio/ODR-DabMod.git # or development version (at your own risk): git clone https://github.com/Opendigitalradio/ODR-DabMod.git -b next ``` 1. Configure the project ``` cd ODR-DabMod ./bootstrap ./configure ``` 1. Compile and install: ``` make sudo make install ``` ### Configure options The configure script can be launched with a variety of options: - Disable ZeroMQ input (to be used with ODR-DabMod), output and remotecontrol: `--disable-zeromq` - Disable the binding to the UHD driver for USRPs: `--disable-output-uhd` - Compile using the `-ffast-math` option that gives a substantial speedup at the cost of floating point correctness: `--enable-fast-math` - Do not pass `-march=native` to the compiler by using the argument: `--disable-native` **Remark:** Do not compile ODR-DabMod with `-march=native` compiler option. This is meant for distribution package maintainers who want to use their own march option, and for people running into compilation issues due to `-march=native`. (e.g. GCC bug 70132 on ARM systems) **Debugging options:** You should not enable any debug option if you need good performance. Create debugging files for each DSP block for data analysis: `--enable-trace` For more information, call: ``` ./configure --help ``` #### Performance optimisation While the performance of modern systems is good enough in most cases to run ODR-DabMod, it is sometimes necessary to increase the compilation optimisation if all features are used or on slow systems. Tricks for best performance: * Do not use `--disable-native` * Use `--enable-fast-math` * Add `-O3` to compiler flags * Disable assertions with `-DNDEBUG` Applying all together: ``` ./configure CFLAGS="-O3 -DNDEBUG" CXXFLAGS="-O3 -DNDEBUG" --enable-fast-math ``` #### Checking for memory usage issues If your compiler supports it, you can enable the address sanitizer to check for memory issues: ``` ./configure CFLAGS="-fsanitize=address -g -O2" CXXFLAGS="-fsanitize=address -g -O2" ``` The resulting binary will be instrumented with additional memory checks, which have a measurable overhead. Please report if you get warnings or errors when using the sanitizer. ## SoapySDR support and required dependencies SoapySDR is a vendor-neutral library to drive SDR devices. It can be used to drive the HackRF and the LimeSDR among others. Required dependencies that need to be installed are, in order: 1. SoapySDR itself from https://github.com/pothosware/SoapySDR 1. The LimeSuite for the LimeSDR from https://github.com/myriadrf/LimeSuite 1. HackRF support for SoapySDR from https://github.com/pothosware/SoapyHackRF ODR-DabMod will automatically recognise if the SoapySDR library is installed on your system, and will print at the end of `./configure` if support is enabled or not. A configuration example is available in `doc/example.ini` ## BladeRF support In order to use `--enable-bladerf`, you need to install the `libbladerf2` including the -dev package. Opendigitalradio-ODR-DabMod-f7eedef/LICENCE000066400000000000000000000017101475762153200204050ustar00rootroot00000000000000LICENSING ========= Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2013, 2014 Matthias P. Braendli, http://mpb.li This file is part of ODR-DabMod. ODR-DabMod is a fork of CRC-DabMod, which was developed by the Communications Research Center Canada. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . Opendigitalradio-ODR-DabMod-f7eedef/Makefile.am000066400000000000000000000130731475762153200214610ustar00rootroot00000000000000# Copyright (C) 2007, 2008, 2009, 2010 Her Majesty the Queen in Right # of Canada (Communications Research Center Canada) # # Copyright (C) 2023 # Matthias P. Braendli, matthias.braendli@mpb.li # # http://opendigitalradio.org # # This file is part of ODR-DabMod. # # ODR-DabMod 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. # # ODR-DabMod 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 ODR-DabMod. If not, see . ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = COPYING NEWS README.md INSTALL.md AUTHORS ChangeLog TODO doc \ lib/fec/README.md lib/fec/LICENSE \ lib/edi/README.md if IS_GIT_REPO GITVERSION_FLAGS = -DGITVERSION="\"`git describe --dirty`\"" else GITVERSION_FLAGS = endif bin_PROGRAMS = odr-dabmod KISS_FLAGS=-DFIXED_POINT=16 odr_dabmod_CFLAGS = -Wall -Isrc -Ilib -Ikiss \ $(GITVERSION_FLAGS) $(KISS_FLAGS) odr_dabmod_CXXFLAGS = -Wall -Isrc -Ilib -Ikiss \ $(GITVERSION_FLAGS) $(BOOST_CPPFLAGS) $(KISS_FLAGS) odr_dabmod_LDADD = $(BOOST_LDFLAGS) $(BOOST_THREAD_LIB) $(UHD_LIBS) $(LIMESDR_LIBS) $(ADDITIONAL_UHD_LIBS) odr_dabmod_SOURCES = src/DabMod.cpp \ src/PcDebug.h \ src/DabModulator.cpp \ src/DabModulator.h \ src/Buffer.cpp \ src/Buffer.h \ src/CharsetTools.cpp \ src/CharsetTools.h \ src/ConfigParser.cpp \ src/ConfigParser.h \ src/ModPlugin.cpp \ src/ModPlugin.h \ src/EtiReader.cpp \ src/EtiReader.h \ src/Eti.cpp \ src/Eti.h \ src/Events.cpp \ src/Events.h \ src/FigParser.cpp \ src/FigParser.h \ src/FicSource.cpp \ src/FicSource.h \ src/PuncturingRule.cpp \ src/PuncturingRule.h \ src/PuncturingEncoder.cpp \ src/PuncturingEncoder.h \ src/SubchannelSource.cpp \ src/SubchannelSource.h \ src/Flowgraph.cpp \ src/Flowgraph.h \ src/OutputMemory.cpp \ src/OutputMemory.h \ src/OutputZeroMQ.cpp \ src/OutputZeroMQ.h \ src/TimestampDecoder.h \ src/TimestampDecoder.cpp \ src/InputFileReader.cpp \ src/InputMemory.cpp \ src/InputMemory.h \ src/InputReader.h \ src/InputTcpReader.cpp \ src/OutputFile.cpp \ src/OutputFile.h \ src/FrameMultiplexer.cpp \ src/FrameMultiplexer.h \ src/PrbsGenerator.cpp \ src/PrbsGenerator.h \ src/BlockPartitioner.cpp \ src/BlockPartitioner.h \ src/SignalMultiplexer.cpp \ src/SignalMultiplexer.h \ src/ConvEncoder.cpp \ src/ConvEncoder.h \ src/TimeInterleaver.cpp \ src/TimeInterleaver.h \ src/FormatConverter.cpp \ src/FormatConverter.h \ src/Utils.cpp \ src/Utils.h \ lib/zmq.hpp \ lib/RemoteControl.cpp \ lib/RemoteControl.h \ lib/Log.cpp \ lib/Log.h \ lib/Json.h \ lib/Json.cpp \ lib/Globals.cpp \ lib/INIReader.h \ lib/crc.h \ lib/crc.c \ lib/Socket.h \ lib/Socket.cpp \ lib/ThreadsafeQueue.h \ lib/fec/char.h \ lib/fec/decode_rs_char.c \ lib/fec/decode_rs.h \ lib/fec/encode_rs_char.c \ lib/fec/encode_rs.h \ lib/fec/fec.h \ lib/fec/init_rs_char.c \ lib/fec/init_rs.h \ lib/fec/rs-common.h \ lib/edi/buffer_unpack.hpp \ lib/edi/common.hpp \ lib/edi/common.cpp \ lib/edi/eti.hpp \ lib/edi/eti.cpp \ lib/edi/ETIDecoder.hpp \ lib/edi/ETIDecoder.cpp \ lib/edi/PFT.hpp \ lib/edi/PFT.cpp \ src/FIRFilter.cpp \ src/FIRFilter.h \ src/MemlessPoly.cpp \ src/MemlessPoly.h \ src/GainControl.cpp \ src/GainControl.h \ src/output/Feedback.cpp \ src/output/Feedback.h \ src/output/SDR.cpp \ src/output/SDR.h \ src/output/SDRDevice.h \ src/output/Dexter.cpp \ src/output/Dexter.h \ src/output/Soapy.cpp \ src/output/Soapy.h \ src/output/UHD.cpp \ src/output/UHD.h \ src/output/USRPTime.cpp \ src/output/USRPTime.h \ src/output/Lime.cpp \ src/output/Lime.h \ src/output/BladeRF.cpp \ src/output/BladeRF.h \ src/PhaseReference.cpp \ src/PhaseReference.h \ src/QpskSymbolMapper.cpp \ src/QpskSymbolMapper.h \ src/FrequencyInterleaver.cpp \ src/FrequencyInterleaver.h \ src/DifferentialModulator.cpp \ src/DifferentialModulator.h \ src/NullSymbol.cpp \ src/NullSymbol.h \ src/CicEqualizer.cpp \ src/CicEqualizer.h \ src/OfdmGenerator.cpp \ src/OfdmGenerator.h \ src/GuardIntervalInserter.cpp \ src/GuardIntervalInserter.h \ src/Resampler.cpp \ src/Resampler.h \ src/PAPRStats.cpp \ src/PAPRStats.h \ src/TII.cpp \ src/TII.h \ kiss/kfc.h \ kiss/kfc.c \ kiss/kiss_fft.c \ kiss/kiss_fft.h \ kiss/kiss_fftnd.c \ kiss/kiss_fftnd.h \ kiss/kiss_fftndr.c \ kiss/kiss_fftndr.h \ kiss/kiss_fftr.c \ kiss/kiss_fftr.h man_MANS = man/odr-dabmod.1 Opendigitalradio-ODR-DabMod-f7eedef/NEWS000066400000000000000000000000001475762153200201060ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/README.md000066400000000000000000000056351475762153200207110ustar00rootroot00000000000000OVERVIEW ======== ODR-DabMod is a *DAB (Digital Audio Broadcasting)* modulator compliant to ETSI EN 300 401. It is the continuation of the work started by the Communications Research Center Canada, and is now pursued in the [Opendigitalradio project](http://opendigitalradio.org). ODR-DabMod is part of the ODR-mmbTools tool-set. More information about the ODR-mmbTools is available in the *guide*, available on the [Opendigitalradio mmbTools page](http://www.opendigitalradio.org/mmbtools). Features -------- - Reads ETI and EDI, outputs compliant COFDM I/Q - Supports native DAB sample rate and can also resample to other rates - Supports all four DAB transmission modes - Configuration file support, see `doc/example.ini` - First-class support for the [PrecisionWave DEXTER](https://precisionwave.com/products/dexter) device - First-class support for [USRP devices](https://www.ettus.com/product) using UHD driver - Tested for B200, B100, USRP2, USRP1 - With WBX daughterboard (where appropriate) - Timestamping support required for SFN - GPSDO monitoring (both Ettus and [ODR LEA-M8F board](http://www.opendigitalradio.org/lea-m8f-gpsdo)) - Second-class support for devices using [SoapySDR](https://github.com/pothosware/SoapySDR/wiki) - Known to work with [LimeSDR board](https://myriadrf.org/projects/limesdr/), the [HackRF](https://greatscottgadgets.com/hackrf/). - Second-class support for LimeSDR through LimeSuite - Second-class support for [BladeRF 2.0](https://www.nuand.com/bladerf-2-0-micro/) devices - Monitoring integration with munin - A FIR filter for improved spectrum mask - TII insertion - Logging: log to file, to syslog - EDI sources: TCP and UDP, both with and without Protection and Fragmentation Layer. - ETI sources: ETI-over-TCP, file (Raw, Framed and Streamed) - A Telnet and ZeroMQ remote-control that can be used to change some parameters during runtime and retrieve statistics. See `doc/README-RC.md` for more information - ZeroMQ PUB and REP output, useful for sending IQ to GNURadio flowgraphs. Development has stalled on the following topics: - Experimental prototype about digital predistortion for PA linearisation. - See `python/dpd/README.md` - A web GUI for control and supervision of modulator and predistortion engine. See `python/gui/README.md` The `src/` directory contains the source code of ODR-DabMod. The `doc/` directory contains the ODR-DabMod documentation, an example configuration file and a script for munin integration. The `lib/` directory contains source code of libraries needed to build ODR-DabMod. The `python/` directory contains a web-based graphical control interface and the digital predistortion project. INSTALL ======= See the `INSTALL.md` file for installation instructions. LICENCE ======= See the files `LICENCE` and `COPYING` CONTACT ======= Matthias P. Braendli *matthias [at] mpb [dot] li* With thanks to other contributors listed in AUTHORS http://opendigitalradio.org/ Opendigitalradio-ODR-DabMod-f7eedef/TODO000066400000000000000000000013021475762153200201050ustar00rootroot00000000000000This TODO file lists ideas and features for future developments. Unless written, no activity has been started on the topics. Smaller things -------------- Remove GuardIntervalInserter implementation for window==0, as it was shown both are equivalent. Resampler improvements ---------------------- * Assess quality of window currently used. * Evaluate usefulness of other windows. * Distribute energy of Fs bin equally to both negative and positive frequencies in the back buffer. Review CicEq ------------ The CIC Equaliser was used for the USRP1 to compensate for spectrum flatness. It is not documented, and its effect poorly explained. Review if still needed, and document appropriately. Opendigitalradio-ODR-DabMod-f7eedef/bootstrap.sh000077500000000000000000000001161475762153200217730ustar00rootroot00000000000000#! /bin/sh autoreconf --install && \ echo "You can call ./configure now" Opendigitalradio-ODR-DabMod-f7eedef/configure.ac000066400000000000000000000217231475762153200217140ustar00rootroot00000000000000# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the # Queen in Right of Canada (Communications Research Center Canada) # Copyright (C) 2025 Matthias P. Braendli, http://opendigitalradio.org # This file is part of ODR-DabMod. # # ODR-DabMod 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. # # ODR-DabMod 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 ODR-DabMod. If not, see . AC_PREREQ([2.69]) AC_INIT([ODR-DabMod],[3.0.0],[matthias.braendli@mpb.li]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE([-Wall foreign subdir-objects]) AC_CONFIG_SRCDIR([src/DabMod.cpp]) AC_CONFIG_HEADERS([config.h]) AM_SILENT_RULES([yes]) # Checks for programs. AC_PROG_CXX AC_PROG_CC AM_PROG_CC_C_O AC_PROG_INSTALL AX_CXX_COMPILE_STDCXX(17,noext,mandatory) EXTRA="" AC_ARG_ENABLE([prof], [AS_HELP_STRING([--enable-prof], [Enable profiling])], [], [enable_prof=no]) AC_ARG_ENABLE([fast-math], [AS_HELP_STRING([--enable-fast-math], [Set -ffast-math])], [], [enable_fast_math=no]) AC_ARG_ENABLE([trace], [AS_HELP_STRING([--enable-trace], [Enable trace output])], [], [enable_trace=no]) AC_ARG_ENABLE([zeromq], [AS_HELP_STRING([--disable-zeromq], [Disable ZeroMQ output and remote control])], [], [enable_zeromq=yes]) AC_ARG_ENABLE([native], [AS_HELP_STRING([--disable-native], [Do not compile with -march=native])], [], [enable_native=yes]) AC_ARG_ENABLE([dexter], [AS_HELP_STRING([--enable-dexter], [Build for PrecisionWave Dexter board])], [], [enable_dexter=no]) AC_ARG_ENABLE([limesdr], [AS_HELP_STRING([--enable-limesdr], [Build for LimeSDR board])], [], [enable_limesdr=no]) AC_ARG_ENABLE([bladerf], [AS_HELP_STRING([--enable-bladerf], [Build for BladeRF boards])], [], [enable_bladerf=no]) # UHD support control AC_ARG_ENABLE([output_uhd], [AS_HELP_STRING([--disable-output-uhd], [Disable UHD output])], [], [enable_output_uhd=yes]) AC_LANG_PUSH([C++]) AX_CHECK_COMPILE_FLAG([-Wduplicated-cond], [CXXFLAGS="$CXXFLAGS -Wduplicated-cond"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wduplicated-branches], [CXXFLAGS="$CXXFLAGS -Wduplicated-branches"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wlogical-op], [CXXFLAGS="$CXXFLAGS -Wlogical-op"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wrestrict], [CXXFLAGS="$CXXFLAGS -Wrestrict"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wno-pragmas], [CXXFLAGS="$CXXFLAGS -Wno-pragmas"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wdouble-promotion], [CXXFLAGS="$CXXFLAGS -Wdouble-promotion"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG(["-Wformat=2"], [CXXFLAGS="$CXXFLAGS -Wformat=2"], [], ["-Werror"]) AC_LANG_POP([C++]) PKG_CHECK_MODULES([FFTW], [fftw3f], [], [AC_MSG_ERROR([FFTW is required])]) echo "Checking zeromq" AS_IF([test "x$enable_zeromq" = "xyes"], [AC_CHECK_LIB([zmq], [zmq_init], [ZMQ_LIBS="-lzmq"], [AC_MSG_ERROR([ZeroMQ libzmq is required])])]) AS_IF([test "x$enable_zeromq" = "xyes"], [AC_DEFINE(HAVE_ZEROMQ, [1], [Define if ZeroMQ is enabled])]) AS_IF([test "x$enable_prof" != "xno"], [EXTRA="$EXTRA -pg"]) AS_IF([test "x$enable_fast_math" != "xno"], [EXTRA="$EXTRA -ffast-math"]) AS_IF([test "x$enable_trace" != "xno"], AC_DEFINE(TRACE, [1], [Enable trace output for all blocks])) # Define conditionals for Makefile.am AM_CONDITIONAL([IS_GIT_REPO], [test -d '.git']) # Defines for config.h AX_PTHREAD([], AC_MSG_ERROR([requires pthread])) PKG_CHECK_MODULES([SOAPYSDR], [SoapySDR], enable_soapysdr=yes, enable_soapysdr=no) AS_IF([test "x$enable_limesdr" = "xyes"], [AC_CHECK_LIB([LimeSuite], [LMS_Init], [LIMESDR_LIBS="-lLimeSuite"], [AC_MSG_ERROR([LimeSDR LimeSuite is required])])]) AS_IF([test "x$enable_dexter" = "xyes"], [AC_CHECK_LIB([iio], [iio_create_scan_context], [IIO_LIBS="-liio"], [AC_MSG_ERROR([libiio is required])])]) AS_IF([test "x$enable_bladerf" = "xyes"], [AC_CHECK_LIB([bladeRF], [bladerf_open], [BLADERF_LIBS="-lbladeRF"], [AC_MSG_ERROR([BladeRF library is required])])]) AC_SUBST([CFLAGS], ["$CFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"]) AC_SUBST([CXXFLAGS], ["$CXXFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"]) AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS $IIO_LIBS $BLADERF_LIBS"]) # Checks for UHD. AS_IF([test "x$enable_output_uhd" = "xyes"], [ PKG_CHECK_MODULES([UHD], [uhd], [], [AC_MSG_ERROR([UHD is required])]) ]) AS_IF([test "x$enable_output_uhd" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_UHD, [1], [Define if UHD output is enabled])]) # I don't know why this is needed, I think the uhd pkg-config file should also # tell us to link against boost thread, but it doesn't. AS_IF([test "x$enable_output_uhd" = "xyes"], [ AX_BOOST_BASE([1.55.0], [], AC_MSG_ERROR([BOOST 1.55 or later is required])) AX_BOOST_THREAD ]) AS_IF([test "x$enable_soapysdr" = "xyes"], [AC_DEFINE(HAVE_SOAPYSDR, [1], [Define if SoapySDR output is enabled])]) AS_IF([test "x$enable_limesdr" = "xyes"], [AC_DEFINE(HAVE_LIMESDR, [1], [Define if LimeSDR output is enabled]) ]) AS_IF([test "x$enable_dexter" = "xyes"], [AC_DEFINE(HAVE_DEXTER, [1], [Define if Dexter output is enabled])]) AS_IF([test "x$enable_bladerf" = "xyes"], [AC_DEFINE(HAVE_BLADERF, [1], [Define if BladeRF output is enabled]) ]) # Checks for header files. AC_CHECK_HEADERS([fcntl.h limits.h memory.h netinet/in.h stdint.h stdlib.h string.h sys/time.h sys/timeb.h unistd.h]) AC_MSG_CHECKING(for M_PIl existence) AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[#include ]], [[double pi = M_PIl;]])], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_DEFINE([M_PIl], [M_PI], [Replacing define])]) AC_LANG_POP([C++]) # Linux has prctl to set thread names AC_MSG_CHECKING(for prctl and PR_SET_NAME) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ #include void set_thread_name() { prctl(PR_SET_NAME,"test",0,0,0); } ]])], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_PRCTL, 1, [Define this symbol if you have prctl and PR_SET_NAME]) ], [ AC_MSG_RESULT(no) ]) # Linux defines MSG_NOSIGNAL, some other systems have SO_NOSIGPIPE instead AC_MSG_CHECKING(for MSG_NOSIGNAL) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ #include int f = MSG_NOSIGNAL; ]])], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_MSG_NOSIGNAL, 1, [Define this symbol if you have MSG_NOSIGNAL]) ], [ AC_MSG_RESULT(no) ]) AC_MSG_CHECKING(for SO_NOSIGPIPE) AC_COMPILE_IFELSE([ AC_LANG_PROGRAM([[ #include int f = SO_NOSIGPIPE; ]])], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SO_NOSIGPIPE, 1, [Define this symbol if you have SO_NOSIGPIPE]) ], [ AC_MSG_RESULT(no) ]) # Check for march AS_IF([test "x$enable_native" = "xyes"], [AC_MSG_CHECKING(if we can add -march=native to CFLAGS) save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -march=native" AC_COMPILE_IFELSE( [AC_LANG_PROGRAM([[ void testfunc(void) {} ]])], [supports_march_native=yes], [supports_march_native=no] ) AC_MSG_RESULT($supports_march_native) if test x"$supports_march_native" = xno; then CXXFLAGS="$save_CXXFLAGS" fi ]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT echo echo "***********************************************" echo enabled="" disabled="" for feat in prof trace output_uhd zeromq soapysdr limesdr bladerf dexter do eval var=\$enable_$feat AS_IF([test "x$var" = "xyes"], [enabled="$enabled $feat"], [disabled="$disabled $feat"]) done echo " Features" echo " Enabled: $enabled" echo " Disabled: $disabled" echo echo enabled="" disabled="" for feat in supports_march_native enable_fast_math do eval var=\$$feat AS_IF([test "x$var" != "xno"], [enabled="$enabled $feat"], [disabled="$disabled $feat"]) done echo " Options" echo " Active: $enabled" echo " Disabled: $disabled" echo echo "***********************************************" echo Opendigitalradio-ODR-DabMod-f7eedef/doc/000077500000000000000000000000001475762153200201665ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/doc/README-Fileinput000066400000000000000000000016701475762153200230070ustar00rootroot00000000000000The file input support three file formats: Framed format is used for file recording. It is the default format. The padding can be removed from data. Format: uint32_t nbFrames for each frame uint16_t frameSize uint8_t data[frameSize] Streamed format is used for streamed applications. As the total number of frames is unknown before end of transmission, the corresponding field is removed. The padding can be removed from data. Format: for each frame uint16_t frameSize uint8_t data[frameSize] Raw format is a bit-by-bit (but byte aligned on sync) recording of a G.703 data stream. The padding is always present. Format: for each frame uint8_t data[6144] Please note that our raw format can also be referred to as ETI(NI, G.703) or ETI(NI). All numbers are little-endian. This description has been taken from the CRC mmbTools forum. Opendigitalradio-ODR-DabMod-f7eedef/doc/README-RC.md000066400000000000000000000042371475762153200217550ustar00rootroot00000000000000Remote Control Interface ======================== The RC interface allows you to change settings at runtime and to access some statistics. Two interfaces are available: Telnet and based on ZeroMQ. The Telnet interface is designed for human interaction. Once you have enabled the interface and set the port, use any telnet client to connect to the server to get the RC command line interface. Since this is totally unsecure telnet, the software will only listen on the local loopback interface. To get secure remote access, use SSH port forwarding. The ZeroMQ interface is designed for machine interaction, e.g. for usage in scripts or from third party tools. The Munin monitoring is also using this interface, please see `doc/stats_dabmod_munin.py`. An example python script to connect to that interface is available in `doc/zmq-ctrl/zmq_remote.py`, and example C++ code is available in `doc/zmq-ctrl/cpp/`. Both interfaces may be enabled simultaneously. Statistics available -------------------- The following statistics are presented through the RC: * Value of TIST in `tist timestamp` * UHD: number of underruns, overruns and frames transmitted * SoapySDR: number of underruns and overruns * OFDM Generator: CFR stats and MER after CFR (if CFR enabled) in `ofdm clip_stats` * OFDM Generator: PAPR before and after CFR in `ofdm papr` More statistics are likely to be added in the future, and we are always open for suggestions. ZMQ RC Protocol --------------- ODR-DabMod binds a zmq rep socket so clients must connect using either req or dealer socket. [] denotes message part as zmq multi-part message are used for delimitation. All message parts are utf-8 encoded strings and match the Telnet command set. Messages to be sent as literal strings are denoted with "" below. The following commands are supported: REQ: ["ping"] REP: ["ok"] REQ: ["list"] REP: ["ok"][module name][module name]... REQ: ["show"][module name] REP: ["ok"][parameter: value][parameter: value]... REQ: ["get"][module name][parameter] REP: [value] _OR_ ["fail"][error description] REQ: ["set"][module name][parameter][value] REP: ["ok"] _OR_ ["fail"][error description] Opendigitalradio-ODR-DabMod-f7eedef/doc/README-SFN.md000066400000000000000000000106151475762153200220740ustar00rootroot00000000000000On the Usage of ODR-DabMod for Synchronous Transmissions ======================================================== Summary ------- ODR-DabMux and ODR-DabMod offer support for timestamped transmission when the UHD output is used. This README explains how this functionality works, and how to set it up. This feature is a prerequisite for the creation of a single-frequency network. Concept ------- The goal of this functionality is to synchronise the transmission for several transmitters. This has been tested with the USRP B100, B200 and the USRP2, that both have the necessary REFCLK and 1PPS inputs. Both are required to synchronise two USRPs: - The REFCLK is used in the USRP for timekeeping. If we want two USRPs to stay synchronised, they both must have a precise 10MHz source at the REFCLK, otherwise their internal clocks will drift off. - The 1PPS signal is used to set the time inside the USRPs. The rising edge of the 1PPS signal has happen synchronously for all transmitters. Usually, GPS is used to drive this 1PPS. For such a system, there will be one multiplexer, which will send the ETI stream to several modulators. The ETI stream, in this case, is transported over the ZMQ interconnection. Each modulator receives ETI frames that contain absolute timestamps, defining the exact point in time when the frame has to be transmitted. These in-band timestamps are composed of two parts: - The TIST field as defined in the ETI standard, giving an offset after the pulse per second signal; - A time information transmitted using the MNSC, representing the precise time when the frame must be transmitted, with one-second resolution. When ODR-DabMux is configured accordingly, the TIST is defined in each frame. The time is always encoded in the MNSC. When the ETI stream is sent to several modulators using non-blocking I/O, it is not possible to rely on a modulator to back-pressure the Ensemble multiplexer. It is therefore necessary to throttle multiplexer output. Each modulator then receives the ETI stream through a ZMQ connection. Each frame contains the complete timestamp, to which an per-modulator offset is added. The sum is then given to the USRP. The offset can be specified in the configuration file of ODR-DabMod, and can be modified on the fly using the remote control interface. ODR-DabMod uses the UHD library to output modulated samples to the USRP device. When started, it defines the USRP time using the local time and the PPS signal. It is therefore important to synchronise computer time using NTP. When a frame arrives with a timestamp that is in the past, the frame is dropped. If the timestamp is too far in the future, the output module waits a short delay. Synchronisation can be verified by using an oscilloscope and a receiver. It is very easy to see if the null symbols align. Then tune the receiver to the ensemble, and alternatively lower the tx gain of each modulator to see if the receiver is still able to receive the ensemble without hiccup. Time and frequency references ----------------------------- In addition to the 10MHz refclk and 1PPS inputs on the USRP, some USRPs also support an integrated GPSDO. For the B200, there are two GPSDOs modules that can be used: The Ettus GPSDO (Jackson Labs Firefly), and the u-blox LEA-M8F on the [Opendigitalradio board](http://www.opendigitalradio.org/lea-m8f-gpsdo). To use the LEA-M8F, some modifications in the UHD library are necessary, because the module outputs a 30.72MHz refclk instead of a 10MHz. The changes are available in the [ODR repository of UHD](https://github.com/Opendigitalradio/uhd), with branch names called *lea-m8f-UHDVERSION*. When using the integrated GPSDO, ODR-DabMod will also monitor if the GPS reception is ok, and if the time reference is usable. Hardware requirements --------------------- The following hardware is required to build a SFN with the ODR-mmbTools: - Two USRPs ; - One or two computers with the mmbTools installed ; - A network connection between the two computers ; - A 10MHz refclk source ; - A 1PPS source synchronised to the 10MHz ; - An oscilloscope to check synchronisation. It is possible to use signal generators as REFCLK source and 1PPS, if there is no GPS-disciplined oscillator available. It is necessary to synchronise the 1PPS source to the 10MHz source. ########### june 2012, initial version, Matthias P. Braendli feb 2014, renamed crc-dabXYZ to odr-dabXYZ, mpb sep 2015, overhaul, talk about GPSDOs, mpb Opendigitalradio-ODR-DabMod-f7eedef/doc/example.ini000066400000000000000000000311721475762153200223260ustar00rootroot00000000000000; Sample configuration file for ODR-DabMod [general] ; Before starting up, run this command, and abort if it doesn't return 0. ; This is useful to ensure that NTP has properly synchronised time. startupcheck= ;startupcheck=chronyc waitsync 10 0.01 ;startupcheck=ntp-wait -fv [remotecontrol] ; The RC feature is described in detail in doc/README-RC.md ; enable the telnet remote control on localhost:2121 telnet=1 telnetport=2121 ; Enable zmq remote control. zmqctrl=1 zmqctrlendpoint=tcp://127.0.0.1:9400 ; accepted formats according to man zmq_bind, i.e. ; tcp://:, e.g. tcp://lo:9400 ; and tcp://: [log] ; Write to a logfile or to syslog. ; Setting filename to stderr is not necessary, as all messages are ; automatically written to stderr. syslog=0 filelog=0 filename=odr-dabmod.log ; If you don't want to see the flowgraph processing time, set: ;show_process_time=0 [input] ; A file or fifo input is using transport=file transport=file source=/dev/stdin ; When the end of file is reached, it is possible to rewind it loop=0 ; EDI input. ; Listen for EDI data on a given UDP port, unicast or multicast. ;transport=edi ; ; EDI over TCP: ; ; Connect to TCP server on a given host ;source=tcp://localhost:9201 ; ; EDI over UDP: ; ; Supported syntax for the source setting: ; Bind to default interface and receive data from port 12000 ;source=udp://:12000 ; ; Bind to interface with IP:192.168.1.22 and receive data from port 12000 ;source=udp://192.168.1.22:12000 ; ; Bind to interface with IP:192.168.1.22 and join multicast group: ; 239.100.101.22 and receive data from port 12000 ;source=udp://192.168.1.22@239.100.101.22:12000 ; ; Bind to default interface (which routes to multicast) and join multicast ; group: 239.100.101.22 and receive data from port 12000 ;source=udp://@239.100.101.22:12000 ; ; Maximum delay in milliseconds that the EDI input is willing to wait ; before it timeouts ;edi_max_delay=240 ; This EDI implementation does not support EDI Packet Resend ; ETI-over-TCP example: ;transport=tcp ;source=localhost:9200 [modulator] ; Mode 'fix' uses a fixed factor and is really not recommended. It is more ; useful on an academic perspective for people trying to understand the DAB ; modulation. ; ; Mode 'max' is the normalization of every OFDM symbol. No overshoot, no ; truncating, but varying output power (around 3dB) which might not be the best ; for some power amplifier. The output samples are limited to a magnitude ; of 32768. ; ; Mode 'var' uses the method specified in ETSI 300 798 Clause 5.3. This method ; normalizes to 4 times the standard deviation for an approximation of the RMS ; power. So around 6/100000 samples will be truncated and will introduce some ; really minor distortion. But this mode also maximizes the output power. This ; is the gain mode recommended for real world operation as it is based on a DAB ; standard; the only difference is that ODR-DabMod internally calculates this with ; 32-bit floats instead of 8 bits. gainmode=var ; ; In mode var, you can choose to normalise the samples to something else than ; 4 times the standard deviation. ;normalise_variance=4 ; Transmission mode ; If not defined, use Transmission Mode 1 ;mode=1 fixed_point=1 ; The digital gain is a value that is multiplied to each sample. It is used ; to tune the chain to make sure that no non-linearities appear up to the ; USRP daughterboard programmable gain amplifier (PGA). ; If there is clipping, the spectral quality of the signal will quickly deteriorate, ; and wide-band noise will be generated. ; ; Be aware that there is a dependency with resampling. digital_gain=0.8 ; Output sample rate. Values other than 2048000 enable ; resampling. ; Warning! digital_gain settings are different if resampling ; is enabled or not ! rate=2048000 ; (DEPRECATED) CIC equaliser for USRP1 and USRP2 ; These USRPs have an upsampler in FPGA that does not have a flat frequency ; response. The CIC equaliser compensates this. This setting is specific to ; the USRP1 and USRP2 devices. ; Set to 0 to disable CicEqualiser ;dac_clk_rate=0 ; The USRP1 does not have flexible clocking, you will need ;rate=3200000 ; and ;dac_clk_rate=128000000 ; When nonzero, overlap ofdmwindowing samples from each OFDM symbol ; onto the previous and next symbol, using a raised cosine window function. ; This has the effect of smoothing the transition from one symbol to the next, ; which improves spectrum shape. ; In Transmission Mode I, every data symbol is composed of 2552 samples. ;ofdmwindowing=10 ; Settings for crest factor reduction. Statistics for ratio of ; samples that were clipped are available through the RC. [cfr] enable=0 ; At what amplitude the signal should be clipped clip=50.0 ; How much to clip the error signal used to compensate the effect ; of clipping error_clip=0.1 [firfilter] ; The FIR Filter can be used to create a better spectral quality. enabled=1 ; The filter taps can be calculated with the python script ; doc/fir-filter/generate-filter.py ; If filtertapsfile is not given, the default taps are used. ;filtertapsfile=simple_taps.txt [poly] ;Predistortion using memoryless polynom, see dpd/ folder for more info enabled=0 polycoeffile=polyCoefs [output] ; choose output: possible values: uhd, file, zmq, dexter, soapysdr, limesdr, bladerf output=uhd [fileoutput] ; Two output formats are supported: In the default mode, ; the file output writes I/Q float values (i.e. complex ; float) to the file. The I and Q samples can take values up ; to 810000 in absolute magnitude with gainmode FIX. With ; gainmode VAR and FIX, they should never exceed 50000. ;format=complexf ; ; The complexf_normalised format applies a compensation factor to the complexf ; output to bring the range of the I and Q components to [-1.0 .. 1.0]. The ; digital_gain is still applied on top of that normalisation. ;format=complexf_normalised ; ; When the format is set to s8, the output writes I/Q 8-bit ; signed integers, where the magnitude is multiplied by 128/50000 ; effectively mapping the gainmode VAR range of -50000 -- 50000 ; to -128 -- 128. For other gainmodes, use the digital_gain setting ; to make sure you don't create clipping. ; ; The format u8 is the same as s8, except that the values are mapped ; between 0 and 255. Use u8 for welle.io, qt-dab or other tools. ; ; Also supported is s16, with system endianness (little endian on x86_64 and ARM) ;format=s8 ; The output file: filename=ofdm.iq show_metadata=0 [uhdoutput] ; The UHD output can be directly used with the Ettus USRP devices ; ; You have to set master_clock_rate to a multiple of the ; sample_rate. Ideally, it should be ; master_clock_rate = 4 * sample_rate ; or even a higher factor. ; ; Settings for the B200: device= master_clock_rate=32768000 type=b200 txgain=40 ; The B200 needs larger gains (up to 89dB) but, ; "Gain settings are application specific, but it is recommended that users ; consider using at least half of the available gain to get reasonable dynamic ; range." ; From the B200 User Manual ; http://files.ettus.com/uhd_docs/manual/html/usrp_b200.html ; ; More information and measurements available on: ; http://wiki.opendigitalradio.org/index.php/USRP_B200_Measurements ; You can set what TX and RX antennas to use. This will depend on the ; USRP model you are using. ;tx_antenna= ;rx_antenna=RX2 ; Settings for a USRP B100: ;device= ; you can put additional UHD device settings here ;master_clock_rate=32768000 ;type=b100 ;txgain=2.0 ; Try first with small gain values ; Also set rate to 2048000 ; For the USRP1 ;device= ;type=usrp1 ; the usrp1 can have two daughterboards, the subdevice parameter allows you ; to choose which one to use ;subdevice=A:0 ; The USRP1 doesn't support master_clock_rate, you need to enable resampling ; You must specify either frequency or channel, but not both. ;frequency=234208000 channel=13C ; Override automatic analog frontend bandwidth calculation. Units: Hz ;bandwidth=2000000 ; Some USRP boards/frontends support setting an LO offset that has the ; effect of shifting DC out of the signal bandwidth. This should also ; improve IQ imbalance effects, because the mirror will centered on another ; frequency (it should be on frequency + 2*lo_offset) ; ; The value can be negative, and its absolute value must be smaller than ; master_clock_rate/2. ;lo_offset=2048000 ; The reference clock to use. The gpsdo is the ODR LEA-M8F board, the ; official Ettus GPSDO is selected with gpsdo-ettus ; possible values : internal, external, MIMO, gpsdo, gpsdo-ettus refclk_source=internal ; The reference one pulse-per second to use ; possible values : none, external, MIMO, gpsdo pps_source=none ; Behaviour when external clock reference lock lost ; possible values: ignore, crash behaviour_refclk_lock_lost=ignore ; The maximum accepted holdover time for the gpsdo once it ; started operating. Initial check interval for GPSDO lock ; at startup is always 180s. ; Valid only if the refclk and pps_source are set to gpsdo. ; Units: seconds ; Set to 0 to disable holdover check ; default value: 0 max_gps_holdover_time=600 ; Enable the TCP server to communicate TX and RX feedback for ; digital predistortion. ; Set to 0 to disable ;dpd_port=50055 ; section defining ZeroMQ output properties [zmqoutput] ; on which port to listen for connections ; please see the Transports section in man zmq ; for more information regarding the syntax listen=tcp://*:54001 ; what ZMQ socket type to use. Valid values: PUB, REP ; Please see man zmq_socket for documentation socket_type=pub ; section defining the SoapySDR output settings. [soapyoutput] ; These options are given to the SoapySDR library: device= master_clock_rate=32768000 txgain=40 ;frequency=234208000 channel=13C ;lo_offset=2048000 ; Override automatic analog frontend bandwidth calculation. Units: Hz ;bandwidth=2000000 ; You can set what TX antenna to use. This will depend on the ; SDR device you are using. ;tx_antenna= ; Enable the TCP server to communicate TX and RX feedback for ; digital predistortion. ; Set to 0 to disable ;dpd_port=50055 [dexteroutput] ; More details about the PrecisionWave DEXTER: ; https://github.com/PrecisionWave/DexterDABModulator txgain=65535 ; channel/frequency is applied to ad9957.center_frequency ;frequency=234208000 channel=13C ; lo offset is applied to dexter_dsp_tx.frequency0 lo_offset=0 max_gps_holdover_time=3600 [limeoutput] ; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion. device= ;master_clock_rate= ; txgain range: 0 .. 100 txgain=20 tx_antenna=BAND1 ;lo_offset=2048000 ;frequency=234208000 channel=13C ; The LimeSDR contains a FIR filter in FPGA that can be used to filter the IQ signal. ; This is useful because it allows us to upsample in a very cheap way in software instead ; of using the FFT-based resampler. upsample=1 ; section defining the BladeRF output settings. [bladerfoutput] ; bladerfoutput is currently under development device= ; The reference clock to use. ; possible values : pps, 10mhz ; Others values than above lead to disable refclk_src refclk_source= ;master_clock_rate, not configurable with bladeRF, fundamental clock runs at 38.4 MHz ; txgain range: -23.75 .. 66 [dB] txgain = 20 ;tx_antenna -> not available channel = 13C bandwidth = 1800000 ; Used for running single-frequency networks [delaymanagement] ; Enable handling of timestamps for SFN synchronous=0 ; Whether to mute the TX when incoming frames have no timestamp mutenotimestamps=0 ; This offset is added to the TIST, and the sum defines the ; TX time of the transmission frame. It can by changed at runtime ; through the remote control. offset=0.002 ; The way the timestamps are interpreted in ODR-DabMod up to v1.1.0 was not ; specified, and you should not assume that two different versions will ; transmit synchronously given the same settings. Always run SFNs with ; identical versions! ; Furthermore, when combining ODR-DabMod with third-party modulators, always ; measure! [tii] ; If these options are set, TII transmission is enabled. ; DAB modes I and II are supported, and must be set explicitly in ; this file. Reading DAB mode from ETI is not supported. enable=0 ; comb is also known as sub-identifier. comb=1 ; pattern is also known as main-identifier. If you run several transmitters ; in SFN, it is better to use the same pattern for all, and vary the comb. ; Otherwise identification of the transmitters may be more difficult. pattern=11 ; There are two variants of TII being used. The old variant that uses the wrong ; phase on the second carrier in each carrier pairs and is therefore not ; conforming to the specification. Modern analysers can decode both variants, ; while others, like the Philips DAB752 and the VAD v2 monitor are known to ; decode only the old non-conforming variant. old_variant=0 Opendigitalradio-ODR-DabMod-f7eedef/doc/fir-filter/000077500000000000000000000000001475762153200222315ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/doc/fir-filter/README000066400000000000000000000002701475762153200231100ustar00rootroot00000000000000This tool can be used to generate FIRFilter taps for the corresponding functionality in ODR-DabMod. The filter parameters are defined in the script, they can be easily adapted there. Opendigitalradio-ODR-DabMod-f7eedef/doc/fir-filter/filtertaps.txt000066400000000000000000000014061475762153200251500ustar00rootroot0000000000000045 -0.00110450468492 0.00120703084394 -0.000840645749122 -0.000187368263141 0.00184351124335 -0.00355578539893 0.00419321097434 -0.00254214904271 -0.00183473504148 0.00781436730176 -0.0125957569107 0.0126200336963 -0.00537294941023 -0.00866683479398 0.0249746385962 -0.0356550291181 0.0319730602205 -0.00795613788068 -0.0363943465054 0.0938014090061 -0.151176810265 0.193567320704 0.791776955128 0.193567320704 -0.151176810265 0.0938014090061 -0.0363943465054 -0.00795613788068 0.0319730602205 -0.0356550291181 0.0249746385962 -0.00866683479398 -0.00537294941023 0.0126200336963 -0.0125957569107 0.00781436730176 -0.00183473504148 -0.00254214904271 0.00419321097434 -0.00355578539893 0.00184351124335 -0.000187368263141 -0.000840645749122 0.00120703084394 -0.00110450468492 Opendigitalradio-ODR-DabMod-f7eedef/doc/fir-filter/generate-filter.py000077500000000000000000000044431475762153200256700ustar00rootroot00000000000000#!/usr/bin/env python # This tool uses gnuradio to generate FIR filter taps # that can be used for the FIRFilter function in # ODR-DabMod # # Usage: # 1) adapt the filter settings below # 2) Call this script and redirect the output of this script into a file # # Requires: # A recent gnuradio version (3.7) # # # The MIT License (MIT) # # Copyright (c) 2013 Matthias P. Braendli # http://mpb.li # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import gnuradio from gnuradio import digital # From documentation at # http://gnuradio.org/doc/doxygen/classgr_1_1filter_1_1firdes.html # use "window method" to design a low-pass FIR filter # # gain: overall gain of filter (typically 1.0) # sampling_freq: sampling freq (Hz) # cutoff_freq: center of transition band (Hz) # transition_width: width of transition band (Hz). # The normalized width of the transition band is what sets the number of taps required. Narrow --> more taps # window_type: What kind of window to use. Determines maximum attenuation and passband ripple. # beta: parameter for Kaiser window gain = 1 sampling_freq = 2.048e6 cutoff = 810e3 transition_width = 250e3 # Generate filter taps and print them out taps = digital.filter.firdes.low_pass(gain, sampling_freq, cutoff, transition_width) # hamming window print(len(taps)) for t in taps: print(t) Opendigitalradio-ODR-DabMod-f7eedef/doc/fir-filter/simplefiltertaps.txt000066400000000000000000000000261475762153200263570ustar00rootroot000000000000005 0.0 0.0 1.0 0.0 0.0 Opendigitalradio-ODR-DabMod-f7eedef/doc/receive_events.py000077500000000000000000000036071475762153200235570ustar00rootroot00000000000000#!/usr/bin/env python # # This is an example program that shows # how to receive runtime events from ODR-DabMod # # LICENSE: see bottom of file import sys import zmq import json from pprint import pprint context = zmq.Context() sock = context.socket(zmq.SUB) ep = "tcp://127.0.0.1:5556" print(f"Receive from {ep}") sock.connect(ep) # subscribe to all events sock.setsockopt(zmq.SUBSCRIBE, bytes([])) while True: parts = sock.recv_multipart() if len(parts) == 2: print("Received event '{}'".format(parts[0].decode())) pprint(json.loads(parts[1].decode())) else: print("Received strange event:") pprint(parts) print() # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to Opendigitalradio-ODR-DabMod-f7eedef/doc/stats_dabmod_munin.py000077500000000000000000000237101475762153200244200ustar00rootroot00000000000000#!/usr/bin/env python2 # # present statistics from ODR-DabMod's # RC interface to munin. Expects ZeroMQ on port # 9400. # # Copy this file to /etc/munin/plugins/dabmod # to use it, and make sure it's executable (chmod +x) import sys import json import zmq import os import re # Values monitored: config_all = "" #default data type is GAUGE # One GAUGE multigraph from 0% to 100% with # ofdm clip_stats clip_ratio # ofdm clip_stats errorclip_ratio config_all += """ multigraph ofdm_clip_stats graph_title OFDM CFR clip stats graph_order clip_ratio errorclip_ratio graph_vlabel number of samples/errors clipped during last ${{graph_period}} graph_category dabmod graph_info This graph shows CFR clipping statistics clip_ratio.info Number of samples clipped clip_ratio.label Number of samples clipped clip_ratio.min 0 clip_ratio.max 100 errorclip_ratio.info Number of errors clipped errorclip_ratio.label Number of errors clipped errorclip_ratio.min 0 errorclip_ratio.max 100""" # One GAUGE multigraph # ofdm clip_stats mer config_all += """ multigraph ofdm_clip_stats_mer graph_title OFDM MER after CFR graph_order mer graph_vlabel MER in dB after CFR graph_category dabmod graph_info This graph shows MER after CFR mer.info MER dB mer.label MER dB mer.min 0 mer.max 100""" # One GAUGE multigraph in dB for # ofdm papr before-cfr # ofdm papr after-cfr config_all += """ multigraph ofdm_papr graph_title OFDM PAPR stats graph_order before_cfr after_cfr graph_args --base 1000 graph_vlabel Averate PAPR before/after CFR during last ${{graph_period}} graph_category dabmod graph_info This graph shows the Peak-to-Average Power Ratio before and after CFR before_cfr.info PAPR before CFR before_cfr.label PAPR before CFR before_cfr.min 0 after_cfr.info PAPR after CFR after_cfr.label PAPR after CFR after_cfr.min 0""" # One GAUGE graph for # tist offset config_all += """ multigraph tist_offset graph_title TIST configured offset graph_order offset graph_args --base 1000 graph_vlabel Configured offset graph_category dabmod graph_info This graph shows the configured TIST offset offset.info Configured offset offset.label Configured offset offset.min 0 offset.max 300""" # One DDERIVE graph for # tist timestamp timestamps config_all += """ multigraph tist_timestamp graph_title TIST timestamp graph_order timestamp graph_args --base 1000 graph_vlabel timestamp value graph_category dabmod graph_info This graph shows the timestamp value in seconds timestamp.info timestamp timestamp.label timestamp timestamp.type DDERIVE timestamp.min 0""" # One DERIVE (min 0) multigraph for # sdr underruns # sdr latepackets config_all += """ multigraph sdr_stats graph_title SDR device statistics graph_order underruns latepackets graph_args --base 1000 graph_vlabel Number of underruns and late packets graph_category dabmod graph_info This graph shows the number of underruns and late packets underruns.info Number of SoapySDR/UHD underruns underruns.label Number of SoapySDR/UHD underruns underruns.type DERIVE underruns.min 0 latepackets.info Number of SoapySDR/UHD late packets latepackets.label Number of SoapySDR/UHD late packets latepackets.type DERIVE latepackets.min 0""" # One DERIVE (min 0) graph for # sdr frames config_all += """ multigraph sdr_frames graph_title SDR number of frames transmitted graph_order frames graph_args --base 1000 graph_vlabel Number of frames transmitted graph_category dabmod graph_info This graph shows the number of frames transmitted frames.info Number of SoapySDR/UHD frames frames.label Number of SoapySDR/UHD frames frames.type DERIVE frames.min 0""" # One GAUGE multigraph # sdr gpsdo_num_sv # and one for device sensors # sdr temp config_all += """ multigraph sdr_gpsdo_sv_holdover graph_title Number of GNSS SVs used and holdover state graph_order num_sv holdover graph_vlabel Number of GNSS SVs and holdover state graph_category dabmod graph_info This graph shows the number of Satellite Vehicles the GNSS receiver uses (Field 7 of GNGGA NMEA sentence), and if it is in holdover num_sv.info Num SVs num_sv.label Num SVs num_sv.min 0 num_sv.max 20 holdover.info Holdover holdover.label Holdover holdover.min 0 holdover.max 1 multigraph sdr_sensors graph_title SDR Sensors graph_order temp graph_vlabel SDR Sensors graph_category dabmod graph_info This graph shows the device temperature in Celsius temp.info Device temperature in Celsius temp.label Celsius temp.min 0 temp.max 100""" ctx = zmq.Context() class RCException(Exception): pass def do_transaction(message_parts, sock): """To a send + receive transaction, quit whole program on timeout""" if isinstance(message_parts, str): sys.stderr.write("do_transaction expects a list!\n"); sys.exit(1) for i, part in enumerate(message_parts): if i == len(message_parts) - 1: f = 0 else: f = zmq.SNDMORE sock.send(part, flags=f) poller = zmq.Poller() poller.register(sock, zmq.POLLIN) socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: rxpackets = sock.recv_multipart() return rxpackets raise RCException("Could not receive data for command '{}'\n".format( message_parts)) def connect(): """Create a connection to the dabmod RC returns: the socket""" sock = zmq.Socket(ctx, zmq.REQ) sock.set(zmq.LINGER, 5) sock.connect("tcp://localhost:9400") try: ping_answer = do_transaction([b"ping"], sock) if not ping_answer == [b"ok"]: sys.stderr.write("Wrong answer to ping\n") sys.exit(1) except RCException as e: print("connect failed because: {}".format(e)) sys.exit(1) return sock def get_rc_value(module, name, sock): try: parts = do_transaction([b"get", module.encode(), name.encode()], sock) if len(parts) != 1: sys.stderr.write("Received unexpected multipart message {}\n".format( parts)) sys.exit(1) return parts[0].decode() except RCException as e: print("get {} {} fail: {}".format(module, name, e)) return "" def handle_re(graph_name, re, rc_value, group_number=1): match = re.search(rc_value) if match: return "{}.value {}\n".format(graph_name, match.group(group_number)) else: return "{}.value U\n".format(graph_name) re_double_value = re.compile(r"(\d+\.\d+)", re.X) re_int_value = re.compile(r"(\d+)", re.X) if len(sys.argv) == 1: sock = connect() munin_values = "" munin_values += "multigraph ofdm_clip_stats\n" ofdm_clip_stats = get_rc_value("ofdm", "clip_stats", sock) re_clip_samples = re.compile(r"(\d+\.\d+)%\ samples\ clipped", re.X) munin_values += handle_re("clip_ratio", re_clip_samples, ofdm_clip_stats) re_clip_errors = re.compile(r"(\d+\.\d+)%\ errors\ clipped", re.X) munin_values += handle_re("errorclip_ratio", re_clip_errors, ofdm_clip_stats) munin_values += "multigraph ofdm_clip_stats_mer\n" re_clip_mer = re.compile(r"MER\ after\ CFR:\ (\d+\.\d+)", re.X) munin_values += handle_re("mer", re_clip_mer, ofdm_clip_stats) munin_values += "multigraph ofdm_papr\n" ofdm_papr_stats = get_rc_value("ofdm", "papr", sock) def muninise_papr(papr): if "N/A" in papr: return "U" else: return float(papr.strip()) # Format is as follows: # "PAPR [dB]: " << std::fixed << # (papr_before == 0 ? string("N/A") : to_string(papr_before)) << # ", " << # (papr_after == 0 ? string("N/A") : to_string(papr_after)); try: _, _, both_papr = ofdm_papr_stats.partition(":") papr_before, papr_after = both_papr.split(",") papr_before = muninise_papr(papr_before) munin_values += "before_cfr.value {}\n".format(papr_before) except: munin_values += "before_cfr.value U\n" try: _, _, both_papr = ofdm_papr_stats.partition(":") papr_before, papr_after = both_papr.split(",") papr_after = muninise_papr(papr_after) munin_values += "after_cfr.value {}\n".format(papr_after) except: munin_values += "after_cfr.value U\n" munin_values += "multigraph tist_offset\n" tist_offset = get_rc_value("tist", "offset", sock) munin_values += handle_re("offset", re_double_value, tist_offset) # Plotting FCT is not useful because it overflows in 6s, and the poll # interval is usually 5min tist_timestamp = get_rc_value("tist", "timestamp", sock) re_tist_timestamp = re.compile(r"(\d+\.\d+)\ for\ frame\ FCT\ (\d+)", re.X) munin_values += "multigraph tist_timestamp\n" munin_values += handle_re("timestamp", re_tist_timestamp, tist_timestamp, 1) munin_values += "multigraph sdr_stats\n" sdr_underruns = get_rc_value("sdr", "underruns", sock) munin_values += handle_re("underruns", re_int_value, sdr_underruns) sdr_latepackets = get_rc_value("sdr", "latepackets", sock) munin_values += handle_re("latepackets", re_int_value, sdr_latepackets) munin_values += "multigraph sdr_gpsdo_sv_holdover\n" try: gps_num_sv = get_rc_value("sdr", "gpsdo_num_sv", sock) munin_values += "num_sv.value {}\n".format(gps_num_sv) except: munin_values += "num_sv.value U\n" try: gps_holdover = get_rc_value("sdr", "gpsdo_holdover", sock) munin_values += "holdover.value {}\n".format(gps_holdover) except: munin_values += "holdover.value U\n" munin_values += "multigraph sdr_sensors\n" try: sdr_temp = get_rc_value("sdr", "temp", sock) munin_values += "temp.value {}\n".format(sdr_temp) except: munin_values += "temp.value U\n" munin_values += "multigraph sdr_frames\n" sdr_frames = get_rc_value("sdr", "frames", sock) munin_values += handle_re("frames", re_int_value, sdr_frames) print(munin_values) elif len(sys.argv) == 2 and sys.argv[1] == "config": # No need to connect print(config_all) else: sys.stderr.write("Invalid command line arguments") sys.exit(1) Opendigitalradio-ODR-DabMod-f7eedef/doc/time-freq-plot.py000077500000000000000000000053211475762153200234110ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Print scope and spectrum from ODR-DabMod I/Q file # # The MIT License (MIT) # # Copyright (c) 2017 Matthias P. Braendli # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys import matplotlib.pyplot as plt import numpy as np rate=2048000 # T = 1/2048000 s # NULL symbol is 2656 T (about 1.3ms) long. T_NULL = 2656 # Full transmission frame in TM1 is 96ms = 196608 T. T_TF = 196608 num_skip_samples = 8 * T_TF num_analyse_samples = 2 * T_TF if len(sys.argv) < 2: print("Specify .iq file name") print("Expected format: complex float I/Q, 2048000 Sps") print("The input file must contain at least 10 transmission frames,") print("i.e. {} samples = {} seconds".format(T_TF * 10, T_TF * 10.0 / rate)) sys.exit(1) fd = open(sys.argv[1], 'rb') # The IQ files potentially have zero samples in the beginning, we need # to skip a few transmission frames source_data = np.fromfile(file=fd, dtype=np.complex64, count=num_skip_samples + num_analyse_samples) print("Read in {} samples".format(len(source_data))) source_data = source_data[num_skip_samples:] source_data_time = np.linspace(0, num_analyse_samples/rate, len(source_data)) print("Signal power: {} of {} samples".format(np.sum(np.abs(source_data**2)), len(source_data))) fft_size = 4096 plt.figure(figsize=(10,8)) plt.subplot(211) plt.title("Real part of signal") plt.plot(source_data_time, np.real(source_data)) signal_spectrum = np.abs(np.fft.fftshift(np.fft.fft(source_data[T_NULL:], fft_size))) freqs = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1./rate)) plt.subplot(212) plt.title("Spectrum of {} samples after the NULL symbol".format(fft_size)) plt.semilogy(freqs, signal_spectrum) plt.show() Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/000077500000000000000000000000001475762153200217375ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/000077500000000000000000000000001475762153200225215ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/OdrModCtrl.cpp000066400000000000000000000202071475762153200252370ustar00rootroot00000000000000/*! * This is an implementation for the zmq ctrl API of the odr-dabmod. * * Copyright (c) 2015 by Jörgen Scott (jorgen.scott@paneda.se) * * ODR-DabMod 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. * * ODR-DabMod 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 ODR-DabMod. If not, see . * * \code * #include "OdrModCtrl.hpp" * #include * ... * zmq::context_t ctx; * std::string error; * COdrModCtrl *pCtrl = new COdrModCtrl(&context, // zmq context * "tcp://127.0.0.1:9400", // zmq endpoint * 1000); // timeout in milliseconds * if (pCtrl->SetTxGain(50, error)) * std::cout << "Tx gain set to 50" << std::endl; * else * std::cout << "An error occured: " << error << std::endl; * delete pCtrl; // destructor will close zmq socket * * \endcode **/ #include "OdrModCtrl.hpp" #include #define MOD_GAIN "gain" #define MOD_UHD "uhd" #define PARAM_DIG_GAIN "digital" #define PARAM_TX_GAIN "txgain" #define PARAM_FREQ "freq" #define PARAM_MUTE "muting" #define PARAM_STAT_DELAY "staticdelay" COdrModCtrl::COdrModCtrl(zmq::context_t *pContext, std::string odrEndpoint, unsigned int timeoutMs) { m_pContext = pContext; m_odrEndpoint = odrEndpoint; m_timeoutMs = (uint32_t) timeoutMs; m_pReqSocket = NULL; } COdrModCtrl::~COdrModCtrl() { if (m_pReqSocket != NULL) { m_pReqSocket->close(); delete m_pReqSocket; } } //// public get methods ///////////////////////////////////////////////////////// bool COdrModCtrl::GetDigitalGain(double &gain, std::string &error) { return DoGet(MOD_GAIN, PARAM_DIG_GAIN, gain, error); } bool COdrModCtrl::GetTxGain(double &gain, std::string &error) { return DoGet(MOD_UHD, PARAM_TX_GAIN, gain, error); } bool COdrModCtrl::GetTxFrequency(double &freqHz, std::string &error) { return DoGet(MOD_UHD, PARAM_FREQ, freqHz, error); } bool COdrModCtrl::GetMuting(bool &mute, std::string &error) { return DoGet(MOD_UHD, PARAM_MUTE, (uint32_t&) mute, error); } bool COdrModCtrl::GetStaticDelay(uint32_t &delayUs, std::string &error) { return DoGet(MOD_UHD, PARAM_STAT_DELAY, delayUs, error); } //// public set methods ///////////////////////////////////////////////////////// bool COdrModCtrl::Ping() { std::string error; if (m_pReqSocket == NULL) { m_pReqSocket = new zmq::socket_t(*m_pContext, ZMQ_REQ); if (!ConnectSocket(m_pReqSocket, m_odrEndpoint, error)) return false; } std::vector msg; msg.push_back("ping"); // send the message if (!SendMessage(m_pReqSocket, msg, error)) { // destroy the socket according to the "Lazy Pirate Pattern" in // the zmq guide m_pReqSocket->close(); delete m_pReqSocket; m_pReqSocket = NULL; return false; } // wait for reply if (!RecvAll(m_pReqSocket, msg, m_timeoutMs, error)) return false; return true; } bool COdrModCtrl::SetDigitalGain(const double gain, std::string &error) { return DoSet(MOD_GAIN, PARAM_DIG_GAIN, gain, error); } bool COdrModCtrl::SetTxGain(const double gain, std::string &error) { return DoSet(MOD_UHD, PARAM_TX_GAIN, gain, error); } bool COdrModCtrl::SetTxFrequency(const double freqHz, std::string &error) { return DoSet(MOD_UHD, PARAM_FREQ, freqHz, error); } bool COdrModCtrl::SetMuting(const bool mute, std::string &error) { return DoSet(MOD_UHD, PARAM_MUTE, mute, error); } bool COdrModCtrl::SetStaticDelay(const int32_t delayUs, std::string &error) { return DoSet(MOD_UHD, PARAM_STAT_DELAY, delayUs, error); } //// private methods //////////////////////////////////////////////////////////// template bool COdrModCtrl::DoSet(const std::string module, const std::string parameter, const Type value, std::string &error) { if (m_pReqSocket == NULL) { m_pReqSocket = new zmq::socket_t(*m_pContext, ZMQ_REQ); if (!ConnectSocket(m_pReqSocket, m_odrEndpoint, error)) return false; } std::vector msg; msg.push_back("set"); msg.push_back(module); msg.push_back(parameter); std::stringstream ss; ss << value; msg.push_back(ss.str()); // send the message if (!SendMessage(m_pReqSocket, msg, error)) { // destroy the socket according to the "Lazy Pirate Pattern" in // the zmq guide m_pReqSocket->close(); delete m_pReqSocket; m_pReqSocket = NULL; return false; } // wait for reply if (!RecvAll(m_pReqSocket, msg, m_timeoutMs, error)) return false; return ParseSetReply(msg, error); } bool COdrModCtrl::ParseSetReply(const std::vector &msg, std::string &error) { error = ""; if (msg.size() < 1) error = "Bad reply format"; else if (msg.size() == 1 && msg[0] == "ok") return true; else if (msg.size() == 2 && msg[0] == "fail") { error = msg[1]; return false; } else { error = "Bad reply format"; return false; } } template bool COdrModCtrl::DoGet(const std::string module, const std::string parameter, Type &value, std::string &error) { if (m_pReqSocket == NULL) { m_pReqSocket = new zmq::socket_t(*m_pContext, ZMQ_REQ); if (!ConnectSocket(m_pReqSocket, m_odrEndpoint, error)) return false; } std::vector msg; msg.push_back("get"); msg.push_back(module); msg.push_back(parameter); // send the message if (!SendMessage(m_pReqSocket, msg, error)) { // destroy the socket according to the "Lazy Pirate Pattern" // in the zmq guide m_pReqSocket->close(); delete m_pReqSocket; m_pReqSocket = NULL; return false; } // wait for reply if (!RecvAll(m_pReqSocket, msg, m_timeoutMs, error)) return false; return ParseGetReply(msg, value, error); } template bool COdrModCtrl::ParseGetReply(const std::vector &msg, Type &value, std::string &error) { error = ""; if (msg.size() < 1) error = "Bad reply format"; else if (msg.size() == 1) { std::stringstream ss(msg[0]); ss >> value; return true; } else if (msg.size() == 2 && msg[0] == "fail") { error = msg[1]; return false; } else { error = "Bad reply format"; return false; } } bool COdrModCtrl::ConnectSocket(zmq::socket_t *pSocket, const std::string endpoint, std::string &error) { error = ""; try { int hwm = 1; int linger = 0; pSocket->setsockopt(ZMQ_RCVHWM, &hwm, sizeof(hwm)); pSocket->setsockopt(ZMQ_SNDHWM, &hwm, sizeof(hwm)); pSocket->setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); pSocket->connect(endpoint.c_str()); return true; } catch(zmq::error_t &ex) { error = "Failed to connect: " + endpoint + std::string(". ZMQ: " + std::string(ex.what())); return false; } } bool COdrModCtrl::SendMessage(zmq::socket_t* pSocket, const std::vector &message, std::string &error) { error = ""; try { std::vector::size_type i = 0; for ( ; i < message.size() - 1; i++) { zmq::message_t zmqMsg(message[i].length()); memcpy ((void*) zmqMsg.data(), message[i].data(), message[i].length()); pSocket->send(zmqMsg, ZMQ_SNDMORE); } zmq::message_t zmqMsg(message[i].length()); memcpy ((void*) zmqMsg.data(), message[i].data(), message[i].length()); pSocket->send(zmqMsg, 0); return true; } catch(zmq::error_t &ex) { error = "ZMQ send error: " + std::string(ex.what()); return false; } } bool COdrModCtrl::RecvAll(zmq::socket_t* pSocket, std::vector &message, unsigned int timeoutMs, std::string &error) { error = ""; message.clear(); int more = -1; size_t more_size = sizeof(more); zmq::pollitem_t pollItems[] = { {*pSocket, 0, ZMQ_POLLIN, 0} }; zmq::poll(&pollItems[0], 1, timeoutMs); while (more != 0) { if (pollItems[0].revents & ZMQ_POLLIN) { zmq::message_t msg; pSocket->recv(&msg); message.push_back(std::string((char*)msg.data(), msg.size())); pSocket->getsockopt(ZMQ_RCVMORE, &more, &more_size); } else { error = "Receive timeout"; return false; } } return true; } Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/OdrModCtrl.hpp000066400000000000000000000056751475762153200252600ustar00rootroot00000000000000/** * This is an interface for the zmq ctrl API of the odr-dabmod. * The class is intended for clients that wish to control the odr-mod. * * Copyright (c) 2015 by Jörgen Scott (jorgen.scott@paneda.se) * * ODR-DabMod 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. * * ODR-DabMod 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 ODR-DabMod. If not, see . **/ #pragma once #include #include #include #include class COdrModCtrl { public: // ctors COdrModCtrl(zmq::context_t *pContext, std::string odrEndpoint, unsigned int timeoutMs); virtual ~COdrModCtrl(); // All methods return true if successful, when false check the error // string. // // IMPORTANT! All methods must be accessed from the same thread. // // For a detailed description of the various parameters, see // example.ini. virtual bool Ping(void); virtual bool GetDigitalGain(double &gain, std::string &error); virtual bool GetTxGain(double &gain, std::string &error); virtual bool GetTxFrequency(double &freqHz, std::string &error); virtual bool GetMuting(bool &mute, std::string &error); virtual bool GetStaticDelay(uint32_t &delayUs, std::string &error); virtual bool SetDigitalGain(const double gain, std::string &error); virtual bool SetTxGain(const double gain, std::string &error); virtual bool SetTxFrequency(const double freqHz, std::string &error); virtual bool SetMuting(const bool mute, std::string &error); virtual bool SetStaticDelay(const int32_t delayUs, std::string &error); private: // methods template bool DoSet(const std::string module, const std::string parameter, const Type value, std::string &error); bool ParseSetReply(const std::vector &msg, std::string &error); template bool DoGet(const std::string module, const std::string parameter, Type &value, std::string &error); template bool ParseGetReply(const std::vector &msg, Type &value, std::string &error); bool ConnectSocket(zmq::socket_t *pSocket, const std::string endpoint, std::string &error); bool SendMessage(zmq::socket_t* pSocket, const std::vector &message, std::string &error); bool RecvAll(zmq::socket_t* pSocket, std::vector &message, unsigned int timeoutMs, std::string &error); // data zmq::context_t *m_pContext; std::string m_odrEndpoint; uint32_t m_timeoutMs; zmq::socket_t *m_pReqSocket; }; Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/test/000077500000000000000000000000001475762153200235005ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/test/CMakeLists.txt000077500000000000000000000013441475762153200262450ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.6) project (ctrl_test) ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK -D_SCL_SECURE_NO_WARNINGS) set(BOOST_LIBRARYDIR) set(BOOST_INCLUDEDIR) set(BOOST_USE_MULTITHREADED ON) set(BOOST_USE_STATIC_LIBS ON) set(BOOST_MIN_VERSION 1.55) find_package( Boost ${BOOST_MIN_VERSION} REQUIRED unit_test_framework system ) set(PROJECT_TEST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ctrl_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../OdrModCtrl.cpp ) include_directories( ${PROJECT_SOURCE_DIR}/../ ) link_directories (/usr/local/lib) add_executable(${PROJECT_NAME} ${PROJECT_TEST_SRCS}) target_link_libraries(${PROJECT_NAME} zmq ${Boost_LIBRARIES} ) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/test/README000066400000000000000000000005551475762153200243650ustar00rootroot00000000000000Instructions for zmq ctrl api test program Dependencies boost, zmq (and cpp binding through zmq.hpp) Build instruction (make sure your in the directory of this file) * mkdir build * cd build * cmake ../ * make Run * make sure the ODR-DABMOD is started and that zmq ctrl api is enabled * make sure the zmq endpoint matches (see ctrl_test.cpp) * run the ctrl_test Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/cpp/test/ctrl_test.cpp000066400000000000000000000053271475762153200262160ustar00rootroot00000000000000/** * This is a test program for the zmq ctrl API of the odr-dabmod. * * Copyright (c) 2015 by Jörgen Scott (jorgen.scott@paneda.se) * * This file is part of CtrlTest. * * ODR-DabMod 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. * * ODR-DabMod 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 ODR-DabMod. If not, see . **/ #define BOOST_TEST_MODULE "C++ unit tests for ODR-DabMod ZMQ Remote Control" #include #include "OdrModCtrl.hpp" // Variables used in the test suite struct TemplateVars { std::string error; zmq::context_t context; COdrModCtrl modCtrl; // NOTE: Make sure odr-dabmod is started before running the test and // that the zmq endpoint matches. TemplateVars() : context(1), modCtrl(&context, "tcp://127.0.0.1:9400", 1000) {} ~TemplateVars() {} }; // Note. ODR-DabMod does not validate parameters therefore there are no tests // made for setting invalid parameters. BOOST_FIXTURE_TEST_SUITE(test_template1, TemplateVars) BOOST_AUTO_TEST_CASE (Ping) { BOOST_CHECK(modCtrl.Ping() == true); } BOOST_AUTO_TEST_CASE (DigitalGain) { BOOST_CHECK(modCtrl.SetDigitalGain(0.5, error) == true); double value; BOOST_CHECK(modCtrl.GetDigitalGain(value, error) == true); BOOST_CHECK(value == 0.5); } BOOST_AUTO_TEST_CASE (TxGain) { BOOST_CHECK(modCtrl.SetTxGain(50, error) == true); double value; BOOST_CHECK(modCtrl.GetTxGain(value, error) == true); BOOST_CHECK(value == 50); } BOOST_AUTO_TEST_CASE (TxFrequency) { BOOST_CHECK(modCtrl.SetTxFrequency(234208000, error) == true); double value; BOOST_CHECK(modCtrl.GetTxFrequency(value, error) == true); BOOST_CHECK(value == 234208000); } BOOST_AUTO_TEST_CASE (Muting) { BOOST_CHECK(modCtrl.SetMuting(true, error) == true); bool value; BOOST_CHECK(modCtrl.GetMuting(value, error) == true); BOOST_CHECK(value == true); BOOST_CHECK(modCtrl.SetMuting(false, error) == true); } BOOST_AUTO_TEST_CASE (StaticDelay) { // reset first (by setting out of range value) or else test // will fail on successive runs BOOST_CHECK(modCtrl.SetStaticDelay(100000, error) == true); BOOST_CHECK(modCtrl.SetStaticDelay(45000, error) == true); uint32_t value; BOOST_CHECK(modCtrl.GetStaticDelay(value, error) == true); BOOST_CHECK(value == 45000); } BOOST_AUTO_TEST_SUITE_END() Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/json_remote_server.py000077500000000000000000000112731475762153200262320ustar00rootroot00000000000000#!/usr/bin/env python # # This is an example program that illustrates # how to interact with the zeromq remote control # using JSON. # # LICENSE: see bottom of file import sys import zmq from pprint import pprint import json import re from http.server import BaseHTTPRequestHandler, HTTPServer import time re_url = re.compile(r"/([a-zA-Z0-9]+).json") ZMQ_REMOTE = "tcp://localhost:9400" HTTP_HOSTNAME = "localhost" HTTP_PORT = 8080 class DabMuxServer(BaseHTTPRequestHandler): def err500(self, message): self.send_response(500) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(json.dumps({"error": message}).encode()) def do_GET(self): m = re_url.match(self.path) if m: sock = context.socket(zmq.REQ) poller = zmq.Poller() poller.register(sock, zmq.POLLIN) sock.connect(ZMQ_REMOTE) sock.send(b"ping") socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: data = sock.recv() if data != b"ok": print(f"Received {data} to ping!", file=sys.stderr) self.err500("ping failure") return else: print("ZMQ error: ping timeout", file=sys.stderr) self.err500("ping timeout") return sock.send(b"showjson", flags=zmq.SNDMORE) sock.send(m.group(1).encode()) socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: data = sock.recv_multipart() print("Received: {}".format(len(data)), file=sys.stderr) parts = [] for i, part_data in enumerate(data): part = part_data.decode() print(" RX {}: {}".format(i, part.replace('\n',' ')), file=sys.stderr) if i == 0 and part != "fail": self.send_response(200) self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(part_data) return parts.append(part) self.err500("data error " + " ".join(parts)) return else: print("ZMQ error: timeout", file=sys.stderr) self.err500("timeout") return else: self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() self.wfile.write("""ODR-DabMod RC HTTP server\n""".encode()) self.wfile.write("""\n""".encode()) for mod in ("sdr", "tist", "modulator", "tii", "ofdm", "gain", "guardinterval"): self.wfile.write(f"""

{mod}.json

\n""".encode()) self.wfile.write("""\n""".encode()) if __name__ == "__main__": context = zmq.Context() webServer = HTTPServer((HTTP_HOSTNAME, HTTP_PORT), DabMuxServer) print("Server started http://%s:%s" % (HTTP_HOSTNAME, HTTP_PORT)) try: webServer.serve_forever() except KeyboardInterrupt: pass webServer.server_close() context.destroy(linger=5) # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to Opendigitalradio-ODR-DabMod-f7eedef/doc/zmq-ctrl/zmq_remote.py000077500000000000000000000054351475762153200245050ustar00rootroot00000000000000#!/usr/bin/env python # # This is an example program that illustrates # how to interact with the zeromq remote control # # LICENSE: see bottom of file import sys import zmq context = zmq.Context() sock = context.socket(zmq.REQ) poller = zmq.Poller() poller.register(sock, zmq.POLLIN) if len(sys.argv) < 2: print("Usage: program url cmd [args...]", file=sys.stderr) sys.exit(1) sock.connect(sys.argv[1]) message_parts = sys.argv[2:] # first do a ping test print("ping", file=sys.stderr) sock.send(b"ping") socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: data = sock.recv_multipart() print("Received: {}".format(len(data)), file=sys.stderr) for i,part in enumerate(data): print(" {}".format(part), file=sys.stderr) for i, part in enumerate(message_parts): if i == len(message_parts) - 1: f = 0 else: f = zmq.SNDMORE print("Send {}({}): '{}'".format(i, f, part), file=sys.stderr) sock.send(part.encode(), flags=f) data = sock.recv_multipart() print("Received: {}".format(len(data)), file=sys.stderr) for i, part in enumerate(data): if message_parts[0] == 'showjson': # This allows you to pipe the JSON into another tool print(part.decode()) else: print(" RX {}: {}".format(i, part.decode().replace('\n',' ')), file=sys.stderr) else: print("ZMQ error: timeout", file=sys.stderr) context.destroy(linger=5) # This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to Opendigitalradio-ODR-DabMod-f7eedef/fpm/000077500000000000000000000000001475762153200202035ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/fpm/LICENSE000066400000000000000000000020551475762153200212120ustar00rootroot00000000000000MIT License Copyright (c) 2019 Mike Lankamp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Opendigitalradio-ODR-DabMod-f7eedef/fpm/README.md000066400000000000000000000050571475762153200214710ustar00rootroot00000000000000# fpm A C++ header-only fixed-point math library. "fpm" stands for "fixed-point math". It is designed to serve as a drop-in replacement for floating-point types and aims to provide as much of the standard library's functionality as possible with exclusively integers. `fpm` requires C++11 or higher. [![Build Status](https://travis-ci.org/MikeLankamp/fpm.svg?branch=master)](https://travis-ci.org/MikeLankamp/fpm) [![Build status](https://ci.appveyor.com/api/projects/status/0velpwqk38spu412?svg=true)](https://ci.appveyor.com/project/MikeLankamp/fpm) `fpm` is designed to guard against accidental conversion to and from floats and supports many of the standard C++ maths functions, including trigonometry, power and logarithmic functions, with performance and accuracy generally comparable to alternative libraries. ## Why use fixed-point math? There are several reasons why you can not or choose not to use floating-point math, but still want a similar type: * Your target platform lacks an FPU, does not support floating-point operations or its floating-point operations are considerably slower than fixed-point integer operations. * You require deterministic calculations. If any of these reasons apply for you, and your problem domain has a clearly outlined range and required resolution, then fixed-point numbers might be a solution for you. ## Quick Start To use `fpm`, include its header `` and use the `fpm::fixed_16_16`, `fpm::fixed_24_8` or `fpm::fixed_8_24` types as if they were native floating-pointer types: ```c++ #include // For fpm::fixed_16_16 #include // For fpm::cos #include // For fpm::operator<< #include // For std::cin, std::cout int main() { std::cout << "Please input a number: "; fpm::fixed_16_16 x; std::cin >> x; std::cout << "The cosine of " << x << " radians is: " << cos(x) << std::endl; return 0; } ``` To use the fixed-point equivalents of the `` functions such as `sqrt`, `sin` and `log`, include the header ``. To stream fixed-point values to or from streams, include the header ``. ## Documentation Please refer to the [documentation](docs/index.md) for detailed information how to use `fpm`, or skip straight to the [performance](docs/performance.md) or [accuracy](docs/accuracy.md) results. ## Contributions This library is a work-in-progress. We welcome any contributions that improve the functional coverage or the performance or accuracy of the mathematical functions. ## License See the [LICENSE](LICENSE) file Opendigitalradio-ODR-DabMod-f7eedef/fpm/fixed.hpp000066400000000000000000000457341475762153200220300ustar00rootroot00000000000000#ifndef FPM_FIXED_HPP #define FPM_FIXED_HPP #include #include #include #include #include #include namespace fpm { //! Fixed-point number type //! \tparam BaseType the base integer type used to store the fixed-point number. This can be a signed or unsigned type. //! \tparam IntermediateType the integer type used to store intermediate results during calculations. //! \tparam FractionBits the number of bits of the BaseType used to store the fraction //! \tparam EnableRounding enable rounding of LSB for multiplication, division, and type conversion template class fixed { static_assert(std::is_integral::value, "BaseType must be an integral type"); static_assert(FractionBits > 0, "FractionBits must be greater than zero"); static_assert(FractionBits <= sizeof(BaseType) * 8 - 1, "BaseType must at least be able to contain entire fraction, with space for at least one integral bit"); static_assert(sizeof(IntermediateType) > sizeof(BaseType), "IntermediateType must be larger than BaseType"); static_assert(std::is_signed::value == std::is_signed::value, "IntermediateType must have same signedness as BaseType"); // Although this value fits in the BaseType in terms of bits, if there's only one integral bit, this value // is incorrect (flips from positive to negative), so we must extend the size to IntermediateType. static constexpr IntermediateType FRACTION_MULT = IntermediateType(1) << FractionBits; struct raw_construct_tag {}; constexpr inline fixed(BaseType val, raw_construct_tag) noexcept : m_value(val) {} public: inline fixed() noexcept = default; // Converts an integral number to the fixed-point type. // Like static_cast, this truncates bits that don't fit. template ::value>::type* = nullptr> constexpr inline explicit fixed(T val) noexcept : m_value(static_cast(val * FRACTION_MULT)) {} // Converts an floating-point number to the fixed-point type. // Like static_cast, this truncates bits that don't fit. template ::value>::type* = nullptr> constexpr inline explicit fixed(T val) noexcept : m_value(static_cast((EnableRounding) ? (val >= 0.0) ? (val * FRACTION_MULT + T{0.5}) : (val * FRACTION_MULT - T{0.5}) : (val * FRACTION_MULT))) {} // Constructs from another fixed-point type with possibly different underlying representation. // Like static_cast, this truncates bits that don't fit. template constexpr inline explicit fixed(fixed val) noexcept : m_value(from_fixed_point(val.raw_value()).raw_value()) {} // Explicit conversion to a floating-point type template ::value>::type* = nullptr> constexpr inline explicit operator T() const noexcept { return static_cast(m_value) / FRACTION_MULT; } // Explicit conversion to an integral type template ::value>::type* = nullptr> constexpr inline explicit operator T() const noexcept { return static_cast(m_value / FRACTION_MULT); } // Returns the raw underlying value of this type. // Do not use this unless you know what you're doing. constexpr inline BaseType raw_value() const noexcept { return m_value; } //! Constructs a fixed-point number from another fixed-point number. //! \tparam NumFractionBits the number of bits used by the fraction in \a value. //! \param value the integer fixed-point number template FractionBits)>::type* = nullptr> static constexpr inline fixed from_fixed_point(T value) noexcept { // To correctly round the last bit in the result, we need one more bit of information. // We do this by multiplying by two before dividing and adding the LSB to the real result. return (EnableRounding) ? fixed(static_cast( value / (T(1) << (NumFractionBits - FractionBits)) + (value / (T(1) << (NumFractionBits - FractionBits - 1)) % 2)), raw_construct_tag{}) : fixed(static_cast(value / (T(1) << (NumFractionBits - FractionBits))), raw_construct_tag{}); } template ::type* = nullptr> static constexpr inline fixed from_fixed_point(T value) noexcept { return fixed(static_cast( value * (T(1) << (FractionBits - NumFractionBits))), raw_construct_tag{}); } // Constructs a fixed-point number from its raw underlying value. // Do not use this unless you know what you're doing. static constexpr inline fixed from_raw_value(BaseType value) noexcept { return fixed(value, raw_construct_tag{}); } // // Constants // static constexpr fixed e() { return from_fixed_point<61>(6267931151224907085ll); } static constexpr fixed pi() { return from_fixed_point<61>(7244019458077122842ll); } static constexpr fixed half_pi() { return from_fixed_point<62>(7244019458077122842ll); } static constexpr fixed two_pi() { return from_fixed_point<60>(7244019458077122842ll); } // // Arithmetic member operators // constexpr inline fixed operator-() const noexcept { return fixed::from_raw_value(-m_value); } inline fixed& operator+=(const fixed& y) noexcept { m_value += y.m_value; return *this; } template ::value>::type* = nullptr> inline fixed& operator+=(I y) noexcept { m_value += y * FRACTION_MULT; return *this; } inline fixed& operator-=(const fixed& y) noexcept { m_value -= y.m_value; return *this; } template ::value>::type* = nullptr> inline fixed& operator-=(I y) noexcept { m_value -= y * FRACTION_MULT; return *this; } inline fixed& operator*=(const fixed& y) noexcept { if (EnableRounding){ // Normal fixed-point multiplication is: x * y / 2**FractionBits. // To correctly round the last bit in the result, we need one more bit of information. // We do this by multiplying by two before dividing and adding the LSB to the real result. auto value = (static_cast(m_value) * y.m_value) / (FRACTION_MULT / 2); m_value = static_cast((value / 2) + (value % 2)); } else { auto value = (static_cast(m_value) * y.m_value) / FRACTION_MULT; m_value = static_cast(value); } return *this; } template ::value>::type* = nullptr> inline fixed& operator*=(I y) noexcept { m_value *= y; return *this; } inline fixed& operator/=(const fixed& y) noexcept { assert(y.m_value != 0); if (EnableRounding){ // Normal fixed-point division is: x * 2**FractionBits / y. // To correctly round the last bit in the result, we need one more bit of information. // We do this by multiplying by two before dividing and adding the LSB to the real result. auto value = (static_cast(m_value) * FRACTION_MULT * 2) / y.m_value; m_value = static_cast((value / 2) + (value % 2)); } else { auto value = (static_cast(m_value) * FRACTION_MULT) / y.m_value; m_value = static_cast(value); } return *this; } template ::value>::type* = nullptr> inline fixed& operator/=(I y) noexcept { m_value /= y; return *this; } private: BaseType m_value; }; // // Convenience typedefs // using fixed_16_16 = fixed; using fixed_24_8 = fixed; using fixed_8_24 = fixed; // // Addition // template constexpr inline fixed operator+(const fixed& x, const fixed& y) noexcept { return fixed(x) += y; } template ::value>::type* = nullptr> constexpr inline fixed operator+(const fixed& x, T y) noexcept { return fixed(x) += y; } template ::value>::type* = nullptr> constexpr inline fixed operator+(T x, const fixed& y) noexcept { return fixed(y) += x; } // // Subtraction // template constexpr inline fixed operator-(const fixed& x, const fixed& y) noexcept { return fixed(x) -= y; } template ::value>::type* = nullptr> constexpr inline fixed operator-(const fixed& x, T y) noexcept { return fixed(x) -= y; } template ::value>::type* = nullptr> constexpr inline fixed operator-(T x, const fixed& y) noexcept { return fixed(x) -= y; } // // Multiplication // template constexpr inline fixed operator*(const fixed& x, const fixed& y) noexcept { return fixed(x) *= y; } template ::value>::type* = nullptr> constexpr inline fixed operator*(const fixed& x, T y) noexcept { return fixed(x) *= y; } template ::value>::type* = nullptr> constexpr inline fixed operator*(T x, const fixed& y) noexcept { return fixed(y) *= x; } // // Division // template constexpr inline fixed operator/(const fixed& x, const fixed& y) noexcept { return fixed(x) /= y; } template ::value>::type* = nullptr> constexpr inline fixed operator/(const fixed& x, T y) noexcept { return fixed(x) /= y; } template ::value>::type* = nullptr> constexpr inline fixed operator/(T x, const fixed& y) noexcept { return fixed(x) /= y; } // // Comparison operators // template constexpr inline bool operator==(const fixed& x, const fixed& y) noexcept { return x.raw_value() == y.raw_value(); } template constexpr inline bool operator!=(const fixed& x, const fixed& y) noexcept { return x.raw_value() != y.raw_value(); } template constexpr inline bool operator<(const fixed& x, const fixed& y) noexcept { return x.raw_value() < y.raw_value(); } template constexpr inline bool operator>(const fixed& x, const fixed& y) noexcept { return x.raw_value() > y.raw_value(); } template constexpr inline bool operator<=(const fixed& x, const fixed& y) noexcept { return x.raw_value() <= y.raw_value(); } template constexpr inline bool operator>=(const fixed& x, const fixed& y) noexcept { return x.raw_value() >= y.raw_value(); } namespace detail { // Number of base-10 digits required to fully represent a number of bits static constexpr int max_digits10(int bits) { // 8.24 fixed-point equivalent of (int)ceil(bits * std::log10(2)); using T = long long; return static_cast((T{bits} * 5050445 + (T{1} << 24) - 1) >> 24); } // Number of base-10 digits that can be fully represented by a number of bits static constexpr int digits10(int bits) { // 8.24 fixed-point equivalent of (int)(bits * std::log10(2)); using T = long long; return static_cast((T{bits} * 5050445) >> 24); } } // namespace detail } // namespace fpm // Specializations for customization points namespace std { template struct hash> { using argument_type = fpm::fixed; using result_type = std::size_t; result_type operator()(argument_type arg) const noexcept(noexcept(std::declval>()(arg.raw_value()))) { return m_hash(arg.raw_value()); } private: std::hash m_hash; }; template struct numeric_limits> { static constexpr bool is_specialized = true; static constexpr bool is_signed = std::numeric_limits::is_signed; static constexpr bool is_integer = false; static constexpr bool is_exact = true; static constexpr bool has_infinity = false; static constexpr bool has_quiet_NaN = false; static constexpr bool has_signaling_NaN = false; static constexpr std::float_denorm_style has_denorm = std::denorm_absent; static constexpr bool has_denorm_loss = false; static constexpr std::float_round_style round_style = std::round_to_nearest; static constexpr bool is_iec_559 = false; static constexpr bool is_bounded = true; static constexpr bool is_modulo = std::numeric_limits::is_modulo; static constexpr int digits = std::numeric_limits::digits; // Any number with `digits10` significant base-10 digits (that fits in // the range of the type) is guaranteed to be convertible from text and // back without change. Worst case, this is 0.000...001, so we can only // guarantee this case. Nothing more. static constexpr int digits10 = 1; // This is equal to max_digits10 for the integer and fractional part together. static constexpr int max_digits10 = fpm::detail::max_digits10(std::numeric_limits::digits - F) + fpm::detail::max_digits10(F); static constexpr int radix = 2; static constexpr int min_exponent = 1 - F; static constexpr int min_exponent10 = -fpm::detail::digits10(F); static constexpr int max_exponent = std::numeric_limits::digits - F; static constexpr int max_exponent10 = fpm::detail::digits10(std::numeric_limits::digits - F); static constexpr bool traps = true; static constexpr bool tinyness_before = false; static constexpr fpm::fixed lowest() noexcept { return fpm::fixed::from_raw_value(std::numeric_limits::lowest()); }; static constexpr fpm::fixed min() noexcept { return lowest(); } static constexpr fpm::fixed max() noexcept { return fpm::fixed::from_raw_value(std::numeric_limits::max()); }; static constexpr fpm::fixed epsilon() noexcept { return fpm::fixed::from_raw_value(1); }; static constexpr fpm::fixed round_error() noexcept { return fpm::fixed(1) / 2; }; static constexpr fpm::fixed denorm_min() noexcept { return min(); } }; template constexpr bool numeric_limits>::is_specialized; template constexpr bool numeric_limits>::is_signed; template constexpr bool numeric_limits>::is_integer; template constexpr bool numeric_limits>::is_exact; template constexpr bool numeric_limits>::has_infinity; template constexpr bool numeric_limits>::has_quiet_NaN; template constexpr bool numeric_limits>::has_signaling_NaN; template constexpr std::float_denorm_style numeric_limits>::has_denorm; template constexpr bool numeric_limits>::has_denorm_loss; template constexpr std::float_round_style numeric_limits>::round_style; template constexpr bool numeric_limits>::is_iec_559; template constexpr bool numeric_limits>::is_bounded; template constexpr bool numeric_limits>::is_modulo; template constexpr int numeric_limits>::digits; template constexpr int numeric_limits>::digits10; template constexpr int numeric_limits>::max_digits10; template constexpr int numeric_limits>::radix; template constexpr int numeric_limits>::min_exponent; template constexpr int numeric_limits>::min_exponent10; template constexpr int numeric_limits>::max_exponent; template constexpr int numeric_limits>::max_exponent10; template constexpr bool numeric_limits>::traps; template constexpr bool numeric_limits>::tinyness_before; } #endif Opendigitalradio-ODR-DabMod-f7eedef/fpm/ios.hpp000066400000000000000000000610631475762153200215140ustar00rootroot00000000000000#ifndef FPM_IOS_HPP #define FPM_IOS_HPP #include "fixed.hpp" #include "math.hpp" #include #include #include #include #include #include #include namespace fpm { template std::basic_ostream& operator<<(std::basic_ostream& os, fixed x) noexcept { const auto uppercase = ((os.flags() & std::ios_base::uppercase) != 0); const auto showpoint = ((os.flags() & std::ios_base::showpoint) != 0); const auto adjustfield = (os.flags() & std::ios_base::adjustfield); const auto width = os.width(); const auto& ctype = std::use_facet>(os.getloc()); const auto& numpunct = std::use_facet>(os.getloc()); auto floatfield = (os.flags() & std::ios_base::floatfield); auto precision = os.precision(); auto show_trailing_zeros = true; auto use_significant_digits = false; // Invalid precision? Reset to the default if (precision < 0) { precision = 6; } // Output buffer. Needs to be big enough for the formatted number without padding. // Optional prefixes (i.e. "+"/"-", decimal separator, exponent "e+/-" and/or "0x"). constexpr auto worst_case_constant_size = 6; // Maximum number of digits from the base type (covers integral + fractional digits) constexpr auto worst_case_digit_count = std::numeric_limits::digits10 + 2; // Exponent suffixes (i.e. maximum digits based on log of the base type size). // Needs a log10, but that isn't constexpr, so we're over-allocating on the stack. Can't hurt. constexpr auto worst_case_suffix_size = std::numeric_limits::digits; // Double the digit count: in the worst case the thousands grouping add a character per digit. using buffer_t = std::array; buffer_t buffer; // Output cursor auto end = buffer.begin(); // Keep track of the start of "internal" padding typename buffer_t::iterator internal_pad = buffer.end(); // Representation of a number. // The value of the number is: raw / divisor * (10|2) ^ exponent // The base of the exponent is 2 in hexfloat mode, or 10 otherwise. struct number_t { I raw; // raw fixed-point value I divisor; // the divisor indicating the place of the decimal point int exponent; // the exponent applied }; // Convert a value without exponent to scientific representation // where the part before the decimal point is less than 10. const auto as_scientific = [](number_t value) { assert(value.exponent == 0); if (value.raw > 0) { while (value.raw / 10 >= value.divisor) { value.divisor *= 10; ++value.exponent; } while (value.raw < value.divisor) { value.raw *= 10; --value.exponent; } } return value; }; number_t value = { x.raw_value(), I{1} << F, 0}; auto base = B{10}; // First write the sign if (value.raw < 0) { *end++ = ctype.widen('-'); value.raw = -value.raw; internal_pad = end; } else if (os.flags() & std::ios_base::showpos) { *end++ = ctype.widen('+'); internal_pad = end; } assert(value.raw >= 0); switch (floatfield) { case std::ios_base::fixed | std::ios_base::scientific: // Hexadecimal mode: figure out the hexadecimal exponent and write "0x" if (value.raw > 0) { auto bit = detail::find_highest_bit(value.raw); value.exponent = bit - F; // exponent is applied to base 2 value.divisor = I{1} << bit; // divisor is at the highest bit, ensuring it starts with "1." precision = (bit + 3) / 4; // precision is number of nibbles, so we show all of them } base = 16; show_trailing_zeros = false; // Always strip trailing zeros in hexfloat mode *end++ = ctype.widen('0'); *end++ = ctype.widen(uppercase ? 'X' : 'x'); break; case std::ios_base::scientific: // Scientific mode, normalize value to scientific notation value = as_scientific(value); break; case std::ios_base::fixed: // Fixed mode. Nothing to do. break; default: { // "auto" mode: figure out the exponent const number_t sci_value = as_scientific(value); // Now `precision` indicates the number of *significant digits* (not fractional digits). use_significant_digits = true; precision = std::max(precision, 1); if (sci_value.exponent >= precision || sci_value.exponent < -4) { // Display as scientific format floatfield = std::ios_base::scientific; value = sci_value; } else { // Display as fixed format. // "showpoint" indicates whether or not we show trailing zeros floatfield = std::ios_base::fixed; show_trailing_zeros = showpoint; } break; } }; // If we didn't write a sign, any internal padding starts here // (after a potential "0x" for hexfloats). if (internal_pad == buffer.end()) { internal_pad = end; } // Separate out the integral part of the number I integral = value.raw / value.divisor; value.raw %= value.divisor; // Here we start printing the number itself const char* const digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; const auto digits_start = end; // Are we already printing significant digits? (yes if we're not counting significant digits) bool significant_digits = !use_significant_digits; // Print the integral part int last_digit = 0; if (integral == 0) { *end++ = ctype.widen('0'); if (value.raw == 0) { // If the fraction is zero too, all zeros including the integral count // as significant digits. significant_digits = true; } } else { while (integral > 0) { last_digit = integral % base; *end++ = ctype.widen(digits[last_digit]); integral /= base; } std::reverse(digits_start, end); significant_digits = true; } if (use_significant_digits && significant_digits) { // Apparently the integral part was significant; subtract its // length from the remaining significant digits. precision -= (end - digits_start); } // At this point, `value` contains only the fraction and // `precision` holds the number of digits to print. assert(value.raw < value.divisor); assert(precision >= 0); // Location of decimal point typename buffer_t::iterator point = buffer.end(); // Start (and length) of the trailing zeros to insert while printing // By tracking this to print them later instead of actually printing them now, // we can support large precisions with a small printing buffer. typename buffer_t::iterator trailing_zeros_start = buffer.end(); std::streamsize trailing_zeros_count = 0; if (precision > 0) { // Print the fractional part *(point = end++) = numpunct.decimal_point(); for (int i = 0; i < precision; ++i) { if (value.raw == 0) { // The rest of the digits are all zeros, mark them // to be printed in this spot. trailing_zeros_start = end; trailing_zeros_count = precision - i; break; } // Shift the divisor if we can to avoid overflow on the value if (value.divisor % base == 0) { value.divisor /= base; } else { value.raw *= base; } assert(value.divisor > 0); assert(value.raw >= 0); last_digit = (value.raw / value.divisor) % base; value.raw %= value.divisor; *end++ = ctype.widen(digits[last_digit]); if (!significant_digits) { // We're still finding the first significant digit if (last_digit != 0) { // Found it significant_digits = true; } else { // Not yet; increment number of digits to print ++precision; } } } } else if (showpoint) { // No fractional part to print, but we still want the point *(point = end++) = numpunct.decimal_point(); } // Insert `ch` into the output at `position`, updating all references accordingly const auto insert_character = [&](typename buffer_t::iterator position, CharT ch) { assert(position >= buffer.begin() && position < end); std::move_backward(position, end, end + 1); if (point != buffer.end() && position < point) { ++point; } if (trailing_zeros_start != buffer.end() && position < trailing_zeros_start) { ++trailing_zeros_start; } ++end; *position = ch; }; // Round the number: round to nearest bool increment = false; if (value.raw > value.divisor / 2) { // Round up increment = true; } else if (value.raw == value.divisor / 2) { // It's a tie (i.e. "xyzw.5"): round to even increment = ((last_digit % 2) == 1); } if (increment) { auto p = end - 1; // Increment all digits backwards while we see "9" while (p >= digits_start) { if (p == point) { // Skip over the decimal point --p; } if ((*p)++ != ctype.widen('9')) { break; } *p-- = ctype.widen('0'); } if (p < digits_start) { // We've incremented all the way to the start (all 9's), we need to insert the // carried-over 1 from incrementing the last 9. assert(p == digits_start - 1); insert_character(++p, ctype.widen('1')); if (floatfield == std::ios::scientific) { // We just made the integral part equal to 10, so we shift the decimal point // back one place (if any) and tweak the exponent, so that we keep the integer part // less than 10. if (point != buffer.end()) { assert(p + 2 == point); std::swap(*(point - 1), *point); --point; } ++value.exponent; // We've introduced an extra digit so we need to strip the last digit // to maintain the same precision --end; } } if (use_significant_digits && *p == ctype.widen('1') && point != buffer.end()) { // We've converted a leading zero to a 1 so we need to strip the last digit // (behind the decimal point) to maintain the same significant digit count. --end; } } if (point != buffer.end()) { if (!show_trailing_zeros) { // Remove trailing zeros while (*(end - 1) == ctype.widen('0')) { --end; } // Also clear the "trailing zeros to append during printing" range trailing_zeros_start = buffer.end(); trailing_zeros_count = 0; } if (end - 1 == point && trailing_zeros_count == 0 && !showpoint) { // Remove the decimal point, too --end; } } // Apply thousands grouping const auto& grouping = numpunct.grouping(); if (!grouping.empty()) { // Step backwards from the end or decimal point, inserting the // thousands separator at every group interval. const CharT thousands_sep = ctype.widen(numpunct.thousands_sep()); std::size_t group = 0; auto p = point != buffer.end() ? point : end; auto size = static_cast(grouping[group]); while (size > 0 && size < CHAR_MAX && p - digits_start > size) { p -= size; insert_character(p, thousands_sep); if (group < grouping.size() - 1) { size = static_cast(grouping[++group]); } } } // Print the exponent if required assert(floatfield != 0); if (floatfield & std::ios_base::scientific) { // Hexadecimal (%a/%A) or decimal (%e/%E) scientific notation if (floatfield & std::ios_base::fixed) { *end++ = ctype.widen(uppercase ? 'P' : 'p'); } else { *end++ = ctype.widen(uppercase ? 'E' : 'e'); } if (value.exponent < 0) { *end++ = ctype.widen('-'); value.exponent = -value.exponent; } else { *end++ = ctype.widen('+'); } if (floatfield == std::ios_base::scientific) { // In decimal scientific notation (%e/%E), the exponent is at least two digits if (value.exponent < 10) { *end++ = ctype.widen('0'); } } const auto exponent_start = end; if (value.exponent == 0) { *end++ = ctype.widen('0'); } else while (value.exponent > 0) { *end++ = ctype.widen(digits[value.exponent % 10]); value.exponent /= 10; } std::reverse(exponent_start, end); } // Write character `ch` `count` times to the stream const auto sputcn = [&](CharT ch, std::streamsize count){ // Fill a buffer to output larger chunks constexpr std::streamsize chunk_size = 64; std::array fill_buffer; std::fill_n(fill_buffer.begin(), std::min(count, chunk_size), ch); for (std::streamsize size, left = count; left > 0; left -= size) { size = std::min(chunk_size, left); os.rdbuf()->sputn(&fill_buffer[0], size); } }; // Outputs a range of characters, making sure to output the trailing zeros range // if it lies in the specified range const auto put_range = [&](typename buffer_t::const_iterator begin, typename buffer_t::const_iterator end) { assert(end >= begin); if (trailing_zeros_start >= begin && trailing_zeros_start <= end) { // Print range with trailing zeros range in the middle assert(trailing_zeros_count > 0); os.rdbuf()->sputn(&*begin, trailing_zeros_start - begin); sputcn(ctype.widen('0'), trailing_zeros_count); os.rdbuf()->sputn(&*trailing_zeros_start, end - trailing_zeros_start); } else { // Print range as-is os.rdbuf()->sputn(&*begin, end - begin); } }; // Pad the buffer if necessary. // Note that the length of trailing zeros is counted towards the length of the content. const auto content_size = end - buffer.begin() + trailing_zeros_count; if (content_size >= width) { // Buffer needs no padding, output as-is put_range(buffer.begin(), end); } else { const auto pad_size = width - content_size; switch (adjustfield) { case std::ios_base::left: // Content is left-aligned, so output the buffer, followed by the padding put_range(buffer.begin(), end); sputcn(os.fill(), pad_size); break; case std::ios_base::internal: // Content is internally aligned, so output the buffer up to the "internal pad" // point, followed by the padding, followed by the remainder of the buffer. put_range(buffer.begin(), internal_pad); sputcn(os.fill(), pad_size); put_range(internal_pad, end); break; default: // Content is right-aligned, so output the padding, followed by the buffer sputcn(os.fill(), pad_size); put_range(buffer.begin(), end); break; } } // Width is reset after every write os.width(0); return os; } template std::basic_istream& operator>>(std::basic_istream& is, fixed& x) { typename std::basic_istream::sentry sentry(is); if (!sentry) { return is; } const auto& ctype = std::use_facet>(is.getloc()); const auto& numpunct = std::use_facet>(is.getloc()); bool thousands_separator_allowed = false; const bool supports_thousands_separators = !numpunct.grouping().empty(); const auto& is_valid_character = [](char ch) { // Note: allowing ['p', 'i', 'n', 't', 'y'] is technically in violation of the spec (we are emulating std::num_get), // but otherwise we cannot parse hexfloats and "infinity". This is a known issue with the spec (LWG #2381). return std::isxdigit(ch) || ch == 'x' || ch == 'X' || ch == 'p' || ch == 'P' || ch == 'i' || ch == 'I' || ch == 'n' || ch == 'N' || ch == 't' || ch == 'T' || ch == 'y' || ch == 'Y' || ch == '-' || ch == '+'; }; const auto& peek = [&]() { for(;;) { auto ch = is.rdbuf()->sgetc(); if (ch == Traits::eof()) { is.setstate(std::ios::eofbit); return '\0'; } if (ch == numpunct.decimal_point()) { return '.'; } if (ch == numpunct.thousands_sep()) { if (!supports_thousands_separators || !thousands_separator_allowed) { return '\0'; } // Ignore valid thousands separators is.rdbuf()->sbumpc(); continue; } auto res = ctype.narrow(ch, 0); if (!is_valid_character(res)) { // Invalid character: end input return '\0'; } return res; } }; const auto& bump = [&]() { is.rdbuf()->sbumpc(); }; const auto& next = [&]() { bump(); return peek(); }; bool negate = false; auto ch = peek(); if (ch == '-') { negate = true; ch = next(); } else if (ch == '+') { ch = next(); } const char infinity[] = "infinity"; // Must be "inf" or "infinity" int i = 0; while (i < 8 && ch == infinity[i]) { ++i; ch = next(); } if (i > 0) { if (i == 3 || i == 8) { x = negate ? std::numeric_limits>::min() : std::numeric_limits>::max(); } else { is.setstate(std::ios::failbit); } return is; } char exponent_char = 'e'; int base = 10; constexpr auto NoFraction = std::numeric_limits::max(); std::size_t fraction_start = NoFraction; std::vector significand; if (ch == '0') { ch = next(); if (ch == 'x' || ch == 'X') { // Hexfloat exponent_char = 'p'; base = 16; ch = next(); } else { significand.push_back(0); } } // Parse the significand thousands_separator_allowed = true; for (;; ch = next()) { if (ch == '.') { if (fraction_start != NoFraction) { // Double decimal point. Stop parsing. break; } fraction_start = significand.size(); thousands_separator_allowed = false; } else { unsigned char val = base; if (ch >= '0' && ch <= '9') { val = ch - '0'; } else if (ch >= 'a' && ch <= 'f') { val = ch - 'a' + 10; } else if (ch >= 'A' && ch <= 'F') { val = ch - 'A' + 10; } if (val < 0 || val >= base) { break; } significand.push_back(val); } } if (significand.empty()) { // We need a significand is.setstate(std::ios::failbit); return is; } thousands_separator_allowed = false; if (fraction_start == NoFraction) { // If we haven't seen a fraction yet, place it at the end of the significand fraction_start = significand.size(); } // Parse the exponent bool exponent_overflow = false; std::size_t exponent = 0; bool exponent_negate = false; if (std::tolower(ch) == exponent_char) { ch = next(); if (ch == '-') { exponent_negate = true; ch = next(); } else if (ch == '+') { ch = next(); } bool parsed = false; while (std::isdigit(ch)) { if (exponent <= std::numeric_limits::max() / 10) { exponent = exponent * 10 + (ch - '0'); } else { exponent_overflow = true; } parsed = true; ch = next(); } if (!parsed) { // If the exponent character is given, the exponent value may not be empty is.setstate(std::ios::failbit); return is; } } // We've parsed all we need. Construct the value. if (exponent_overflow) { // Absolute exponent is too large if (std::all_of(significand.begin(), significand.end(), [](unsigned char x){ return x == 0; })) { // Significand is zero. Exponent doesn't matter. x = fixed(0); } else if (exponent_negate) { // A huge negative exponent approaches 0. x = fixed::from_raw_value(0); } else { // A huge positive exponent approaches infinity. x = std::numeric_limits>::max(); } return is; } // Shift the fraction offset according to exponent { const auto exponent_mult = (base == 10) ? 1: 4; if (exponent_negate) { const auto adjust = std::min(exponent / exponent_mult, fraction_start); fraction_start -= adjust; exponent -= adjust * exponent_mult; } else { const auto adjust = std::min(exponent / exponent_mult, significand.size() - fraction_start); fraction_start += adjust; exponent -= adjust * exponent_mult; } } constexpr auto IsSigned = std::is_signed::value; constexpr auto IntBits = sizeof(B) * 8 - F - (IsSigned ? 1 : 0); constexpr auto MaxInt = (I{1} << IntBits) - 1; constexpr auto MaxFraction = (I{1} << F) - 1; constexpr auto MaxValue = (I{1} << sizeof(B) * 8) - 1; // Parse the integer part I integer = 0; for (std::size_t i = 0; i < fraction_start; ++i) { if (integer > MaxInt / base) { // Overflow x = negate ? std::numeric_limits>::min() : std::numeric_limits>::max(); return is; } assert(significand[i] < base); integer = integer * base + significand[i]; } // Parse the fractional part I fraction = 0; I divisor = 1; for (std::size_t i = fraction_start; i < significand.size(); ++i) { assert(significand[i] < base); if (divisor > MaxFraction / base) { // We're done break; } fraction = fraction * base + significand[i]; divisor *= base; } // Construct the value from the parsed parts I raw_value = (integer << F) + (fraction << F) / divisor; // Apply remaining exponent if (exponent_char == 'p') { // Base-2 exponent if (exponent_negate) { raw_value >>= exponent; } else { raw_value <<= exponent; } } else { // Base-10 exponent if (exponent_negate) { I remainder = 0; for (std::size_t e = 0; e < exponent; ++e) { remainder = raw_value % 10; raw_value /= 10; } raw_value += remainder / 5; } else { for (std::size_t e = 0; e < exponent; ++e) { if (raw_value > MaxValue / 10) { // Overflow x = negate ? std::numeric_limits>::min() : std::numeric_limits>::max(); return is; } raw_value *= 10; } } } x = fixed::from_raw_value(static_cast(negate ? -raw_value : raw_value)); return is; } } #endif Opendigitalradio-ODR-DabMod-f7eedef/fpm/math.hpp000066400000000000000000000472641475762153200216620ustar00rootroot00000000000000#ifndef FPM_MATH_HPP #define FPM_MATH_HPP #include "fixed.hpp" #include #ifdef _MSC_VER #include #endif namespace fpm { // // Helper functions // namespace detail { // Returns the index of the most-signifcant set bit inline long find_highest_bit(unsigned long long value) noexcept { assert(value != 0); #if defined(_MSC_VER) unsigned long index; #if defined(_WIN64) _BitScanReverse64(&index, value); #else if (_BitScanReverse(&index, static_cast(value >> 32)) != 0) { index += 32; } else { _BitScanReverse(&index, static_cast(value & 0xfffffffflu)); } #endif return index; #elif defined(__GNUC__) || defined(__clang__) return sizeof(value) * 8 - 1 - __builtin_clzll(value); #else # error "your platform does not support find_highest_bit()" #endif } } // // Classification methods // template constexpr inline int fpclassify(fixed x) noexcept { return (x.raw_value() == 0) ? FP_ZERO : FP_NORMAL; } template constexpr inline bool isfinite(fixed) noexcept { return true; } template constexpr inline bool isinf(fixed) noexcept { return false; } template constexpr inline bool isnan(fixed) noexcept { return false; } template constexpr inline bool isnormal(fixed x) noexcept { return x.raw_value() != 0; } template constexpr inline bool signbit(fixed x) noexcept { return x.raw_value() < 0; } template constexpr inline bool isgreater(fixed x, fixed y) noexcept { return x > y; } template constexpr inline bool isgreaterequal(fixed x, fixed y) noexcept { return x >= y; } template constexpr inline bool isless(fixed x, fixed y) noexcept { return x < y; } template constexpr inline bool islessequal(fixed x, fixed y) noexcept { return x <= y; } template constexpr inline bool islessgreater(fixed x, fixed y) noexcept { return x != y; } template constexpr inline bool isunordered(fixed x, fixed y) noexcept { return false; } // // Nearest integer operations // template inline fixed ceil(fixed x) noexcept { constexpr auto FRAC = B(1) << F; auto value = x.raw_value(); if (value > 0) value += FRAC - 1; return fixed::from_raw_value(value / FRAC * FRAC); } template inline fixed floor(fixed x) noexcept { constexpr auto FRAC = B(1) << F; auto value = x.raw_value(); if (value < 0) value -= FRAC - 1; return fixed::from_raw_value(value / FRAC * FRAC); } template inline fixed trunc(fixed x) noexcept { constexpr auto FRAC = B(1) << F; return fixed::from_raw_value(x.raw_value() / FRAC * FRAC); } template inline fixed round(fixed x) noexcept { constexpr auto FRAC = B(1) << F; auto value = x.raw_value() / (FRAC / 2); return fixed::from_raw_value(((value / 2) + (value % 2)) * FRAC); } template fixed nearbyint(fixed x) noexcept { // Rounding mode is assumed to be FE_TONEAREST constexpr auto FRAC = B(1) << F; auto value = x.raw_value(); const bool is_half = std::abs(value % FRAC) == FRAC / 2; value /= FRAC / 2; value = (value / 2) + (value % 2); value -= (value % 2) * is_half; return fixed::from_raw_value(value * FRAC); } template constexpr inline fixed rint(fixed x) noexcept { // Rounding mode is assumed to be FE_TONEAREST return nearbyint(x); } // // Mathematical functions // template constexpr inline fixed abs(fixed x) noexcept { return (x >= fixed{0}) ? x : -x; } template constexpr inline fixed fmod(fixed x, fixed y) noexcept { return assert(y.raw_value() != 0), fixed::from_raw_value(x.raw_value() % y.raw_value()); } template constexpr inline fixed remainder(fixed x, fixed y) noexcept { return assert(y.raw_value() != 0), x - nearbyint(x / y) * y; } template inline fixed remquo(fixed x, fixed y, int* quo) noexcept { assert(y.raw_value() != 0); assert(quo != nullptr); *quo = x.raw_value() / y.raw_value(); return fixed::from_raw_value(x.raw_value() % y.raw_value()); } // // Manipulation functions // template constexpr inline fixed copysign(fixed x, fixed y) noexcept { return x = abs(x), (y >= fixed{0}) ? x : -x; } template constexpr inline fixed nextafter(fixed from, fixed to) noexcept { return from == to ? to : to > from ? fixed::from_raw_value(from.raw_value() + 1) : fixed::from_raw_value(from.raw_value() - 1); } template constexpr inline fixed nexttoward(fixed from, fixed to) noexcept { return nextafter(from, to); } template inline fixed modf(fixed x, fixed* iptr) noexcept { const auto raw = x.raw_value(); constexpr auto FRAC = B{1} << F; *iptr = fixed::from_raw_value(raw / FRAC * FRAC); return fixed::from_raw_value(raw % FRAC); } // // Power functions // template ::value>::type* = nullptr> fixed pow(fixed base, T exp) noexcept { using Fixed = fixed; if (base == Fixed(0)) { assert(exp > 0); return Fixed(0); } Fixed result {1}; if (exp < 0) { for (Fixed intermediate = base; exp != 0; exp /= 2, intermediate *= intermediate) { if ((exp % 2) != 0) { result /= intermediate; } } } else { for (Fixed intermediate = base; exp != 0; exp /= 2, intermediate *= intermediate) { if ((exp % 2) != 0) { result *= intermediate; } } } return result; } template fixed pow(fixed base, fixed exp) noexcept { using Fixed = fixed; if (base == Fixed(0)) { assert(exp > Fixed(0)); return Fixed(0); } if (exp < Fixed(0)) { return 1 / pow(base, -exp); } constexpr auto FRAC = B(1) << F; if (exp.raw_value() % FRAC == 0) { // Non-fractional exponents are easier to calculate return pow(base, exp.raw_value() / FRAC); } // For negative bases we do not support fractional exponents. // Technically fractions with odd denominators could work, // but that's too much work to figure out. assert(base > Fixed(0)); return exp2(log2(base) * exp); } template fixed exp(fixed x) noexcept { using Fixed = fixed; if (x < Fixed(0)) { return 1 / exp(-x); } constexpr auto FRAC = B(1) << F; const B x_int = x.raw_value() / FRAC; x -= x_int; assert(x >= Fixed(0) && x < Fixed(1)); constexpr auto fA = Fixed::template from_fixed_point<63>( 128239257017632854ll); // 1.3903728105644451e-2 constexpr auto fB = Fixed::template from_fixed_point<63>( 320978614890280666ll); // 3.4800571158543038e-2 constexpr auto fC = Fixed::template from_fixed_point<63>(1571680799599592947ll); // 1.7040197373796334e-1 constexpr auto fD = Fixed::template from_fixed_point<63>(4603349000587966862ll); // 4.9909609871464493e-1 constexpr auto fE = Fixed::template from_fixed_point<62>(4612052447974689712ll); // 1.0000794567422495 constexpr auto fF = Fixed::template from_fixed_point<63>(9223361618412247875ll); // 9.9999887043019773e-1 return pow(Fixed::e(), x_int) * (((((fA * x + fB) * x + fC) * x + fD) * x + fE) * x + fF); } template fixed exp2(fixed x) noexcept { using Fixed = fixed; if (x < Fixed(0)) { return 1 / exp2(-x); } constexpr auto FRAC = B(1) << F; const B x_int = x.raw_value() / FRAC; x -= x_int; assert(x >= Fixed(0) && x < Fixed(1)); constexpr auto fA = Fixed::template from_fixed_point<63>( 17491766697771214ll); // 1.8964611454333148e-3 constexpr auto fB = Fixed::template from_fixed_point<63>( 82483038782406547ll); // 8.9428289841091295e-3 constexpr auto fC = Fixed::template from_fixed_point<63>( 515275173969157690ll); // 5.5866246304520701e-2 constexpr auto fD = Fixed::template from_fixed_point<63>(2214897896212987987ll); // 2.4013971109076949e-1 constexpr auto fE = Fixed::template from_fixed_point<63>(6393224161192452326ll); // 6.9315475247516736e-1 constexpr auto fF = Fixed::template from_fixed_point<63>(9223371050976163566ll); // 9.9999989311082668e-1 return Fixed(1 << x_int) * (((((fA * x + fB) * x + fC) * x + fD) * x + fE) * x + fF); } template fixed expm1(fixed x) noexcept { return exp(x) - 1; } template fixed log2(fixed x) noexcept { using Fixed = fixed; assert(x > Fixed(0)); // Normalize input to the [1:2] domain B value = x.raw_value(); const long highest = detail::find_highest_bit(value); if (highest >= F) { value >>= (highest - F); } else { value <<= (F - highest); } x = Fixed::from_raw_value(value); assert(x >= Fixed(1) && x < Fixed(2)); constexpr auto fA = Fixed::template from_fixed_point<63>( 413886001457275979ll); // 4.4873610194131727e-2 constexpr auto fB = Fixed::template from_fixed_point<63>(-3842121857793256941ll); // -4.1656368651734915e-1 constexpr auto fC = Fixed::template from_fixed_point<62>( 7522345947206307744ll); // 1.6311487636297217 constexpr auto fD = Fixed::template from_fixed_point<61>(-8187571043052183818ll); // -3.5507929249026341 constexpr auto fE = Fixed::template from_fixed_point<60>( 5870342889289496598ll); // 5.0917108110420042 constexpr auto fF = Fixed::template from_fixed_point<61>(-6457199832668582866ll); // -2.8003640347009253 return Fixed(highest - F) + (((((fA * x + fB) * x + fC) * x + fD) * x + fE) * x + fF); } template fixed log(fixed x) noexcept { using Fixed = fixed; return log2(x) / log2(Fixed::e()); } template fixed log10(fixed x) noexcept { using Fixed = fixed; return log2(x) / log2(Fixed(10)); } template fixed log1p(fixed x) noexcept { return log(1 + x); } template fixed cbrt(fixed x) noexcept { using Fixed = fixed; if (x == Fixed(0)) { return x; } if (x < Fixed(0)) { return -cbrt(-x); } assert(x >= Fixed(0)); // Finding the cube root of an integer, taken from Hacker's Delight, // based on the square root algorithm. // We start at the greatest power of eight that's less than the argument. int ofs = ((detail::find_highest_bit(x.raw_value()) + 2*F) / 3 * 3); I num = I{x.raw_value()}; I res = 0; const auto do_round = [&] { for (; ofs >= 0; ofs -= 3) { res += res; const I val = (3*res*(res + 1) + 1) << ofs; if (num >= val) { num -= val; res++; } } }; // We should shift by 2*F (since there are two multiplications), but that // could overflow even the intermediate type, so we have to split the // algorithm up in two rounds of F bits each. Each round will deplete // 'num' digit by digit, so after a round we can shift it again. num <<= F; ofs -= F; do_round(); num <<= F; ofs += F; do_round(); return Fixed::from_raw_value(static_cast(res)); } template fixed sqrt(fixed x) noexcept { using Fixed = fixed; assert(x >= Fixed(0)); if (x == Fixed(0)) { return x; } // Finding the square root of an integer in base-2, from: // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_.28base_2.29 // Shift by F first because it's fixed-point. I num = I{x.raw_value()} << F; I res = 0; // "bit" starts at the greatest power of four that's less than the argument. for (I bit = I{1} << ((detail::find_highest_bit(x.raw_value()) + F) / 2 * 2); bit != 0; bit >>= 2) { const I val = res + bit; res >>= 1; if (num >= val) { num -= val; res += bit; } } // Round the last digit up if necessary if (num > res) { res++; } return Fixed::from_raw_value(static_cast(res)); } template fixed hypot(fixed x, fixed y) noexcept { assert(x != 0 || y != 0); return sqrt(x*x + y*y); } // // Trigonometry functions // template fixed sin(fixed x) noexcept { // This sine uses a fifth-order curve-fitting approximation originally // described by Jasper Vijn on coranac.com which has a worst-case // relative error of 0.07% (over [-pi:pi]). using Fixed = fixed; // Turn x from [0..2*PI] domain into [0..4] domain x = fmod(x, Fixed::two_pi()); x = x / Fixed::half_pi(); // Take x modulo one rotation, so [-4..+4]. if (x < Fixed(0)) { x += Fixed(4); } int sign = +1; if (x > Fixed(2)) { // Reduce domain to [0..2]. sign = -1; x -= Fixed(2); } if (x > Fixed(1)) { // Reduce domain to [0..1]. x = Fixed(2) - x; } const Fixed x2 = x*x; return sign * x * (Fixed::pi() - x2*(Fixed::two_pi() - 5 - x2*(Fixed::pi() - 3)))/2; } template inline fixed cos(fixed x) noexcept { using Fixed = fixed; if (x > Fixed(0)) { // Prevent an overflow due to the addition of π/2 return sin(x - (Fixed::two_pi() - Fixed::half_pi())); } else { return sin(Fixed::half_pi() + x); } } template inline fixed tan(fixed x) noexcept { auto cx = cos(x); // Tangent goes to infinity at 90 and -90 degrees. // We can't represent that with fixed-point maths. assert(abs(cx).raw_value() > 1); return sin(x) / cx; } namespace detail { // Calculates atan(x) assuming that x is in the range [0,1] template fixed atan_sanitized(fixed x) noexcept { using Fixed = fixed; assert(x >= Fixed(0) && x <= Fixed(1)); constexpr auto fA = Fixed::template from_fixed_point<63>( 716203666280654660ll); // 0.0776509570923569 constexpr auto fB = Fixed::template from_fixed_point<63>(-2651115102768076601ll); // -0.287434475393028 constexpr auto fC = Fixed::template from_fixed_point<63>( 9178930894564541004ll); // 0.995181681698119 (PI/4 - A - B) const auto xx = x * x; return ((fA*xx + fB)*xx + fC)*x; } // Calculate atan(y / x), assuming x != 0. // // If x is very, very small, y/x can easily overflow the fixed-point range. // If q = y/x and q > 1, atan(q) would calculate atan(1/q) as intermediate step // anyway. We can shortcut that here and avoid the loss of information, thus // improving the accuracy of atan(y/x) for very small x. template fixed atan_div(fixed y, fixed x) noexcept { using Fixed = fixed; assert(x != Fixed(0)); // Make sure y and x are positive. // If y / x is negative (when y or x, but not both, are negative), negate the result to // keep the correct outcome. if (y < Fixed(0)) { if (x < Fixed(0)) { return atan_div(-y, -x); } return -atan_div(-y, x); } if (x < Fixed(0)) { return -atan_div(y, -x); } assert(y >= Fixed(0)); assert(x > Fixed(0)); if (y > x) { return Fixed::half_pi() - detail::atan_sanitized(x / y); } return detail::atan_sanitized(y / x); } } template fixed atan(fixed x) noexcept { using Fixed = fixed; if (x < Fixed(0)) { return -atan(-x); } if (x > Fixed(1)) { return Fixed::half_pi() - detail::atan_sanitized(Fixed(1) / x); } return detail::atan_sanitized(x); } template fixed asin(fixed x) noexcept { using Fixed = fixed; assert(x >= Fixed(-1) && x <= Fixed(+1)); const auto yy = Fixed(1) - x * x; if (yy == Fixed(0)) { return copysign(Fixed::half_pi(), x); } return detail::atan_div(x, sqrt(yy)); } template fixed acos(fixed x) noexcept { using Fixed = fixed; assert(x >= Fixed(-1) && x <= Fixed(+1)); if (x == Fixed(-1)) { return Fixed::pi(); } const auto yy = Fixed(1) - x * x; return Fixed(2)*detail::atan_div(sqrt(yy), Fixed(1) + x); } template fixed atan2(fixed y, fixed x) noexcept { using Fixed = fixed; if (x == Fixed(0)) { assert(y != Fixed(0)); return (y > Fixed(0)) ? Fixed::half_pi() : -Fixed::half_pi(); } auto ret = detail::atan_div(y, x); if (x < Fixed(0)) { return (y >= Fixed(0)) ? ret + Fixed::pi() : ret - Fixed::pi(); } return ret; } } #endif Opendigitalradio-ODR-DabMod-f7eedef/kiss/000077500000000000000000000000001475762153200203725ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/kiss/CHANGELOG000066400000000000000000000117161475762153200216120ustar00rootroot000000000000001.3.0 2012-07-18 removed non-standard malloc.h from kiss_fft.h moved -lm to end of link line checked various return values converted python Numeric code to NumPy fixed test of int32_t on 64 bit OS added padding in a couple of places to allow SIMD alignment of structs 1.2.9 2010-05-27 threadsafe ( including OpenMP ) first edition of kissfft.hh the C++ template fft engine 1.2.8 Changed memory.h to string.h -- apparently more standard Added openmp extensions. This can have fairly linear speedups for larger FFT sizes. 1.2.7 Shrank the real-fft memory footprint. Thanks to Galen Seitz. 1.2.6 (Nov 14, 2006) The "thanks to GenArts" release. Added multi-dimensional real-optimized FFT, see tools/kiss_fftndr Thanks go to GenArts, Inc. for sponsoring the development. 1.2.5 (June 27, 2006) The "release for no good reason" release. Changed some harmless code to make some compilers' warnings go away. Added some more digits to pi -- why not. Added kiss_fft_next_fast_size() function to help people decide how much to pad. Changed multidimensional test from 8 dimensions to only 3 to avoid testing problems with fixed point (sorry Buckaroo Banzai). 1.2.4 (Oct 27, 2005) The "oops, inverse fixed point real fft was borked" release. Fixed scaling bug for inverse fixed point real fft -- also fixed test code that should've been failing. Thanks to Jean-Marc Valin for bug report. Use sys/types.h for more portable types than short,int,long => int16_t,int32_t,int64_t If your system does not have these, you may need to define them -- but at least it breaks in a loud and easily fixable way -- unlike silently using the wrong size type. Hopefully tools/psdpng.c is fixed -- thanks to Steve Kellog for pointing out the weirdness. 1.2.3 (June 25, 2005) The "you want to use WHAT as a sample" release. Added ability to use 32 bit fixed point samples -- requires a 64 bit intermediate result, a la 'long long' Added ability to do 4 FFTs in parallel by using SSE SIMD instructions. This is accomplished by using the __m128 (vector of 4 floats) as kiss_fft_scalar. Define USE_SIMD to use this. I know, I know ... this is drifting a bit from the "kiss" principle, but the speed advantages make it worth it for some. Also recent gcc makes it SOO easy to use vectors of 4 floats like a POD type. 1.2.2 (May 6, 2005) The Matthew release Replaced fixed point division with multiply&shift. Thanks to Jean-Marc Valin for discussions regarding. Considerable speedup for fixed-point. Corrected overflow protection in real fft routines when using fixed point. Finder's Credit goes to Robert Oschler of robodance for pointing me at the bug. This also led to the CHECK_OVERFLOW_OP macro. 1.2.1 (April 4, 2004) compiles cleanly with just about every -W warning flag under the sun reorganized kiss_fft_state so it could be read-only/const. This may be useful for embedded systems that are willing to predeclare twiddle factors, factorization. Fixed C_MUL,S_MUL on 16-bit platforms. tmpbuf will only be allocated if input & output buffers are same scratchbuf will only be allocated for ffts that are not multiples of 2,3,5 NOTE: The tmpbuf,scratchbuf changes may require synchronization code for multi-threaded apps. 1.2 (Feb 23, 2004) interface change -- cfg object is forward declaration of struct instead of void* This maintains type saftey and lets the compiler warn/error about stupid mistakes. (prompted by suggestion from Erik de Castro Lopo) small speed improvements added psdpng.c -- sample utility that will create png spectrum "waterfalls" from an input file ( not terribly useful yet) 1.1.1 (Feb 1, 2004 ) minor bug fix -- only affects odd rank, in-place, multi-dimensional FFTs 1.1 : (Jan 30,2004) split sample_code/ into test/ and tools/ Removed 2-D fft and added N-D fft (arbitrary) modified fftutil.c to allow multi-d FFTs Modified core fft routine to allow an input stride via kiss_fft_stride() (eased support of multi-D ffts) Added fast convolution filtering (FIR filtering using overlap-scrap method, with tail scrap) Add kfc.[ch]: the KISS FFT Cache. It takes care of allocs for you ( suggested by Oscar Lesta ). 1.0.1 (Dec 15, 2003) fixed bug that occurred when nfft==1. Thanks to Steven Johnson. 1.0 : (Dec 14, 2003) changed kiss_fft function from using a single buffer, to two buffers. If the same buffer pointer is supplied for both in and out, kiss will manage the buffer copies. added kiss_fft2d and kiss_fftr as separate source files (declarations in kiss_fft.h ) 0.4 :(Nov 4,2003) optimized for radix 2,3,4,5 0.3 :(Oct 28, 2003) woops, version 2 didn't actually factor out any radices other than 2. Thanks to Steven Johnson for finding this one. 0.2 :(Oct 27, 2003) added mixed radix, only radix 2,4 optimized versions 0.1 :(May 19 2003) initial release, radix 2 only Opendigitalradio-ODR-DabMod-f7eedef/kiss/COPYING000066400000000000000000000003661475762153200214320ustar00rootroot00000000000000Copyright (c) 2003-2010 Mark Borgerding . All rights reserved. KISS FFT is provided under: SPDX-License-Identifier: BSD-3-Clause Being under the terms of the BSD 3-clause "New" or "Revised" License, according with: LICENSES/BSD-3-Clause Opendigitalradio-ODR-DabMod-f7eedef/kiss/README.md000066400000000000000000000221131475762153200216500ustar00rootroot00000000000000# KISS FFT [![Build Status](https://travis-ci.com/mborgerding/kissfft.svg?branch=master)](https://travis-ci.com/mborgerding/kissfft) KISS FFT - A mixed-radix Fast Fourier Transform based up on the principle, "Keep It Simple, Stupid." There are many great fft libraries already around. Kiss FFT is not trying to be better than any of them. It only attempts to be a reasonably efficient, moderately useful FFT that can use fixed or floating data types and can be incorporated into someone's C program in a few minutes with trivial licensing. ## USAGE: The basic usage for 1-d complex FFT is: ```c #include "kiss_fft.h" kiss_fft_cfg cfg = kiss_fft_alloc( nfft ,is_inverse_fft ,0,0 ); while ... ... // put kth sample in cx_in[k].r and cx_in[k].i kiss_fft( cfg , cx_in , cx_out ); ... // transformed. DC is in cx_out[0].r and cx_out[0].i kiss_fft_free(cfg); ``` - **Note**: frequency-domain data is stored from dc up to 2pi. so cx_out[0] is the dc bin of the FFT and cx_out[nfft/2] is the Nyquist bin (if exists) Declarations are in "kiss_fft.h", along with a brief description of the functions you'll need to use. Code definitions for 1d complex FFTs are in kiss_fft.c. You can do other cool stuff with the extras you'll find in tools/ > - multi-dimensional FFTs > - real-optimized FFTs (returns the positive half-spectrum: (nfft/2+1) complex frequency bins) > - fast convolution FIR filtering (not available for fixed point) > - spectrum image creation The core fft and most tools/ code can be compiled to use float, double, Q15 short or Q31 samples. The default is float. ## BUILDING: There are two functionally-equivalent build systems supported by kissfft: - Make (traditional Makefiles for Unix / Linux systems) - CMake (more modern and feature-rich build system developed by Kitware) To build kissfft, the following build environment can be used: - GNU build environment with GCC, Clang and GNU Make or CMake (>= 3.6) - Microsoft Visual C++ (MSVC) with CMake (>= 3.6) Additional libraries required to build and test kissfft include: - libpng for psdpng tool, - libfftw3 to validate kissfft results against it, - python 2/3 with Numpy to validate kissfft results against it. - OpenMP supported by GCC, Clang or MSVC for multi-core FFT transformations Environments like Cygwin and MinGW can be highly likely used to build kissfft targeting Windows platform, but no tests were performed to the date. Both Make and CMake builds are easily configurable: - `KISSFFT_DATATYPE=` (for Make) or `-DKISSFFT_DATATYPE=` (for CMake) denote the principal datatype used by kissfft. It can be one of the following: - float (default) - double - int16_t - int32_t - SIMD (requires SSE instruction set support on target CPU) - `KISSFFT_OPENMP=1` (for Make) or `-DKISSFFT_OPENMP=ON` (for CMake) builds kissfft with OpenMP support. Please note that a supported compiler is required and this option is turned off by default. - `KISSFFT_STATIC=1` (for Make) or `-DKISSFFT_STATIC=ON` (for CMake) instructs the builder to create static library ('.lib' for Windows / '.a' for Unix or Linux). By default, this option is turned off and the shared library is created ('.dll' for Windows, '.so' for Linux or Unix, '.dylib' for Mac OSX) - `-DKISSFFT_TEST=OFF` (for CMake) disables building tests for kissfft. On Make, building tests is done separately by 'make testall' or 'make testsingle', so no specific setting is required. - `KISSFFT_TOOLS=0` (for Make) or `-DKISSFFT_TOOLS=OFF` (for CMake) builds kissfft without command-line tools like 'fastconv'. By default the tools are built. - `KISSFFT_USE_ALLOCA=1` (for Make) or `-DKISSFFT_USE_ALLOCA=ON` (for CMake) build kissfft with 'alloca' usage instead of 'malloc' / 'free'. - `PREFIX=/full/path/to/installation/prefix/directory` (for Make) or `-DCMAKE_INSTALL_PREFIX=/full/path/to/installation/prefix/directory` (for CMake) specifies the prefix directory to install kissfft into. For example, to build kissfft as a static library with 'int16_t' datatype and OpenMP support using Make, run the command from kissfft source tree: ``` make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 all ``` The same configuration for CMake is: ``` mkdir build && cd build cmake -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all ``` To specify '/tmp/1234' as installation prefix directory, run: ``` make PREFIX=/tmp/1234 KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 install ``` or ``` mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/tmp/1234 -DKISSFFT_DATATYPE=int16_t -DKISSFFT_STATIC=ON -DKISSFFT_OPENMP=ON .. make all make install ``` ## TESTING: To validate the build configured as an example above, run the following command from kissfft source tree: ``` make KISSFFT_DATATYPE=int16_t KISSFFT_STATIC=1 KISSFFT_OPENMP=1 testsingle ``` if using Make, or: ``` make test ``` if using CMake. To test all possible build configurations, please run an extended testsuite from kissfft source tree: ``` sh test/kissfft-testsuite.sh ``` Please note that the extended testsuite takes around 20-40 minutes depending on device it runs on. This testsuite is useful for reporting bugs or testing the pull requests. ## BACKGROUND I started coding this because I couldn't find a fixed point FFT that didn't use assembly code. I started with floating point numbers so I could get the theory straight before working on fixed point issues. In the end, I had a little bit of code that could be recompiled easily to do ffts with short, float or double (other types should be easy too). Once I got my FFT working, I was curious about the speed compared to a well respected and highly optimized fft library. I don't want to criticize this great library, so let's call it FFT_BRANDX. During this process, I learned: > 1. FFT_BRANDX has more than 100K lines of code. The core of kiss_fft is about 500 lines (cpx 1-d). > 2. It took me an embarrassingly long time to get FFT_BRANDX working. > 3. A simple program using FFT_BRANDX is 522KB. A similar program using kiss_fft is 18KB (without optimizing for size). > 4. FFT_BRANDX is roughly twice as fast as KISS FFT in default mode. It is wonderful that free, highly optimized libraries like FFT_BRANDX exist. But such libraries carry a huge burden of complexity necessary to extract every last bit of performance. **Sometimes simpler is better, even if it's not better.** ## FREQUENTLY ASKED QUESTIONS: > Q: Can I use kissfft in a project with a ___ license?
> A: Yes. See LICENSE below. > Q: Why don't I get the output I expect?
> A: The two most common causes of this are > 1) scaling : is there a constant multiplier between what you got and what you want? > 2) mixed build environment -- all code must be compiled with same preprocessor > definitions for FIXED_POINT and kiss_fft_scalar > Q: Will you write/debug my code for me?
> A: Probably not unless you pay me. I am happy to answer pointed and topical questions, but > I may refer you to a book, a forum, or some other resource. ## PERFORMANCE (on Athlon XP 2100+, with gcc 2.96, float data type) Kiss performed 10000 1024-pt cpx ffts in .63 s of cpu time. For comparison, it took md5sum twice as long to process the same amount of data. Transforming 5 minutes of CD quality audio takes less than a second (nfft=1024). **DO NOT:** - use Kiss if you need the Fastest Fourier Transform in the World - ask me to add features that will bloat the code ## UNDER THE HOOD Kiss FFT uses a time decimation, mixed-radix, out-of-place FFT. If you give it an input buffer and output buffer that are the same, a temporary buffer will be created to hold the data. No static data is used. The core routines of kiss_fft are thread-safe (but not all of the tools directory).[ No scaling is done for the floating point version (for speed). Scaling is done both ways for the fixed-point version (for overflow prevention). Optimized butterflies are used for factors 2,3,4, and 5. The real (i.e. not complex) optimization code only works for even length ffts. It does two half-length FFTs in parallel (packed into real&imag), and then combines them via twiddling. The result is nfft/2+1 complex frequency bins from DC to Nyquist. If you don't know what this means, search the web. The fast convolution filtering uses the overlap-scrap method, slightly modified to put the scrap at the tail. ## LICENSE Revised BSD License, see COPYING for verbiage. Basically, "free to use&change, give credit where due, no guarantees" Note this license is compatible with GPL at one end of the spectrum and closed, commercial software at the other end. See http://www.fsf.org/licensing/licenses ## TODO - Add real optimization for odd length FFTs - Document/revisit the input/output fft scaling - Make doc describing the overlap (tail) scrap fast convolution filtering in kiss_fastfir.c - Test all the ./tools/ code with fixed point (kiss_fastfir.c doesn't work, maybe others) ## AUTHOR Mark Borgerding Mark@Borgerding.net Opendigitalradio-ODR-DabMod-f7eedef/kiss/_kiss_fft_guts.h000066400000000000000000000112331475762153200235540ustar00rootroot00000000000000/* * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ /* kiss_fft.h defines kiss_fft_scalar as either short or a float type and defines typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ #ifndef _kiss_fft_guts_h #define _kiss_fft_guts_h #include "kiss_fft.h" #include "kiss_fft_log.h" #include #define MAXFACTORS 32 /* e.g. an fft of length 128 has 4 factors as far as kissfft is concerned 4*4*4*2 */ struct kiss_fft_state{ int nfft; int inverse; int factors[2*MAXFACTORS]; kiss_fft_cpx twiddles[1]; }; /* Explanation of macros dealing with complex math: C_MUL(m,a,b) : m = a*b C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise C_SUB( res, a,b) : res = a - b C_SUBFROM( res , a) : res -= a C_ADDTO( res , a) : res += a * */ #ifdef FIXED_POINT #include #if (FIXED_POINT==32) # define FRACBITS 31 # define SAMPPROD int64_t #define SAMP_MAX INT32_MAX #define SAMP_MIN INT32_MIN #else # define FRACBITS 15 # define SAMPPROD int32_t #define SAMP_MAX INT16_MAX #define SAMP_MIN INT16_MIN #endif #if defined(CHECK_OVERFLOW) # define CHECK_OVERFLOW_OP(a,op,b) \ if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ KISS_FFT_WARNING("overflow (%d " #op" %d) = %ld", (a),(b),(SAMPPROD)(a) op (SAMPPROD)(b)); } #endif # define smul(a,b) ( (SAMPPROD)(a)*(b) ) # define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) # define S_MUL(a,b) sround( smul(a,b) ) # define C_MUL(m,a,b) \ do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) # define DIVSCALAR(x,k) \ (x) = sround( smul( x, SAMP_MAX/k ) ) # define C_FIXDIV(c,div) \ do { DIVSCALAR( (c).r , div); \ DIVSCALAR( (c).i , div); }while (0) # define C_MULBYSCALAR( c, s ) \ do{ (c).r = sround( smul( (c).r , s ) ) ;\ (c).i = sround( smul( (c).i , s ) ) ; }while(0) #else /* not FIXED_POINT*/ # define S_MUL(a,b) ( (a)*(b) ) #define C_MUL(m,a,b) \ do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) # define C_FIXDIV(c,div) /* NOOP */ # define C_MULBYSCALAR( c, s ) \ do{ (c).r *= (s);\ (c).i *= (s); }while(0) #endif #ifndef CHECK_OVERFLOW_OP # define CHECK_OVERFLOW_OP(a,op,b) /* noop */ #endif #define C_ADD( res, a,b)\ do { \ CHECK_OVERFLOW_OP((a).r,+,(b).r)\ CHECK_OVERFLOW_OP((a).i,+,(b).i)\ (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ }while(0) #define C_SUB( res, a,b)\ do { \ CHECK_OVERFLOW_OP((a).r,-,(b).r)\ CHECK_OVERFLOW_OP((a).i,-,(b).i)\ (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ }while(0) #define C_ADDTO( res , a)\ do { \ CHECK_OVERFLOW_OP((res).r,+,(a).r)\ CHECK_OVERFLOW_OP((res).i,+,(a).i)\ (res).r += (a).r; (res).i += (a).i;\ }while(0) #define C_SUBFROM( res , a)\ do {\ CHECK_OVERFLOW_OP((res).r,-,(a).r)\ CHECK_OVERFLOW_OP((res).i,-,(a).i)\ (res).r -= (a).r; (res).i -= (a).i; \ }while(0) #ifdef FIXED_POINT # define KISS_FFT_COS(phase) floor(.5+SAMP_MAX * cos (phase)) # define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) # define HALF_OF(x) ((x)>>1) #elif defined(USE_SIMD) # define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) # define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) # define HALF_OF(x) ((x)*_mm_set1_ps(.5)) #else # define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) # define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) # define HALF_OF(x) ((x)*((kiss_fft_scalar).5)) #endif #define kf_cexp(x,phase) \ do{ \ (x)->r = KISS_FFT_COS(phase);\ (x)->i = KISS_FFT_SIN(phase);\ }while(0) /* a debugging function */ #define pcpx(c)\ KISS_FFT_DEBUG("%g + %gi\n",(double)((c)->r),(double)((c)->i)) #ifdef KISS_FFT_USE_ALLOCA // define this to allow use of alloca instead of malloc for temporary buffers // Temporary buffers are used in two case: // 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5 // 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform. #include #define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes) #define KISS_FFT_TMP_FREE(ptr) #else #define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes) #define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr) #endif #endif /* _kiss_fft_guts_h */ Opendigitalradio-ODR-DabMod-f7eedef/kiss/kfc.c000066400000000000000000000046501475762153200213060ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #include "kfc.h" typedef struct cached_fft *kfc_cfg; struct cached_fft { int nfft; int inverse; kiss_fft_cfg cfg; kfc_cfg next; }; static kfc_cfg cache_root=NULL; static int ncached=0; static kiss_fft_cfg find_cached_fft(int nfft,int inverse) { size_t len; kfc_cfg cur=cache_root; kfc_cfg prev=NULL; while ( cur ) { if ( cur->nfft == nfft && inverse == cur->inverse ) break;/*found the right node*/ prev = cur; cur = prev->next; } if (cur== NULL) { /* no cached node found, need to create a new one*/ kiss_fft_alloc(nfft,inverse,0,&len); #ifdef USE_SIMD int padding = (16-sizeof(struct cached_fft)) & 15; // make sure the cfg aligns on a 16 byte boundary len += padding; #endif cur = (kfc_cfg)KISS_FFT_MALLOC((sizeof(struct cached_fft) + len )); if (cur == NULL) return NULL; cur->cfg = (kiss_fft_cfg)(cur+1); #ifdef USE_SIMD cur->cfg = (kiss_fft_cfg) ((char*)(cur+1)+padding); #endif kiss_fft_alloc(nfft,inverse,cur->cfg,&len); cur->nfft=nfft; cur->inverse=inverse; cur->next = NULL; if ( prev ) prev->next = cur; else cache_root = cur; ++ncached; } return cur->cfg; } void kfc_cleanup(void) { kfc_cfg cur=cache_root; kfc_cfg next=NULL; while (cur){ next = cur->next; free(cur); cur=next; } ncached=0; cache_root = NULL; } void kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout) { kiss_fft( find_cached_fft(nfft,0),fin,fout ); } void kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout) { kiss_fft( find_cached_fft(nfft,1),fin,fout ); } #ifdef KFC_TEST static void check(int nc) { if (ncached != nc) { fprintf(stderr,"ncached should be %d,but it is %d\n",nc,ncached); exit(1); } } int main(void) { kiss_fft_cpx buf1[1024],buf2[1024]; memset(buf1,0,sizeof(buf1)); check(0); kfc_fft(512,buf1,buf2); check(1); kfc_fft(512,buf1,buf2); check(1); kfc_ifft(512,buf1,buf2); check(2); kfc_cleanup(); check(0); return 0; } #endif Opendigitalradio-ODR-DabMod-f7eedef/kiss/kfc.h000066400000000000000000000030031475762153200213020ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #ifndef KFC_H #define KFC_H #include "kiss_fft.h" #ifdef __cplusplus extern "C" { #endif /* KFC -- Kiss FFT Cache Not needing to deal with kiss_fft_alloc and a config object may be handy for a lot of programs. KFC uses the underlying KISS FFT functions, but caches the config object. The first time kfc_fft or kfc_ifft for a given FFT size, the cfg object is created for it. All subsequent calls use the cached configuration object. NOTE: You should probably not use this if your program will be using a lot of various sizes of FFTs. There is a linear search through the cached objects. If you are only using one or two FFT sizes, this will be negligible. Otherwise, you may want to use another method of managing the cfg objects. There is no automated cleanup of the cached objects. This could lead to large memory usage in a program that uses a lot of *DIFFERENT* sized FFTs. If you want to force all cached cfg objects to be freed, call kfc_cleanup. */ /*forward complex FFT */ void KISS_FFT_API kfc_fft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout); /*reverse complex FFT */ void KISS_FFT_API kfc_ifft(int nfft, const kiss_fft_cpx * fin,kiss_fft_cpx * fout); /*free all cached objects*/ void KISS_FFT_API kfc_cleanup(void); #ifdef __cplusplus } #endif #endif Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fft.c000066400000000000000000000270121475762153200223500ustar00rootroot00000000000000/* * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #include "_kiss_fft_guts.h" /* The guts header contains all the multiplication and addition macros that are defined for fixed or floating point complex numbers. It also delares the kf_ internal functions. */ static void kf_bfly2( kiss_fft_cpx * Fout, const size_t fstride, const kiss_fft_cfg st, int m ) { kiss_fft_cpx * Fout2; kiss_fft_cpx * tw1 = st->twiddles; kiss_fft_cpx t; Fout2 = Fout + m; do{ C_FIXDIV(*Fout,2); C_FIXDIV(*Fout2,2); C_MUL (t, *Fout2 , *tw1); tw1 += fstride; C_SUB( *Fout2 , *Fout , t ); C_ADDTO( *Fout , t ); ++Fout2; ++Fout; }while (--m); } static void kf_bfly4( kiss_fft_cpx * Fout, const size_t fstride, const kiss_fft_cfg st, const size_t m ) { kiss_fft_cpx *tw1,*tw2,*tw3; kiss_fft_cpx scratch[6]; size_t k=m; const size_t m2=2*m; const size_t m3=3*m; tw3 = tw2 = tw1 = st->twiddles; do { C_FIXDIV(*Fout,4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2],4); C_FIXDIV(Fout[m3],4); C_MUL(scratch[0],Fout[m] , *tw1 ); C_MUL(scratch[1],Fout[m2] , *tw2 ); C_MUL(scratch[2],Fout[m3] , *tw3 ); C_SUB( scratch[5] , *Fout, scratch[1] ); C_ADDTO(*Fout, scratch[1]); C_ADD( scratch[3] , scratch[0] , scratch[2] ); C_SUB( scratch[4] , scratch[0] , scratch[2] ); C_SUB( Fout[m2], *Fout, scratch[3] ); tw1 += fstride; tw2 += fstride*2; tw3 += fstride*3; C_ADDTO( *Fout , scratch[3] ); if(st->inverse) { Fout[m].r = scratch[5].r - scratch[4].i; Fout[m].i = scratch[5].i + scratch[4].r; Fout[m3].r = scratch[5].r + scratch[4].i; Fout[m3].i = scratch[5].i - scratch[4].r; }else{ Fout[m].r = scratch[5].r + scratch[4].i; Fout[m].i = scratch[5].i - scratch[4].r; Fout[m3].r = scratch[5].r - scratch[4].i; Fout[m3].i = scratch[5].i + scratch[4].r; } ++Fout; }while(--k); } static void kf_bfly3( kiss_fft_cpx * Fout, const size_t fstride, const kiss_fft_cfg st, size_t m ) { size_t k=m; const size_t m2 = 2*m; kiss_fft_cpx *tw1,*tw2; kiss_fft_cpx scratch[5]; kiss_fft_cpx epi3; epi3 = st->twiddles[fstride*m]; tw1=tw2=st->twiddles; do{ C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); C_MUL(scratch[1],Fout[m] , *tw1); C_MUL(scratch[2],Fout[m2] , *tw2); C_ADD(scratch[3],scratch[1],scratch[2]); C_SUB(scratch[0],scratch[1],scratch[2]); tw1 += fstride; tw2 += fstride*2; Fout[m].r = Fout->r - HALF_OF(scratch[3].r); Fout[m].i = Fout->i - HALF_OF(scratch[3].i); C_MULBYSCALAR( scratch[0] , epi3.i ); C_ADDTO(*Fout,scratch[3]); Fout[m2].r = Fout[m].r + scratch[0].i; Fout[m2].i = Fout[m].i - scratch[0].r; Fout[m].r -= scratch[0].i; Fout[m].i += scratch[0].r; ++Fout; }while(--k); } static void kf_bfly5( kiss_fft_cpx * Fout, const size_t fstride, const kiss_fft_cfg st, int m ) { kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; int u; kiss_fft_cpx scratch[13]; kiss_fft_cpx * twiddles = st->twiddles; kiss_fft_cpx *tw; kiss_fft_cpx ya,yb; ya = twiddles[fstride*m]; yb = twiddles[fstride*2*m]; Fout0=Fout; Fout1=Fout0+m; Fout2=Fout0+2*m; Fout3=Fout0+3*m; Fout4=Fout0+4*m; tw=st->twiddles; for ( u=0; ur += scratch[7].r + scratch[8].r; Fout0->i += scratch[7].i + scratch[8].i; scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); C_SUB(*Fout1,scratch[5],scratch[6]); C_ADD(*Fout4,scratch[5],scratch[6]); scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); C_ADD(*Fout2,scratch[11],scratch[12]); C_SUB(*Fout3,scratch[11],scratch[12]); ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; } } /* perform the butterfly for one stage of a mixed radix FFT */ static void kf_bfly_generic( kiss_fft_cpx * Fout, const size_t fstride, const kiss_fft_cfg st, int m, int p ) { int u,k,q1,q; kiss_fft_cpx * twiddles = st->twiddles; kiss_fft_cpx t; int Norig = st->nfft; kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p); if (scratch == NULL){ KISS_FFT_ERROR("Memory allocation failed."); return; } for ( u=0; u=Norig) twidx-=Norig; C_MUL(t,scratch[q] , twiddles[twidx] ); C_ADDTO( Fout[ k ] ,t); } k += m; } } KISS_FFT_TMP_FREE(scratch); } static void kf_work( kiss_fft_cpx * Fout, const kiss_fft_cpx * f, const size_t fstride, int in_stride, int * factors, const kiss_fft_cfg st ) { kiss_fft_cpx * Fout_beg=Fout; const int p=*factors++; /* the radix */ const int m=*factors++; /* stage's fft length/p */ const kiss_fft_cpx * Fout_end = Fout + p*m; #ifdef _OPENMP // use openmp extensions at the // top-level (not recursive) if (fstride==1 && p<=5 && m!=1) { int k; // execute the p different work units in different threads # pragma omp parallel for for (k=0;k floor_sqrt) p = n; /* no more factors, skip to end */ } n /= p; *facbuf++ = p; *facbuf++ = n; } while (n > 1); } /* * * User-callable function to allocate all necessary storage space for the fft. * * The return value is a contiguous block of memory, allocated with malloc. As such, * It can be freed with free(), rather than a kiss_fft-specific function. * */ kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) { KISS_FFT_ALIGN_CHECK(mem) kiss_fft_cfg st=NULL; size_t memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fft_state) + sizeof(kiss_fft_cpx)*(nfft-1)); /* twiddle factors*/ if ( lenmem==NULL ) { st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); }else{ if (mem != NULL && *lenmem >= memneeded) st = (kiss_fft_cfg)mem; *lenmem = memneeded; } if (st) { int i; st->nfft=nfft; st->inverse = inverse_fft; for (i=0;iinverse) phase *= -1; kf_cexp(st->twiddles+i, phase ); } kf_factor(nfft,st->factors); } return st; } void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) { if (fin == fout) { //NOTE: this is not really an in-place FFT algorithm. //It just performs an out-of-place FFT into a temp buffer if (fout == NULL){ KISS_FFT_ERROR("fout buffer NULL."); return; } kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft); if (tmpbuf == NULL){ KISS_FFT_ERROR("Memory allocation error."); return; } kf_work(tmpbuf,fin,1,in_stride, st->factors,st); memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft); KISS_FFT_TMP_FREE(tmpbuf); }else{ kf_work( fout, fin, 1,in_stride, st->factors,st ); } } void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) { kiss_fft_stride(cfg,fin,fout,1); } void kiss_fft_cleanup(void) { // nothing needed any more } int kiss_fft_next_fast_size(int n) { while(1) { int m=n; while ( (m%2) == 0 ) m/=2; while ( (m%3) == 0 ) m/=3; while ( (m%5) == 0 ) m/=5; if (m<=1) break; /* n is completely factorable by twos, threes, and fives */ n++; } return n; } Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fft.h000066400000000000000000000103521475762153200223540ustar00rootroot00000000000000/* * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #ifndef KISS_FFT_H #define KISS_FFT_H #include #include #include #include // Define KISS_FFT_SHARED macro to properly export symbols #ifdef KISS_FFT_SHARED # ifdef _WIN32 # ifdef KISS_FFT_BUILD # define KISS_FFT_API __declspec(dllexport) # else # define KISS_FFT_API __declspec(dllimport) # endif # else # define KISS_FFT_API __attribute__ ((visibility ("default"))) # endif #else # define KISS_FFT_API #endif #ifdef __cplusplus extern "C" { #endif /* ATTENTION! If you would like a : -- a utility that will handle the caching of fft objects -- real-only (no imaginary time component ) FFT -- a multi-dimensional FFT -- a command-line utility to perform ffts -- a command-line utility to perform fast-convolution filtering Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c in the tools/ directory. */ /* User may override KISS_FFT_MALLOC and/or KISS_FFT_FREE. */ #ifdef USE_SIMD # include # define kiss_fft_scalar __m128 # ifndef KISS_FFT_MALLOC # define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) # define KISS_FFT_ALIGN_CHECK(ptr) # define KISS_FFT_ALIGN_SIZE_UP(size) ((size + 15UL) & ~0xFUL) # endif # ifndef KISS_FFT_FREE # define KISS_FFT_FREE _mm_free # endif #else # define KISS_FFT_ALIGN_CHECK(ptr) # define KISS_FFT_ALIGN_SIZE_UP(size) (size) # ifndef KISS_FFT_MALLOC # define KISS_FFT_MALLOC malloc # endif # ifndef KISS_FFT_FREE # define KISS_FFT_FREE free # endif #endif #ifdef FIXED_POINT #include # if (FIXED_POINT == 32) # define kiss_fft_scalar int32_t # else # define kiss_fft_scalar int16_t # endif #else # ifndef kiss_fft_scalar /* default is float */ # define kiss_fft_scalar float # endif #endif typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; typedef struct kiss_fft_state* kiss_fft_cfg; /* * kiss_fft_alloc * * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. * * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); * * The return value from fft_alloc is a cfg buffer used internally * by the fft routine or NULL. * * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. * The returned value should be free()d when done to avoid memory leaks. * * The state can be placed in a user supplied buffer 'mem': * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, * then the function places the cfg in mem and the size used in *lenmem * and returns mem. * * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), * then the function returns NULL and places the minimum cfg * buffer size in *lenmem. * */ kiss_fft_cfg KISS_FFT_API kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); /* * kiss_fft(cfg,in_out_buf) * * Perform an FFT on a complex input buffer. * for a forward FFT, * fin should be f[0] , f[1] , ... ,f[nfft-1] * fout will be F[0] , F[1] , ... ,F[nfft-1] * Note that each element is complex and can be accessed like f[k].r and f[k].i * */ void KISS_FFT_API kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); /* A more generic version of the above function. It reads its input from every Nth sample. * */ void KISS_FFT_API kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); /* If kiss_fft_alloc allocated a buffer, it is one contiguous buffer and can be simply free()d when no longer needed*/ #define kiss_fft_free KISS_FFT_FREE /* Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up your compiler output to call this before you exit. */ void KISS_FFT_API kiss_fft_cleanup(void); /* * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) */ int KISS_FFT_API kiss_fft_next_fast_size(int n); /* for real ffts, we need an even size */ #define kiss_fftr_next_fast_size_real(n) \ (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) #ifdef __cplusplus } #endif #endif Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fft_log.h000066400000000000000000000017121475762153200232150ustar00rootroot00000000000000/* * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #ifndef kiss_fft_log_h #define kiss_fft_log_h #define ERROR 1 #define WARNING 2 #define INFO 3 #define DEBUG 4 #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #if defined(NDEBUG) # define KISS_FFT_LOG_MSG(severity, ...) ((void)0) #else # define KISS_FFT_LOG_MSG(severity, ...) \ fprintf(stderr, "[" #severity "] " __FILE__ ":" TOSTRING(__LINE__) " "); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n") #endif #define KISS_FFT_ERROR(...) KISS_FFT_LOG_MSG(ERROR, __VA_ARGS__) #define KISS_FFT_WARNING(...) KISS_FFT_LOG_MSG(WARNING, __VA_ARGS__) #define KISS_FFT_INFO(...) KISS_FFT_LOG_MSG(INFO, __VA_ARGS__) #define KISS_FFT_DEBUG(...) KISS_FFT_LOG_MSG(DEBUG, __VA_ARGS__) #endif /* kiss_fft_log_h */Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fftnd.c000066400000000000000000000135571475762153200227030ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #include "kiss_fftnd.h" #include "_kiss_fft_guts.h" struct kiss_fftnd_state{ int dimprod; /* dimsum would be mighty tasty right now */ int ndims; int *dims; kiss_fft_cfg *states; /* cfg states for each dimension */ kiss_fft_cpx * tmpbuf; /*buffer capable of hold the entire input */ }; kiss_fftnd_cfg kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem) { KISS_FFT_ALIGN_CHECK(mem) kiss_fftnd_cfg st = NULL; int i; int dimprod=1; size_t memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fftnd_state)); char * ptr = NULL; for (i=0;istates[i] */ dimprod *= dims[i]; } memneeded += KISS_FFT_ALIGN_SIZE_UP(sizeof(int) * ndims);/* st->dims */ memneeded += KISS_FFT_ALIGN_SIZE_UP(sizeof(void*) * ndims);/* st->states */ memneeded += KISS_FFT_ALIGN_SIZE_UP(sizeof(kiss_fft_cpx) * dimprod); /* st->tmpbuf */ if (lenmem == NULL) {/* allocate for the caller*/ ptr = (char *) malloc (memneeded); } else { /* initialize supplied buffer if big enough */ if (*lenmem >= memneeded) ptr = (char *) mem; *lenmem = memneeded; /*tell caller how big struct is (or would be) */ } if (!ptr) return NULL; /*malloc failed or buffer too small */ st = (kiss_fftnd_cfg) ptr; st->dimprod = dimprod; st->ndims = ndims; ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fftnd_state)); st->states = (kiss_fft_cfg *)ptr; ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(void*) * ndims); st->dims = (int*)ptr; ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(int) * ndims); st->tmpbuf = (kiss_fft_cpx*)ptr; ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(kiss_fft_cpx) * dimprod); for (i=0;idims[i] = dims[i]; kiss_fft_alloc (st->dims[i], inverse_fft, NULL, &len); st->states[i] = kiss_fft_alloc (st->dims[i], inverse_fft, ptr,&len); ptr += len; } /* Hi there! If you're looking at this particular code, it probably means you've got a brain-dead bounds checker that thinks the above code overwrites the end of the array. It doesn't. -- Mark P.S. The below code might give you some warm fuzzies and help convince you. */ if ( ptr - (char*)st != (int)memneeded ) { fprintf(stderr, "################################################################################\n" "Internal error! Memory allocation miscalculation\n" "################################################################################\n" ); } return st; } /* This works by tackling one dimension at a time. In effect, Each stage starts out by reshaping the matrix into a DixSi 2d matrix. A Di-sized fft is taken of each column, transposing the matrix as it goes. Here's a 3-d example: Take a 2x3x4 matrix, laid out in memory as a contiguous buffer [ [ [ a b c d ] [ e f g h ] [ i j k l ] ] [ [ m n o p ] [ q r s t ] [ u v w x ] ] ] Stage 0 ( D=2): treat the buffer as a 2x12 matrix [ [a b ... k l] [m n ... w x] ] FFT each column with size 2. Transpose the matrix at the same time using kiss_fft_stride. [ [ a+m a-m ] [ b+n b-n] ... [ k+w k-w ] [ l+x l-x ] ] Note fft([x y]) == [x+y x-y] Stage 1 ( D=3) treats the buffer (the output of stage D=2) as an 3x8 matrix, [ [ a+m a-m b+n b-n c+o c-o d+p d-p ] [ e+q e-q f+r f-r g+s g-s h+t h-t ] [ i+u i-u j+v j-v k+w k-w l+x l-x ] ] And perform FFTs (size=3) on each of the columns as above, transposing the matrix as it goes. The output of stage 1 is (Legend: ap = [ a+m e+q i+u ] am = [ a-m e-q i-u ] ) [ [ sum(ap) fft(ap)[0] fft(ap)[1] ] [ sum(am) fft(am)[0] fft(am)[1] ] [ sum(bp) fft(bp)[0] fft(bp)[1] ] [ sum(bm) fft(bm)[0] fft(bm)[1] ] [ sum(cp) fft(cp)[0] fft(cp)[1] ] [ sum(cm) fft(cm)[0] fft(cm)[1] ] [ sum(dp) fft(dp)[0] fft(dp)[1] ] [ sum(dm) fft(dm)[0] fft(dm)[1] ] ] Stage 2 ( D=4) treats this buffer as a 4*6 matrix, [ [ sum(ap) fft(ap)[0] fft(ap)[1] sum(am) fft(am)[0] fft(am)[1] ] [ sum(bp) fft(bp)[0] fft(bp)[1] sum(bm) fft(bm)[0] fft(bm)[1] ] [ sum(cp) fft(cp)[0] fft(cp)[1] sum(cm) fft(cm)[0] fft(cm)[1] ] [ sum(dp) fft(dp)[0] fft(dp)[1] sum(dm) fft(dm)[0] fft(dm)[1] ] ] Then FFTs each column, transposing as it goes. The resulting matrix is the 3d FFT of the 2x3x4 input matrix. Note as a sanity check that the first element of the final stage's output (DC term) is sum( [ sum(ap) sum(bp) sum(cp) sum(dp) ] ) , i.e. the summation of all 24 input elements. */ void kiss_fftnd(kiss_fftnd_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) { int i,k; const kiss_fft_cpx * bufin=fin; kiss_fft_cpx * bufout; /*arrange it so the last bufout == fout*/ if ( st->ndims & 1 ) { bufout = fout; if (fin==fout) { memcpy( st->tmpbuf, fin, sizeof(kiss_fft_cpx) * st->dimprod ); bufin = st->tmpbuf; } }else bufout = st->tmpbuf; for ( k=0; k < st->ndims; ++k) { int curdim = st->dims[k]; int stride = st->dimprod / curdim; for ( i=0 ; istates[k], bufin+i , bufout+i*curdim, stride ); /*toggle back and forth between the two buffers*/ if (bufout == st->tmpbuf){ bufout = fout; bufin = st->tmpbuf; }else{ bufout = st->tmpbuf; bufin = fout; } } } Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fftnd.h000066400000000000000000000012011475762153200226670ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #ifndef KISS_FFTND_H #define KISS_FFTND_H #include "kiss_fft.h" #ifdef __cplusplus extern "C" { #endif typedef struct kiss_fftnd_state * kiss_fftnd_cfg; kiss_fftnd_cfg KISS_FFT_API kiss_fftnd_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem); void KISS_FFT_API kiss_fftnd(kiss_fftnd_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); #ifdef __cplusplus } #endif #endif Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fftndr.c000066400000000000000000000071031475762153200230530ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #include "kiss_fftndr.h" #include "_kiss_fft_guts.h" #define MAX(x,y) ( ( (x)<(y) )?(y):(x) ) struct kiss_fftndr_state { int dimReal; int dimOther; kiss_fftr_cfg cfg_r; kiss_fftnd_cfg cfg_nd; void * tmpbuf; }; static int prod(const int *dims, int ndims) { int x=1; while (ndims--) x *= *dims++; return x; } kiss_fftndr_cfg kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem) { KISS_FFT_ALIGN_CHECK(mem) kiss_fftndr_cfg st = NULL; size_t nr=0 , nd=0,ntmp=0; int dimReal = dims[ndims-1]; int dimOther = prod(dims,ndims-1); size_t memneeded; char * ptr = NULL; (void)kiss_fftr_alloc(dimReal,inverse_fft,NULL,&nr); (void)kiss_fftnd_alloc(dims,ndims-1,inverse_fft,NULL,&nd); ntmp = MAX( 2*dimOther , dimReal+2) * sizeof(kiss_fft_scalar) // freq buffer for one pass + dimOther*(dimReal+2) * sizeof(kiss_fft_scalar); // large enough to hold entire input in case of in-place memneeded = KISS_FFT_ALIGN_SIZE_UP(sizeof( struct kiss_fftndr_state )) + KISS_FFT_ALIGN_SIZE_UP(nr) + KISS_FFT_ALIGN_SIZE_UP(nd) + KISS_FFT_ALIGN_SIZE_UP(ntmp); if (lenmem==NULL) { ptr = (char*) malloc(memneeded); }else{ if (*lenmem >= memneeded) ptr = (char *)mem; *lenmem = memneeded; } if (ptr==NULL) return NULL; st = (kiss_fftndr_cfg) ptr; memset( st , 0 , memneeded); ptr += KISS_FFT_ALIGN_SIZE_UP(sizeof(struct kiss_fftndr_state)); st->dimReal = dimReal; st->dimOther = dimOther; st->cfg_r = kiss_fftr_alloc( dimReal,inverse_fft,ptr,&nr); ptr += KISS_FFT_ALIGN_SIZE_UP(nr); st->cfg_nd = kiss_fftnd_alloc(dims,ndims-1,inverse_fft, ptr,&nd); ptr += KISS_FFT_ALIGN_SIZE_UP(nd); st->tmpbuf = ptr; return st; } void kiss_fftndr(kiss_fftndr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) { int k1,k2; int dimReal = st->dimReal; int dimOther = st->dimOther; int nrbins = dimReal/2+1; kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther); // timedata is N0 x N1 x ... x Nk real // take a real chunk of data, fft it and place the output at correct intervals for (k1=0;k1cfg_r, timedata + k1*dimReal , tmp1 ); // tmp1 now holds nrbins complex points for (k2=0;k2cfg_nd, tmp2+k2*dimOther, tmp1); // tmp1 now holds dimOther complex points for (k1=0;k1dimReal; int dimOther = st->dimOther; int nrbins = dimReal/2+1; kiss_fft_cpx * tmp1 = (kiss_fft_cpx*)st->tmpbuf; kiss_fft_cpx * tmp2 = tmp1 + MAX(nrbins,dimOther); for (k2=0;k2cfg_nd, tmp1, tmp2+k2*dimOther); } for (k1=0;k1cfg_r,tmp1,timedata + k1*dimReal); } } Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fftndr.h000066400000000000000000000023121475762153200230550ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #ifndef KISS_NDR_H #define KISS_NDR_H #include "kiss_fft.h" #include "kiss_fftr.h" #include "kiss_fftnd.h" #ifdef __cplusplus extern "C" { #endif typedef struct kiss_fftndr_state *kiss_fftndr_cfg; kiss_fftndr_cfg KISS_FFT_API kiss_fftndr_alloc(const int *dims,int ndims,int inverse_fft,void*mem,size_t*lenmem); /* dims[0] must be even If you don't care to allocate space, use mem = lenmem = NULL */ void KISS_FFT_API kiss_fftndr( kiss_fftndr_cfg cfg, const kiss_fft_scalar *timedata, kiss_fft_cpx *freqdata); /* input timedata has dims[0] X dims[1] X ... X dims[ndims-1] scalar points output freqdata has dims[0] X dims[1] X ... X dims[ndims-1]/2+1 complex points */ void KISS_FFT_API kiss_fftndri( kiss_fftndr_cfg cfg, const kiss_fft_cpx *freqdata, kiss_fft_scalar *timedata); /* input and output dimensions are the exact opposite of kiss_fftndr */ #define kiss_fftndr_free free #ifdef __cplusplus } #endif #endif Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fftr.c000066400000000000000000000112771475762153200225400ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #include "kiss_fftr.h" #include "_kiss_fft_guts.h" struct kiss_fftr_state{ kiss_fft_cfg substate; kiss_fft_cpx * tmpbuf; kiss_fft_cpx * super_twiddles; #ifdef USE_SIMD void * pad; #endif }; kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) { KISS_FFT_ALIGN_CHECK(mem) int i; kiss_fftr_cfg st = NULL; size_t subsize = 0, memneeded; if (nfft & 1) { KISS_FFT_ERROR("Real FFT optimization must be even."); return NULL; } nfft >>= 1; kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2); if (lenmem == NULL) { st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); } else { if (*lenmem >= memneeded) st = (kiss_fftr_cfg) mem; *lenmem = memneeded; } if (!st) return NULL; st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); st->super_twiddles = st->tmpbuf + nfft; kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); for (i = 0; i < nfft/2; ++i) { double phase = -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); if (inverse_fft) phase *= -1; kf_cexp (st->super_twiddles+i,phase); } return st; } void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) { /* input buffer timedata is stored row-wise */ int k,ncfft; kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; if ( st->substate->inverse) { KISS_FFT_ERROR("kiss fft usage error: improper alloc"); return;/* The caller did not call the correct function */ } ncfft = st->substate->nfft; /*perform the parallel fft of two real signals packed in real,imag*/ kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); /* The real part of the DC element of the frequency spectrum in st->tmpbuf * contains the sum of the even-numbered elements of the input time sequence * The imag part is the sum of the odd-numbered elements * * The sum of tdc.r and tdc.i is the sum of the input time sequence. * yielding DC of input time sequence * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... * yielding Nyquist bin of input time sequence */ tdc.r = st->tmpbuf[0].r; tdc.i = st->tmpbuf[0].i; C_FIXDIV(tdc,2); CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); freqdata[0].r = tdc.r + tdc.i; freqdata[ncfft].r = tdc.r - tdc.i; #ifdef USE_SIMD freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); #else freqdata[ncfft].i = freqdata[0].i = 0; #endif for ( k=1;k <= ncfft/2 ; ++k ) { fpk = st->tmpbuf[k]; fpnk.r = st->tmpbuf[ncfft-k].r; fpnk.i = - st->tmpbuf[ncfft-k].i; C_FIXDIV(fpk,2); C_FIXDIV(fpnk,2); C_ADD( f1k, fpk , fpnk ); C_SUB( f2k, fpk , fpnk ); C_MUL( tw , f2k , st->super_twiddles[k-1]); freqdata[k].r = HALF_OF(f1k.r + tw.r); freqdata[k].i = HALF_OF(f1k.i + tw.i); freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); } } void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) { /* input buffer timedata is stored row-wise */ int k, ncfft; if (st->substate->inverse == 0) { KISS_FFT_ERROR("kiss fft usage error: improper alloc"); return;/* The caller did not call the correct function */ } ncfft = st->substate->nfft; st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; C_FIXDIV(st->tmpbuf[0],2); for (k = 1; k <= ncfft / 2; ++k) { kiss_fft_cpx fk, fnkc, fek, fok, tmp; fk = freqdata[k]; fnkc.r = freqdata[ncfft - k].r; fnkc.i = -freqdata[ncfft - k].i; C_FIXDIV( fk , 2 ); C_FIXDIV( fnkc , 2 ); C_ADD (fek, fk, fnkc); C_SUB (tmp, fk, fnkc); C_MUL (fok, tmp, st->super_twiddles[k-1]); C_ADD (st->tmpbuf[k], fek, fok); C_SUB (st->tmpbuf[ncfft - k], fek, fok); #ifdef USE_SIMD st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); #else st->tmpbuf[ncfft - k].i *= -1; #endif } kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); } Opendigitalradio-ODR-DabMod-f7eedef/kiss/kiss_fftr.h000066400000000000000000000021701475762153200225350ustar00rootroot00000000000000/* * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. * This file is part of KISS FFT - https://github.com/mborgerding/kissfft * * SPDX-License-Identifier: BSD-3-Clause * See COPYING file for more information. */ #ifndef KISS_FTR_H #define KISS_FTR_H #include "kiss_fft.h" #ifdef __cplusplus extern "C" { #endif /* Real optimized version can save about 45% cpu time vs. complex fft of a real seq. */ typedef struct kiss_fftr_state *kiss_fftr_cfg; kiss_fftr_cfg KISS_FFT_API kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); /* nfft must be even If you don't care to allocate space, use mem = lenmem = NULL */ void KISS_FFT_API kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); /* input timedata has nfft scalar points output freqdata has nfft/2+1 complex points */ void KISS_FFT_API kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); /* input freqdata has nfft/2+1 complex points output timedata has nfft scalar points */ #define kiss_fftr_free KISS_FFT_FREE #ifdef __cplusplus } #endif #endif Opendigitalradio-ODR-DabMod-f7eedef/lib/000077500000000000000000000000001475762153200201675ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/lib/Globals.cpp000066400000000000000000000023311475762153200222550ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* This file is part of the ODR-mmbTools. 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 . */ /* Ensure construction and destruction of static globals in the right order */ #include "Log.h" #include "RemoteControl.h" // the RC needs logging, and needs to be initialised later. Logger etiLog; #if ENABLE_REMOTECONTROL RemoteControllers rcs; #endif // ENABLE_REMOTECONTROL Opendigitalradio-ODR-DabMod-f7eedef/lib/INIReader.h000066400000000000000000000340671475762153200221140ustar00rootroot00000000000000// Read an INI file into easy-to-access name/value pairs. // inih and INIReader are released under the New BSD license: /* Copyright (c) 2009, Ben Hoyt All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Ben Hoyt nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // Go to the project home page for more info: // // https://github.com/benhoyt/inih #ifndef __INI_H__ #define __INI_H__ /* Make this header file easier to include in C++ code */ #ifdef __cplusplus extern "C" { #endif #include /* Typedef for prototype of handler function. */ typedef int (*ini_handler)(void* user, const char* section, const char* name, const char* value); /* Typedef for prototype of fgets-style reader function. */ typedef char* (*ini_reader)(char* str, int num, void* stream); /* Parse given INI-style file. May have [section]s, name=value pairs (whitespace stripped), and comments starting with ';' (semicolon). Section is "" if name=value pair parsed before any section heading. name:value pairs are also supported as a concession to Python's configparser. For each name=value pair parsed, call handler function with given user pointer as well as section, name, and value (data only valid for duration of handler call). Handler should return nonzero on success, zero on error. Returns 0 on success, line number of first error on parse error (doesn't stop on first error), -1 on file open error, or -2 on memory allocation error (only when INI_USE_STACK is zero). */ int ini_parse(const char* filename, ini_handler handler, void* user); /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't close the file when it's finished -- the caller must do that. */ int ini_parse_file(FILE* file, ini_handler handler, void* user); /* Same as ini_parse(), but takes an ini_reader function pointer instead of filename. Used for implementing custom or string-based I/O. */ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user); /* Nonzero to allow multi-line value parsing, in the style of Python's configparser. If allowed, ini_parse() will call the handler with the same name for each subsequent line parsed. */ #ifndef INI_ALLOW_MULTILINE #define INI_ALLOW_MULTILINE 1 #endif /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of the file. See http://code.google.com/p/inih/issues/detail?id=21 */ #ifndef INI_ALLOW_BOM #define INI_ALLOW_BOM 1 #endif /* Nonzero to allow inline comments (with valid inline comment characters specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match Python 3.2+ configparser behaviour. */ #ifndef INI_ALLOW_INLINE_COMMENTS #define INI_ALLOW_INLINE_COMMENTS 1 #endif #ifndef INI_INLINE_COMMENT_PREFIXES #define INI_INLINE_COMMENT_PREFIXES ";" #endif /* Nonzero to use stack, zero to use heap (malloc/free). */ #ifndef INI_USE_STACK #define INI_USE_STACK 1 #endif /* Stop parsing on first error (default is to keep parsing). */ #ifndef INI_STOP_ON_FIRST_ERROR #define INI_STOP_ON_FIRST_ERROR 0 #endif /* Maximum line length for any line in INI file. */ #ifndef INI_MAX_LINE #define INI_MAX_LINE 200 #endif #ifdef __cplusplus } #endif /* inih -- simple .INI file parser inih is released under the New BSD license (see LICENSE.txt). Go to the project home page for more info: https://github.com/benhoyt/inih */ #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #if !INI_USE_STACK #include #endif #define MAX_SECTION 50 #define MAX_NAME 50 /* Strip whitespace chars off end of given string, in place. Return s. */ inline static char* rstrip(char* s) { char* p = s + strlen(s); while (p > s && isspace((unsigned char)(*--p))) *p = '\0'; return s; } /* Return pointer to first non-whitespace char in given string. */ inline static char* lskip(const char* s) { while (*s && isspace((unsigned char)(*s))) s++; return (char*)s; } /* Return pointer to first char (of chars) or inline comment in given string, or pointer to null at end of string if neither found. Inline comment must be prefixed by a whitespace character to register as a comment. */ inline static char* find_chars_or_comment(const char* s, const char* chars) { #if INI_ALLOW_INLINE_COMMENTS int was_space = 0; while (*s && (!chars || !strchr(chars, *s)) && !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { was_space = isspace((unsigned char)(*s)); s++; } #else while (*s && (!chars || !strchr(chars, *s))) { s++; } #endif return (char*)s; } /* Version of strncpy that ensures dest (size bytes) is null-terminated. */ inline static char* strncpy0(char* dest, const char* src, size_t size) { strncpy(dest, src, size - 1); dest[size - 1] = '\0'; return dest; } /* See documentation in header file. */ inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, void* user) { /* Uses a fair bit of stack (use heap instead if you need to) */ #if INI_USE_STACK char line[INI_MAX_LINE]; #else char* line; #endif char section[MAX_SECTION] = ""; char prev_name[MAX_NAME] = ""; char* start; char* end; char* name; char* value; int lineno = 0; int error = 0; #if !INI_USE_STACK line = (char*)malloc(INI_MAX_LINE); if (!line) { return -2; } #endif /* Scan through stream line by line */ while (reader(line, INI_MAX_LINE, stream) != NULL) { lineno++; start = line; #if INI_ALLOW_BOM if (lineno == 1 && (unsigned char)start[0] == 0xEF && (unsigned char)start[1] == 0xBB && (unsigned char)start[2] == 0xBF) { start += 3; } #endif start = lskip(rstrip(start)); if (*start == ';' || *start == '#') { /* Per Python configparser, allow both ; and # comments at the start of a line */ } #if INI_ALLOW_MULTILINE else if (*prev_name && *start && start > line) { #if INI_ALLOW_INLINE_COMMENTS end = find_chars_or_comment(start, NULL); if (*end) *end = '\0'; rstrip(start); #endif /* Non-blank line with leading whitespace, treat as continuation of previous name's value (as per Python configparser). */ if (!handler(user, section, prev_name, start) && !error) error = lineno; } #endif else if (*start == '[') { /* A "[section]" line */ end = find_chars_or_comment(start + 1, "]"); if (*end == ']') { *end = '\0'; strncpy0(section, start + 1, sizeof(section)); *prev_name = '\0'; } else if (!error) { /* No ']' found on section line */ error = lineno; } } else if (*start) { /* Not a comment, must be a name[=:]value pair */ end = find_chars_or_comment(start, "=:"); if (*end == '=' || *end == ':') { *end = '\0'; name = rstrip(start); value = lskip(end + 1); #if INI_ALLOW_INLINE_COMMENTS end = find_chars_or_comment(value, NULL); if (*end) *end = '\0'; #endif rstrip(value); /* Valid name[=:]value pair found, call handler */ strncpy0(prev_name, name, sizeof(prev_name)); if (!handler(user, section, name, value) && !error) error = lineno; } else if (!error) { /* No '=' or ':' found on name[=:]value line */ error = lineno; } } #if INI_STOP_ON_FIRST_ERROR if (error) break; #endif } #if !INI_USE_STACK free(line); #endif return error; } /* See documentation in header file. */ inline int ini_parse_file(FILE* file, ini_handler handler, void* user) { return ini_parse_stream((ini_reader)fgets, file, handler, user); } /* See documentation in header file. */ inline int ini_parse(const char* filename, ini_handler handler, void* user) { FILE* file; int error; file = fopen(filename, "r"); if (!file) return -1; error = ini_parse_file(file, handler, user); fclose(file); return error; } #endif /* __INI_H__ */ #ifndef __INIREADER_H__ #define __INIREADER_H__ #include #include #include // Read an INI file into easy-to-access name/value pairs. (Note that I've gone // for simplicity here rather than speed, but it should be pretty decent.) class INIReader { public: // Empty Constructor INIReader() {}; // Construct INIReader and parse given filename. See ini.h for more info // about the parsing. INIReader(std::string filename); // Return the result of ini_parse(), i.e., 0 on success, line number of // first error on parse error, or -1 on file open error. int ParseError() const; // Return the list of sections found in ini file std::set Sections(); // Get a string value from INI file, returning default_value if not found. std::string Get(std::string section_name, std::string default_value); // Get an integer (long) value from INI file, returning default_value if // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). long GetInteger(std::string section_name, long default_value); // Get a real (floating point double) value from INI file, returning // default_value if not found or not a valid floating point value // according to strtod(). double GetReal(std::string section_name, double default_value); // Get a boolean value from INI file, returning default_value if not found or if // not a valid true/false value. Valid true values are "true", "yes", "on", "1", // and valid false values are "false", "no", "off", "0" (not case sensitive). bool GetBoolean(std::string section_name, bool default_value); private: int _error; std::map _values; std::set _sections; static std::string MakeKey(std::string section, std::string name); static int ValueHandler(void* user, const char* section, const char* name, const char* value); }; #endif // __INIREADER_H__ #ifndef __INIREADER__ #define __INIREADER__ #include #include #include using std::string; inline INIReader::INIReader(string filename) { _error = ini_parse(filename.c_str(), ValueHandler, this); } inline int INIReader::ParseError() const { return _error; } inline std::set INIReader::Sections() { return _sections; } inline string INIReader::Get(string section_name, string default_value) { string key = section_name; return _values.count(key) ? _values[key] : default_value; } inline long INIReader::GetInteger(string section_name, long default_value) { string valstr = Get(section_name, ""); const char* value = valstr.c_str(); char* end; // This parses "1234" (decimal) and also "0x4D2" (hex) long n = strtol(value, &end, 0); return end > value ? n : default_value; } inline double INIReader::GetReal(string section_name, double default_value) { string valstr = Get(section_name, ""); const char* value = valstr.c_str(); char* end; double n = strtod(value, &end); return end > value ? n : default_value; } inline bool INIReader::GetBoolean(string section_name, bool default_value) { string valstr = Get(section_name, ""); // Convert to lower case to make string comparisons case-insensitive std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") return true; else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") return false; else return default_value; } inline string INIReader::MakeKey(string section, string name) { string key = section+"."+name; // Convert to lower case to make section/name lookups case-insensitive std::transform(key.begin(), key.end(), key.begin(), ::tolower); return key; } inline int INIReader::ValueHandler(void* user, const char* section, const char* name, const char* value) { INIReader* reader = (INIReader*)user; string key = MakeKey(section, name); if (reader->_values[key].size() > 0) reader->_values[key] += "\n"; reader->_values[key] += value; reader->_sections.insert(section); return 1; } #endif // __INIREADER__ Opendigitalradio-ODR-DabMod-f7eedef/lib/Json.cpp000066400000000000000000000102601475762153200216030ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #include #include #include #include #include #include #include "Json.h" namespace json { static std::string escape_json(const std::string &s) { std::ostringstream o; for (auto c = s.cbegin(); c != s.cend(); c++) { switch (*c) { case '"': o << "\\\""; break; case '\\': o << "\\\\"; break; case '\b': o << "\\b"; break; case '\f': o << "\\f"; break; case '\n': o << "\\n"; break; case '\r': o << "\\r"; break; case '\t': o << "\\t"; break; default: if ('\x00' <= *c && *c <= '\x1f') { o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast(*c); } else { o << *c; } } } return o.str(); } std::string map_to_json(const map_t& values) { std::ostringstream ss; ss << "{ "; size_t ix = 0; for (const auto& element : values) { if (ix > 0) { ss << ","; } ss << "\"" << escape_json(element.first) << "\": "; ss << value_to_json(element.second); ix++; } ss << " }"; return ss.str(); } std::string value_to_json(const value_t& value) { std::ostringstream ss; if (std::holds_alternative(value.v)) { ss << "\"" << escape_json(std::get(value.v)) << "\""; } else if (std::holds_alternative(value.v)) { ss << std::fixed << std::get(value.v); } else if (std::holds_alternative(value.v)) { ss << std::get(value.v); } else if (std::holds_alternative(value.v)) { ss << std::get(value.v); } else if (std::holds_alternative(value.v)) { ss << std::get(value.v); } else if (std::holds_alternative(value.v)) { ss << std::get(value.v); } else if (std::holds_alternative(value.v)) { ss << (std::get(value.v) ? "true" : "false"); } else if (std::holds_alternative(value.v)) { ss << "null"; } else if (std::holds_alternative >(value.v)) { const auto& vec = std::get >(value.v); ss << "[ "; size_t list_ix = 0; for (const auto& list_element : vec) { if (list_ix > 0) { ss << ","; } ss << value_to_json(list_element); list_ix++; } ss << "]"; } else if (std::holds_alternative >(value.v)) { const map_t& v = *std::get >(value.v); ss << map_to_json(v); } else { throw std::logic_error("variant alternative not handled"); } return ss.str(); } } Opendigitalradio-ODR-DabMod-f7eedef/lib/Json.h000066400000000000000000000034261475762153200212560ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org This module adds remote-control capability to some of the dabmux/dabmod modules. */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include namespace json { // STL containers are not required to support incomplete types, // hence the shared_ptr struct value_t { std::variant< std::shared_ptr>, std::vector, std::string, double, int64_t, uint64_t, int32_t, uint32_t, bool, std::nullopt_t> v; }; using map_t = std::unordered_map; std::string map_to_json(const map_t& values); std::string value_to_json(const value_t& value); } Opendigitalradio-ODR-DabMod-f7eedef/lib/Log.cpp000066400000000000000000000133161475762153200214200ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* This file is part of the ODR-mmbTools. 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 . */ #include #include #include #include #include #include "Log.h" using namespace std; Logger::Logger() { m_io_thread = std::thread(&Logger::io_process, this); } Logger::~Logger() { m_message_queue.trigger_wakeup(); m_io_thread.join(); std::lock_guard guard(m_backend_mutex); backends.clear(); } void Logger::register_backend(std::shared_ptr backend) { std::lock_guard guard(m_backend_mutex); backends.push_back(backend); } void Logger::log(log_level_t level, const char* fmt, ...) { if (level == discard) { return; } int size = 100; std::string str; va_list ap; while (1) { str.resize(size); va_start(ap, fmt); int n = vsnprintf((char *)str.c_str(), size, fmt, ap); va_end(ap); if (n > -1 && n < size) { str.resize(n); break; } if (n > -1) size = n + 1; else size *= 2; } logstr(level, move(str)); } void Logger::logstr(log_level_t level, std::string&& message) { if (level == discard) { return; } log_message_t m(level, move(message)); m_message_queue.push(move(m)); } void Logger::io_process() { while (1) { log_message_t m; try { m_message_queue.wait_and_pop(m); } catch (const ThreadsafeQueueWakeup&) { break; } auto message = m.message; /* Remove a potential trailing newline. * It doesn't look good in syslog */ if (message[message.length()-1] == '\n') { message.resize(message.length()-1); } { std::lock_guard guard(m_backend_mutex); for (auto &backend : backends) { backend->log(m.level, message); } if (m.level != log_level_t::trace) { using namespace std::chrono; time_t t = system_clock::to_time_t(system_clock::now()); cerr << put_time(std::gmtime(&t), "%Y-%m-%dZ%H:%M:%S") << " " << levels_as_str[m.level] << " " << message << endl; } } } } LogLine Logger::level(log_level_t level) { return LogLine(this, level); } LogToFile::LogToFile(const std::string& filename) : name("FILE") { FILE* fd = fopen(filename.c_str(), "a"); if (fd == nullptr) { fprintf(stderr, "Cannot open log file !"); throw std::runtime_error("Cannot open log file !"); } log_file.reset(fd); } void LogToFile::log(log_level_t level, const std::string& message) { if (not (level == log_level_t::trace or level == log_level_t::discard)) { const char* log_level_text[] = { "DEBUG", "INFO", "WARN", "ERROR", "ALERT", "EMERG"}; // fprintf is thread-safe fprintf(log_file.get(), SYSLOG_IDENT ": %s: %s\n", log_level_text[(size_t)level], message.c_str()); fflush(log_file.get()); } } void LogToSyslog::log(log_level_t level, const std::string& message) { if (not (level == log_level_t::trace or level == log_level_t::discard)) { int syslog_level = LOG_EMERG; switch (level) { case debug: syslog_level = LOG_DEBUG; break; case info: syslog_level = LOG_INFO; break; /* we don't have the notice level */ case warn: syslog_level = LOG_WARNING; break; case error: syslog_level = LOG_ERR; break; default: syslog_level = LOG_CRIT; break; case alert: syslog_level = LOG_ALERT; break; case emerg: syslog_level = LOG_EMERG; break; } syslog(syslog_level, SYSLOG_IDENT " %s", message.c_str()); } } LogTracer::LogTracer(const string& trace_filename) : name("TRACE") { etiLog.level(info) << "Setting up TRACE to " << trace_filename; FILE* fd = fopen(trace_filename.c_str(), "a"); if (fd == nullptr) { fprintf(stderr, "Cannot open trace file !"); throw std::runtime_error("Cannot open trace file !"); } m_trace_file.reset(fd); using namespace std::chrono; auto now = steady_clock::now().time_since_epoch(); m_trace_micros_startup = duration_cast(now).count(); fprintf(m_trace_file.get(), "0,TRACER,startup at %" PRIu64 "\n", m_trace_micros_startup); } void LogTracer::log(log_level_t level, const std::string& message) { if (level == log_level_t::trace) { using namespace std::chrono; const auto now = steady_clock::now().time_since_epoch(); const auto micros = duration_cast(now).count(); fprintf(m_trace_file.get(), "%" PRIu64 ",%s\n", micros - m_trace_micros_startup, message.c_str()); } } Opendigitalradio-ODR-DabMod-f7eedef/lib/Log.h000066400000000000000000000127631475762153200210720ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* This file is part of the ODR-mmbTools. 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ThreadsafeQueue.h" #define SYSLOG_IDENT PACKAGE_NAME #define SYSLOG_FACILITY LOG_LOCAL0 enum log_level_t {debug = 0, info, warn, error, alert, emerg, trace, discard}; static const std::string levels_as_str[] = { " ", " ", "WARN ", "ERROR", "ALERT", "EMERG", "TRACE", "-----"} ; /** Abstract class all backends must inherit from */ class LogBackend { public: virtual ~LogBackend() {}; virtual void log(log_level_t level, const std::string& message) = 0; virtual std::string get_name() const = 0; }; /** A Logging backend for Syslog */ class LogToSyslog : public LogBackend { public: LogToSyslog() : name("SYSLOG") { openlog(SYSLOG_IDENT, LOG_PID, SYSLOG_FACILITY); } virtual ~LogToSyslog() { closelog(); } void log(log_level_t level, const std::string& message); std::string get_name() const { return name; } private: const std::string name; LogToSyslog(const LogToSyslog& other) = delete; const LogToSyslog& operator=(const LogToSyslog& other) = delete; }; class LogToFile : public LogBackend { public: LogToFile(const std::string& filename); void log(log_level_t level, const std::string& message); std::string get_name() const { return name; } private: const std::string name; struct FILEDeleter{ void operator()(FILE* fd){ if(fd) fclose(fd);}}; std::unique_ptr log_file; LogToFile(const LogToFile& other) = delete; const LogToFile& operator=(const LogToFile& other) = delete; }; class LogTracer : public LogBackend { public: LogTracer(const std::string& filename); void log(log_level_t level, const std::string& message); std::string get_name() const { return name; } private: std::string name; uint64_t m_trace_micros_startup = 0; struct FILEDeleter{ void operator()(FILE* fd){ if(fd) fclose(fd);}}; std::unique_ptr m_trace_file; LogTracer(const LogTracer& other) = delete; const LogTracer& operator=(const LogTracer& other) = delete; }; class LogLine; struct log_message_t { log_message_t(log_level_t _level, std::string&& _message) : level(_level), message(move(_message)) {} log_message_t() : level(debug), message("") {} log_level_t level; std::string message; }; class Logger { public: Logger(); Logger(const Logger& other) = delete; const Logger& operator=(const Logger& other) = delete; ~Logger(); void register_backend(std::shared_ptr backend); /* Log the message to all backends */ void log(log_level_t level, const char* fmt, ...); void logstr(log_level_t level, std::string&& message); /* All logging IO is done in another thread */ void io_process(void); /* Return a LogLine for the given level * so that you can write etiLog.level(info) << "stuff = " << 21 */ LogLine level(log_level_t level); private: std::list > backends; ThreadsafeQueue m_message_queue; std::thread m_io_thread; std::mutex m_backend_mutex; }; /* etiLog is a singleton used in all parts of the program to output log messages. * It is constructed in Globals.cpp */ extern Logger etiLog; // Accumulate a line of logs, using same syntax as stringstream // The line is logged when the LogLine gets destroyed class LogLine { public: LogLine(const LogLine& logline); const LogLine& operator=(const LogLine& other) = delete; LogLine(Logger* logger, log_level_t level) : logger_(logger) { level_ = level; } // Push the new element into the stringstream template LogLine& operator<<(T s) { if (level_ != discard) { os << s; } return *this; } ~LogLine() { if (level_ != discard) { logger_->logstr(level_, os.str()); } } private: std::ostringstream os; log_level_t level_; Logger* logger_; }; Opendigitalradio-ODR-DabMod-f7eedef/lib/RemoteControl.cpp000066400000000000000000000456741475762153200235070ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #include #include #include #include #include #include #include #include "RemoteControl.h" #if defined(HAVE_ZEROMQ) #include "zmq.hpp" #endif using namespace std; RemoteControllerTelnet::~RemoteControllerTelnet() { m_active = false; if (m_restarter_thread.joinable()) { m_restarter_thread.join(); } if (m_child_thread.joinable()) { m_child_thread.join(); } } void RemoteControllerTelnet::restart() { if (m_restarter_thread.joinable()) { m_restarter_thread.join(); } m_restarter_thread = std::thread( &RemoteControllerTelnet::restart_thread, this, 0); } RemoteControllable::~RemoteControllable() { rcs.remove_controllable(this); } std::list RemoteControllable::get_supported_parameters() const { std::list parameterlist; for (const auto& param : m_parameters) { parameterlist.push_back(param[0]); } return parameterlist; } void RemoteControllers::add_controller(std::shared_ptr rc) { m_controllers.push_back(rc); } void RemoteControllers::enrol(RemoteControllable *rc) { controllables.push_back(rc); } void RemoteControllers::remove_controllable(RemoteControllable *rc) { controllables.remove(rc); } std::list< std::vector > RemoteControllers::get_param_list_values(const std::string& name) { RemoteControllable* controllable = get_controllable_(name); std::list< std::vector > allparams; for (auto ¶m : controllable->get_supported_parameters()) { std::vector item; item.push_back(param); try { item.push_back(controllable->get_parameter(param)); } catch (const ParameterError &e) { item.push_back(std::string("error: ") + e.what()); } allparams.push_back(item); } return allparams; } std::string RemoteControllers::get_showjson() { json::map_t root; for (auto &controllable : rcs.controllables) { root[controllable->get_rc_name()].v = std::make_shared(controllable->get_all_values()); } return json::map_to_json(root); } std::string RemoteControllers::get_param(const std::string& name, const std::string& param) { RemoteControllable* controllable = get_controllable_(name); return controllable->get_parameter(param); } void RemoteControllers::check_faults() { for (auto &controller : m_controllers) { if (controller->fault_detected()) { etiLog.level(warn) << "Detected Remote Control fault, restarting it"; controller->restart(); } } } RemoteControllable* RemoteControllers::get_controllable_(const std::string& name) { auto rc = std::find_if(controllables.begin(), controllables.end(), [&](RemoteControllable* r) { return r->get_rc_name() == name; }); if (rc == controllables.end()) { throw ParameterError(string{"Module name '"} + name + "' unknown"); } else { return *rc; } } void RemoteControllers::set_param( const std::string& name, const std::string& param, const std::string& value) { etiLog.level(info) << "RC: Setting " << name << " " << param << " to " << value; RemoteControllable* controllable = get_controllable_(name); try { return controllable->set_parameter(param, value); } catch (const ios_base::failure& e) { etiLog.level(info) << "RC: Failed to set " << name << " " << param << " to " << value << ": " << e.what(); throw ParameterError("Cannot understand value"); } } // This runs in a separate thread, because // it would take too long to be done in the main loop // thread. void RemoteControllerTelnet::restart_thread(long) { m_active = false; if (m_child_thread.joinable()) { m_child_thread.join(); } m_child_thread = std::thread(&RemoteControllerTelnet::process, this, 0); } void RemoteControllerTelnet::handle_accept(Socket::TCPSocket&& socket) { const std::string welcome = PACKAGE_NAME " Remote Control CLI\n" "Write 'help' for help.\n" "**********\n"; const std::string prompt = "> "; std::string in_message; try { etiLog.level(info) << "RC: Accepted"; socket.sendall(welcome.data(), welcome.size()); while (m_active and in_message != "quit") { socket.sendall(prompt.data(), prompt.size()); stringstream in_message_stream; char last_char = '\0'; try { while (last_char != '\n') { try { auto ret = socket.recv(&last_char, 1, 0, 1000); if (ret == 1) { in_message_stream << last_char; } else { break; } } catch (const Socket::TCPSocket::Timeout&) { if (not m_active) { break; } } } } catch (const Socket::TCPSocket::Interrupted&) { in_message_stream.clear(); } if (in_message_stream.str().size() == 0) { etiLog.level(info) << "RC: Connection terminated"; break; } std::getline(in_message_stream, in_message); while (in_message.length() > 0 && (in_message[in_message.length()-1] == '\r' || in_message[in_message.length()-1] == '\n')) { in_message.erase(in_message.length()-1, 1); } if (in_message.length() == 0) { continue; } etiLog.level(info) << "RC: Got message '" << in_message << "'"; dispatch_command(socket, in_message); } etiLog.level(info) << "RC: Closing socket"; socket.close(); } catch (const std::exception& e) { etiLog.level(error) << "Remote control caught exception: " << e.what(); } } void RemoteControllerTelnet::process(long) { try { m_active = true; m_socket.listen(m_port, "localhost"); etiLog.level(info) << "RC: Waiting for connection on port " << m_port; while (m_active) { auto sock = m_socket.accept(1000); if (sock.valid()) { handle_accept(move(sock)); etiLog.level(info) << "RC: Connection closed. Waiting for connection on port " << m_port; } } } catch (const runtime_error& e) { etiLog.level(warn) << "RC: Encountered error: " << e.what(); } etiLog.level(info) << "RC: Leaving"; m_fault = true; } static std::vector tokenise(const std::string& message) { stringstream ss(message); std::vector all_tokens; std::string item; while (std::getline(ss, item, ' ')) { all_tokens.push_back(move(item)); } return all_tokens; } void RemoteControllerTelnet::dispatch_command(Socket::TCPSocket& socket, string command) { vector cmd = tokenise(command); if (cmd[0] == "help") { reply(socket, "The following commands are supported:\n" " list\n" " * Lists the modules that are loaded and their parameters\n" " show MODULE\n" " * Lists all parameters and their values from module MODULE\n" " get MODULE PARAMETER\n" " * Gets the value for the specified PARAMETER from module MODULE\n" " set MODULE PARAMETER VALUE\n" " * Sets the value for the PARAMETER ofr module MODULE\n" " quit\n" " * Terminate this session\n" "\n"); } else if (cmd[0] == "list") { stringstream ss; if (cmd.size() == 1) { for (auto &controllable : rcs.controllables) { ss << controllable->get_rc_name() << endl; list< vector > params = controllable->get_parameter_descriptions(); for (auto ¶m : params) { ss << "\t" << param[0] << " : " << param[1] << endl; } } } else { reply(socket, "Too many arguments for command 'list'"); } reply(socket, ss.str()); } else if (cmd[0] == "show") { if (cmd.size() == 2) { try { stringstream ss; list< vector > r = rcs.get_param_list_values(cmd[1]); for (auto ¶m_val : r) { ss << param_val[0] << ": " << param_val[1] << endl; } reply(socket, ss.str()); } catch (const ParameterError &e) { reply(socket, e.what()); } } else { reply(socket, "Incorrect parameters for command 'show'"); } } else if (cmd[0] == "get") { if (cmd.size() == 3) { try { string r = rcs.get_param(cmd[1], cmd[2]); reply(socket, r); } catch (const ParameterError &e) { reply(socket, e.what()); } } else { reply(socket, "Incorrect parameters for command 'get'"); } } else if (cmd[0] == "set") { if (cmd.size() >= 4) { try { stringstream new_param_value; for (size_t i = 3; i < cmd.size(); i++) { new_param_value << cmd[i]; if (i+1 < cmd.size()) { new_param_value << " "; } } rcs.set_param(cmd[1], cmd[2], new_param_value.str()); reply(socket, "ok"); } catch (const ParameterError &e) { reply(socket, e.what()); } catch (const exception &e) { reply(socket, "Error: Invalid parameter value. "); } } else { reply(socket, "Incorrect parameters for command 'set'"); } } else if (cmd[0] == "quit") { reply(socket, "Goodbye"); } else { reply(socket, "Message not understood"); } } void RemoteControllerTelnet::reply(Socket::TCPSocket& socket, string message) { stringstream ss; ss << message << "\r\n"; socket.sendall(message.data(), message.size()); } #if defined(HAVE_ZEROMQ) RemoteControllerZmq::~RemoteControllerZmq() { m_active = false; m_fault = false; if (m_restarter_thread.joinable()) { m_restarter_thread.join(); } if (m_child_thread.joinable()) { m_child_thread.join(); } } void RemoteControllerZmq::restart() { if (m_restarter_thread.joinable()) { m_restarter_thread.join(); } m_restarter_thread = std::thread(&RemoteControllerZmq::restart_thread, this); } // This runs in a separate thread, because // it would take too long to be done in the main loop // thread. void RemoteControllerZmq::restart_thread() { m_active = false; if (m_child_thread.joinable()) { m_child_thread.join(); } m_child_thread = std::thread(&RemoteControllerZmq::process, this); } void RemoteControllerZmq::recv_all(zmq::socket_t& pSocket, std::vector &message) { bool more = true; do { zmq::message_t msg; const auto zresult = pSocket.recv(msg); if (zresult) { std::string incoming((char*)msg.data(), msg.size()); message.push_back(incoming); more = msg.more(); } else { more = false; } } while (more); } void RemoteControllerZmq::send_ok_reply(zmq::socket_t &pSocket) { zmq::message_t msg(2); char repCode[2] = {'o', 'k'}; memcpy ((void*) msg.data(), repCode, 2); pSocket.send(msg, zmq::send_flags::none); } void RemoteControllerZmq::send_fail_reply(zmq::socket_t &pSocket, const std::string &error) { zmq::message_t msg1(4); char repCode[4] = {'f', 'a', 'i', 'l'}; memcpy ((void*) msg1.data(), repCode, 4); pSocket.send(msg1, zmq::send_flags::sndmore); zmq::message_t msg2(error.length()); memcpy ((void*) msg2.data(), error.c_str(), error.length()); pSocket.send(msg2, zmq::send_flags::none); } void RemoteControllerZmq::process() { m_fault = false; m_active = true; // create zmq reply socket for receiving ctrl parameters try { zmq::socket_t repSocket(m_zmqContext, ZMQ_REP); // connect the socket int hwm = 100; int linger = 0; repSocket.setsockopt(ZMQ_RCVHWM, &hwm, sizeof(hwm)); repSocket.setsockopt(ZMQ_SNDHWM, &hwm, sizeof(hwm)); repSocket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); repSocket.bind(m_endpoint.c_str()); // create pollitem that polls the ZMQ sockets zmq::pollitem_t pollItems[] = { {repSocket, 0, ZMQ_POLLIN, 0} }; while (m_active) { zmq::poll(pollItems, 1, 100); std::vector msg; if (pollItems[0].revents & ZMQ_POLLIN) { recv_all(repSocket, msg); std::string command((char*)msg[0].data(), msg[0].size()); if (msg.size() == 1 && command == "ping") { send_ok_reply(repSocket); } else if (msg.size() == 1 && command == "list") { size_t cohort_size = rcs.controllables.size(); for (auto &controllable : rcs.controllables) { std::stringstream ss; ss << "{ \"name\": \"" << controllable->get_rc_name() << "\"," << " \"params\": { "; list< vector > params = controllable->get_parameter_descriptions(); size_t i = 0; for (auto ¶m : params) { if (i > 0) { ss << ", "; } ss << "\"" << param[0] << "\": " << "\"" << param[1] << "\""; i++; } ss << " } }"; std::string msg_s = ss.str(); zmq::message_t zmsg(ss.str().size()); memcpy ((void*) zmsg.data(), msg_s.data(), msg_s.size()); repSocket.send(zmsg, (--cohort_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } else if (msg.size() == 1 && command == "showjson") { try { std::string json = rcs.get_showjson(); zmq::message_t zmsg(json.size()); memcpy(zmsg.data(), json.data(), json.size()); repSocket.send(zmsg, zmq::send_flags::none); } catch (const ParameterError &err) { send_fail_reply(repSocket, err.what()); } } else if (msg.size() == 2 && command == "show") { const std::string module((char*) msg[1].data(), msg[1].size()); try { list< vector > r = rcs.get_param_list_values(module); size_t r_size = r.size(); for (auto ¶m_val : r) { std::stringstream ss; ss << param_val[0] << ": " << param_val[1] << endl; zmq::message_t zmsg(ss.str().size()); memcpy(zmsg.data(), ss.str().data(), ss.str().size()); repSocket.send(zmsg, (--r_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } catch (const ParameterError &err) { send_fail_reply(repSocket, err.what()); } } else if (msg.size() == 3 && command == "get") { const std::string module((char*) msg[1].data(), msg[1].size()); const std::string parameter((char*) msg[2].data(), msg[2].size()); try { std::string value = rcs.get_param(module, parameter); zmq::message_t zmsg(value.size()); memcpy ((void*) zmsg.data(), value.data(), value.size()); repSocket.send(zmsg, zmq::send_flags::none); } catch (const ParameterError &err) { send_fail_reply(repSocket, err.what()); } } else if (msg.size() == 4 && command == "set") { const std::string module((char*) msg[1].data(), msg[1].size()); const std::string parameter((char*) msg[2].data(), msg[2].size()); const std::string value((char*) msg[3].data(), msg[3].size()); try { rcs.set_param(module, parameter, value); send_ok_reply(repSocket); } catch (const ParameterError &err) { send_fail_reply(repSocket, err.what()); } } else { send_fail_reply(repSocket, "Unsupported command. commands: list, show, get, set, showjson"); } } } repSocket.close(); } catch (const zmq::error_t &e) { etiLog.level(error) << "ZMQ RC error: " << std::string(e.what()); } catch (const std::exception& e) { etiLog.level(error) << "ZMQ RC caught exception: " << e.what(); m_fault = true; } } #endif Opendigitalradio-ODR-DabMod-f7eedef/lib/RemoteControl.h000066400000000000000000000166351475762153200231470ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org This module adds remote-control capability to some of the dabmux/dabmod modules. */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #define ENABLE_REMOTECONTROL 1 #if defined(HAVE_ZEROMQ) # include "zmq.hpp" #endif #include #include #include #include #include #include #include #include #include #include #include "Log.h" #include "Socket.h" #include "Json.h" #define RC_ADD_PARAMETER(p, desc) { \ std::vector p; \ p.push_back(#p); \ p.push_back(desc); \ m_parameters.push_back(p); \ } class ParameterError : public std::exception { public: ParameterError(std::string message) : m_message(message) {} ~ParameterError() throw() {} const char* what() const throw() { return m_message.c_str(); } private: std::string m_message; }; class RemoteControllable; /* Remote controllers (that recieve orders from the user) * must implement BaseRemoteController */ class BaseRemoteController { public: /* When this returns one, the remote controller cannot be * used anymore, and must be restarted */ virtual bool fault_detected() = 0; /* In case of a fault, the remote controller can be * restarted. */ virtual void restart() = 0; virtual ~BaseRemoteController() {} }; /* Objects that support remote control must implement the following class */ class RemoteControllable { public: RemoteControllable(const std::string& name) : m_rc_name(name) {} RemoteControllable(const RemoteControllable& other) = delete; RemoteControllable& operator=(const RemoteControllable& other) = delete; virtual ~RemoteControllable(); /* return a short name used to identify the controllable. * It might be used in the commands the user has to type, so keep * it short */ virtual std::string get_rc_name() const { return m_rc_name; } /* Return a list of possible parameters that can be set */ virtual std::list get_supported_parameters() const; /* Return a mapping of the descriptions of all parameters */ virtual std::list< std::vector > get_parameter_descriptions() const { return m_parameters; } /* Base function to set parameters. */ virtual void set_parameter(const std::string& parameter, const std::string& value) = 0; /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const = 0; virtual const json::map_t get_all_values() const = 0; protected: std::string m_rc_name; std::list< std::vector > m_parameters; }; /* Holds all our remote controllers and controlled object. */ class RemoteControllers { public: void add_controller(std::shared_ptr rc); void enrol(RemoteControllable *rc); void remove_controllable(RemoteControllable *rc); void check_faults(); std::list< std::vector > get_param_list_values(const std::string& name); std::string get_param(const std::string& name, const std::string& param); std::string get_showjson(); void set_param( const std::string& name, const std::string& param, const std::string& value); std::list controllables; private: RemoteControllable* get_controllable_(const std::string& name); std::list > m_controllers; }; /* rcs is a singleton used in all parts of the program to interact with the RC. * It is constructed in Globals.cpp */ extern RemoteControllers rcs; /* Implements a Remote controller based on a simple telnet CLI * that listens on localhost */ class RemoteControllerTelnet : public BaseRemoteController { public: RemoteControllerTelnet() : m_active(false), m_fault(false), m_port(0) { } RemoteControllerTelnet(int port) : m_active(port > 0), m_fault(false), m_port(port) { restart(); } RemoteControllerTelnet& operator=(const RemoteControllerTelnet& other) = delete; RemoteControllerTelnet(const RemoteControllerTelnet& other) = delete; ~RemoteControllerTelnet(); virtual bool fault_detected() { return m_fault; } virtual void restart(); private: void restart_thread(long); void process(long); void dispatch_command(Socket::TCPSocket& socket, std::string command); void reply(Socket::TCPSocket& socket, std::string message); void handle_accept(Socket::TCPSocket&& socket); std::atomic m_active; /* This is set to true if a fault occurred */ std::atomic m_fault; std::thread m_restarter_thread; std::thread m_child_thread; Socket::TCPSocket m_socket; int m_port; }; #if defined(HAVE_ZEROMQ) /* Implements a Remote controller using ZMQ transportlayer * that listens on localhost */ class RemoteControllerZmq : public BaseRemoteController { public: RemoteControllerZmq() : m_active(false), m_fault(false), m_zmqContext(1), m_endpoint("") { } RemoteControllerZmq(const std::string& endpoint) : m_active(not endpoint.empty()), m_fault(false), m_zmqContext(1), m_endpoint(endpoint), m_child_thread(&RemoteControllerZmq::process, this) { } RemoteControllerZmq& operator=(const RemoteControllerZmq& other) = delete; RemoteControllerZmq(const RemoteControllerZmq& other) = delete; ~RemoteControllerZmq(); virtual bool fault_detected() { return m_fault; } virtual void restart(); private: void restart_thread(); void recv_all(zmq::socket_t &pSocket, std::vector &message); void send_ok_reply(zmq::socket_t &pSocket); void send_fail_reply(zmq::socket_t &pSocket, const std::string &error); void process(); std::atomic m_active; /* This is set to true if a fault occurred */ std::atomic m_fault; std::thread m_restarter_thread; zmq::context_t m_zmqContext; std::string m_endpoint; std::thread m_child_thread; }; #endif Opendigitalradio-ODR-DabMod-f7eedef/lib/Socket.cpp000066400000000000000000001077141475762153200221350ustar00rootroot00000000000000/* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #include "Socket.h" #include #include #include #include #include #include #include namespace Socket { using namespace std; void InetAddress::resolveUdpDestination(const std::string& destination, int port) { char service[NI_MAXSERV]; snprintf(service, NI_MAXSERV-1, "%d", port); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = 0; hints.ai_protocol = 0; struct addrinfo *result, *rp; int s = getaddrinfo(destination.c_str(), service, &hints, &result); if (s != 0) { throw runtime_error(string("getaddrinfo failed: ") + gai_strerror(s)); } for (rp = result; rp != nullptr; rp = rp->ai_next) { // Take the first result memcpy(&addr, rp->ai_addr, rp->ai_addrlen); break; } freeaddrinfo(result); if (rp == nullptr) { throw runtime_error("Could not resolve"); } } string InetAddress::to_string() const { char received_from_str[64] = {}; sockaddr *addr = reinterpret_cast(&addr); const char* ret = inet_ntop(AF_INET, addr, received_from_str, 63); if (ret == nullptr) { throw invalid_argument(string("Error converting InetAddress") + strerror(errno)); } return ret; } UDPPacket::UDPPacket() { } UDPPacket::UDPPacket(size_t initSize) : buffer(initSize), address() { } UDPSocket::UDPSocket() { reinit(0, ""); } UDPSocket::UDPSocket(int port) { reinit(port, ""); } UDPSocket::UDPSocket(int port, const std::string& name) { reinit(port, name); } UDPSocket::UDPSocket(UDPSocket&& other) { m_sock = other.m_sock; m_port = other.m_port; m_multicast_source = other.m_multicast_source; other.m_port = 0; other.m_sock = INVALID_SOCKET; other.m_multicast_source = ""; } const UDPSocket& UDPSocket::operator=(UDPSocket&& other) { m_sock = other.m_sock; m_port = other.m_port; m_multicast_source = other.m_multicast_source; other.m_port = 0; other.m_sock = INVALID_SOCKET; other.m_multicast_source = ""; return *this; } void UDPSocket::setBlocking(bool block) { int res = fcntl(m_sock, F_SETFL, block ? 0 : O_NONBLOCK); if (res == -1) { throw runtime_error(string("Can't change blocking state of socket: ") + strerror(errno)); } } void UDPSocket::reinit(int port) { return reinit(port, ""); } void UDPSocket::reinit(int port, const std::string& name) { if (m_sock != INVALID_SOCKET) { ::close(m_sock); } m_port = port; if (port == 0) { // No need to bind to a given port, creating the // socket is enough m_sock = ::socket(AF_INET, SOCK_DGRAM, 0); post_init(); return; } char service[NI_MAXSERV]; snprintf(service, NI_MAXSERV-1, "%d", port); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = nullptr; hints.ai_addr = nullptr; hints.ai_next = nullptr; struct addrinfo *result, *rp; int s = getaddrinfo(name.empty() ? nullptr : name.c_str(), port == 0 ? nullptr : service, &hints, &result); if (s != 0) { throw runtime_error(string("getaddrinfo failed: ") + gai_strerror(s)); } /* getaddrinfo() returns a list of address structures. Try each address until we successfully bind(2). If socket(2) (or bind(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != nullptr; rp = rp->ai_next) { int sfd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) { continue; } if (::bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { m_sock = sfd; post_init(); break; } ::close(sfd); } freeaddrinfo(result); if (rp == nullptr) { throw runtime_error(string{"Could not bind to port "} + to_string(port)); } } void UDPSocket::post_init() { int pktinfo = 1; if (setsockopt(m_sock, IPPROTO_IP, IP_PKTINFO, &pktinfo, sizeof(pktinfo)) == SOCKET_ERROR) { throw runtime_error(string("Can't request pktinfo: ") + strerror(errno)); } } void UDPSocket::init_receive_multicast(int port, const string& local_if_addr, const string& mcastaddr) { if (m_sock != INVALID_SOCKET) { ::close(m_sock); } m_port = port; m_sock = ::socket(AF_INET, SOCK_DGRAM, 0); post_init(); int reuse_setting = 1; if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, &reuse_setting, sizeof(reuse_setting)) == SOCKET_ERROR) { throw runtime_error("Can't reuse address"); } struct sockaddr_in la; memset((char *) &la, 0, sizeof(la)); la.sin_family = AF_INET; la.sin_port = htons(port); la.sin_addr.s_addr = INADDR_ANY; if (::bind(m_sock, (struct sockaddr*)&la, sizeof(la))) { throw runtime_error(string("Could not bind: ") + strerror(errno)); } m_multicast_source = mcastaddr; join_group(mcastaddr.c_str(), local_if_addr.c_str()); } void UDPSocket::close() { if (m_sock != INVALID_SOCKET) { ::close(m_sock); } m_sock = INVALID_SOCKET; } UDPSocket::~UDPSocket() { if (m_sock != INVALID_SOCKET) { ::close(m_sock); } } UDPPacket UDPSocket::receive(size_t max_size) { struct sockaddr_in addr; struct msghdr msg; struct iovec iov; constexpr size_t BUFFER_SIZE = 1024; char control_buffer[BUFFER_SIZE]; struct cmsghdr *cmsg; UDPPacket packet(max_size); memset(&msg, 0, sizeof(msg)); msg.msg_name = &addr; msg.msg_namelen = sizeof(addr); msg.msg_iov = &iov; iov.iov_base = packet.buffer.data(); iov.iov_len = packet.buffer.size(); msg.msg_iovlen = 1; msg.msg_control = control_buffer; msg.msg_controllen = sizeof(control_buffer); ssize_t ret = recvmsg(m_sock, &msg, 0); if (ret == SOCKET_ERROR) { packet.buffer.resize(0); // This suppresses the -Wlogical-op warning #if EAGAIN == EWOULDBLOCK if (errno == EAGAIN) #else if (errno == EAGAIN or errno == EWOULDBLOCK) #endif { return packet; } throw runtime_error(string("Can't receive data: ") + strerror(errno)); } struct in_pktinfo *pktinfo = nullptr; for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); break; } } if (pktinfo) { char src_addr[INET_ADDRSTRLEN]; char dst_addr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(addr.sin_addr), src_addr, INET_ADDRSTRLEN); inet_ntop(AF_INET, &(pktinfo->ipi_addr), dst_addr, INET_ADDRSTRLEN); //fprintf(stderr, "Received packet from %s to %s: %zu\n", src_addr, dst_addr, ret); memcpy(&packet.address.addr, &addr, sizeof(addr)); if (m_multicast_source.empty() or strcmp(dst_addr, m_multicast_source.c_str()) == 0) { packet.buffer.resize(ret); } else { // Ignore packet for different multicast group packet.buffer.resize(0); } } else { //fprintf(stderr, "No pktinfo: %zu\n", ret); packet.buffer.resize(ret); } return packet; } void UDPSocket::send(UDPPacket& packet) { const int ret = sendto(m_sock, packet.buffer.data(), packet.buffer.size(), 0, packet.address.as_sockaddr(), sizeof(*packet.address.as_sockaddr())); if (ret == SOCKET_ERROR && errno != ECONNREFUSED) { throw runtime_error(string("Can't send UDP packet: ") + strerror(errno)); } } void UDPSocket::send(const std::vector& data, InetAddress destination) { const int ret = sendto(m_sock, data.data(), data.size(), 0, destination.as_sockaddr(), sizeof(*destination.as_sockaddr())); if (ret == SOCKET_ERROR && errno != ECONNREFUSED) { throw runtime_error(string("Can't send UDP packet: ") + strerror(errno)); } } void UDPSocket::send(const std::string& data, InetAddress destination) { const int ret = sendto(m_sock, data.data(), data.size(), 0, destination.as_sockaddr(), sizeof(*destination.as_sockaddr())); if (ret == SOCKET_ERROR && errno != ECONNREFUSED) { throw runtime_error(string("Can't send UDP packet: ") + strerror(errno)); } } void UDPSocket::join_group(const char* groupname, const char* if_addr) { ip_mreqn group; if ((group.imr_multiaddr.s_addr = inet_addr(groupname)) == INADDR_NONE) { throw runtime_error("Cannot convert multicast group name"); } if (!IN_MULTICAST(ntohl(group.imr_multiaddr.s_addr))) { throw runtime_error(string("Group name '") + groupname + "' is not a multicast address"); } if (if_addr) { group.imr_address.s_addr = inet_addr(if_addr); } else { group.imr_address.s_addr = htons(INADDR_ANY); } group.imr_ifindex = 0; if (setsockopt(m_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) == SOCKET_ERROR) { throw runtime_error(string("Can't join multicast group: ") + strerror(errno)); } } void UDPSocket::setMulticastSource(const char* source_addr) { struct in_addr addr; if (inet_aton(source_addr, &addr) == 0) { throw runtime_error(string("Can't parse source address: ") + strerror(errno)); } if (setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)) == SOCKET_ERROR) { throw runtime_error(string("Can't set source address: ") + strerror(errno)); } } void UDPSocket::setMulticastTTL(int ttl) { if (setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == SOCKET_ERROR) { throw runtime_error(string("Can't set multicast ttl: ") + strerror(errno)); } } SOCKET UDPSocket::getNativeSocket() const { return m_sock; } int UDPSocket::getPort() const { return m_port; } void UDPReceiver::add_receive_port(int port, const string& bindto, const string& mcastaddr) { UDPSocket sock; if (IN_MULTICAST(ntohl(inet_addr(mcastaddr.c_str())))) { sock.init_receive_multicast(port, bindto, mcastaddr); } else { sock.reinit(port, bindto); } m_sockets.push_back(std::move(sock)); } vector UDPReceiver::receive(int timeout_ms) { constexpr size_t MAX_FDS = 64; struct pollfd fds[MAX_FDS]; if (m_sockets.size() > MAX_FDS) { throw std::runtime_error("UDPReceiver only supports up to 64 ports"); } for (size_t i = 0; i < m_sockets.size(); i++) { fds[i].fd = m_sockets[i].getNativeSocket(); fds[i].events = POLLIN; } int retval = poll(fds, m_sockets.size(), timeout_ms); if (retval == -1 and errno == EINTR) { throw Interrupted(); } else if (retval == -1) { std::string errstr(strerror(errno)); throw std::runtime_error("UDP receive with poll() error: " + errstr); } else if (retval > 0) { vector received; for (size_t i = 0; i < m_sockets.size(); i++) { if (fds[i].revents & POLLIN) { auto p = m_sockets[i].receive(2048); // This is larger than the usual MTU if (not p.buffer.empty()) { ReceivedPacket rp; rp.packetdata = std::move(p.buffer); rp.received_from = std::move(p.address); rp.port_received_on = m_sockets[i].getPort(); received.push_back(std::move(rp)); } } } return received; } else { throw Timeout(); } } TCPSocket::TCPSocket() { } TCPSocket::~TCPSocket() { if (m_sock != -1) { ::close(m_sock); } } TCPSocket::TCPSocket(TCPSocket&& other) : m_sock(other.m_sock), m_remote_address(std::move(other.m_remote_address)) { if (other.m_sock != -1) { other.m_sock = -1; } } TCPSocket& TCPSocket::operator=(TCPSocket&& other) { swap(m_remote_address, other.m_remote_address); m_sock = other.m_sock; if (other.m_sock != -1) { other.m_sock = -1; } return *this; } bool TCPSocket::valid() const { return m_sock != -1; } void TCPSocket::connect(const std::string& hostname, int port, int timeout_ms) { if (m_sock != INVALID_SOCKET) { throw std::logic_error("You may only connect an invalid TCPSocket"); } char service[NI_MAXSERV]; snprintf(service, NI_MAXSERV-1, "%d", port); /* Obtain address(es) matching host/port */ struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = 0; struct addrinfo *result, *rp; int s = getaddrinfo(hostname.c_str(), service, &hints, &result); if (s != 0) { throw runtime_error(string("getaddrinfo failed: ") + gai_strerror(s)); } int flags = 0; /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != nullptr; rp = rp->ai_next) { int sfd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; flags = fcntl(sfd, F_GETFL); if (flags == -1) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not get socket flags: " + errstr); } if (fcntl(sfd, F_SETFL, flags | O_NONBLOCK) == -1) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not set O_NONBLOCK: " + errstr); } int ret = ::connect(sfd, rp->ai_addr, rp->ai_addrlen); if (ret == 0) { m_sock = sfd; break; } if (ret == -1 and errno == EINPROGRESS) { m_sock = sfd; struct pollfd fds[1]; fds[0].fd = m_sock; fds[0].events = POLLOUT; int retval = poll(fds, 1, timeout_ms); if (retval == -1) { std::string errstr(strerror(errno)); ::close(m_sock); freeaddrinfo(result); throw runtime_error("TCP: connect error on poll: " + errstr); } else if (retval > 0) { int so_error = 0; socklen_t len = sizeof(so_error); if (getsockopt(m_sock, SOL_SOCKET, SO_ERROR, &so_error, &len) == -1) { std::string errstr(strerror(errno)); ::close(m_sock); freeaddrinfo(result); throw runtime_error("TCP: getsockopt error connect: " + errstr); } if (so_error == 0) { break; } } else { ::close(m_sock); freeaddrinfo(result); throw runtime_error("Timeout on connect"); } break; } ::close(sfd); } if (m_sock != INVALID_SOCKET) { #if defined(HAVE_SO_NOSIGPIPE) int val = 1; if (setsockopt(m_sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)) == SOCKET_ERROR) { throw runtime_error("Can't set SO_NOSIGPIPE"); } #endif } // Don't keep the socket blocking if (fcntl(m_sock, F_SETFL, flags) == -1) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not set O_NONBLOCK: " + errstr); } freeaddrinfo(result); if (rp == nullptr) { throw runtime_error("Could not connect"); } } void TCPSocket::connect(const std::string& hostname, int port, bool nonblock) { if (m_sock != INVALID_SOCKET) { throw std::logic_error("You may only connect an invalid TCPSocket"); } char service[NI_MAXSERV]; snprintf(service, NI_MAXSERV-1, "%d", port); /* Obtain address(es) matching host/port */ struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = 0; hints.ai_protocol = 0; struct addrinfo *result, *rp; int s = getaddrinfo(hostname.c_str(), service, &hints, &result); if (s != 0) { throw runtime_error(string("getaddrinfo failed: ") + gai_strerror(s)); } /* getaddrinfo() returns a list of address structures. Try each address until we successfully connect(2). If socket(2) (or connect(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != nullptr; rp = rp->ai_next) { int sfd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue; if (nonblock) { int flags = fcntl(sfd, F_GETFL); if (flags == -1) { std::string errstr(strerror(errno)); freeaddrinfo(result); ::close(sfd); throw std::runtime_error("TCP: Could not get socket flags: " + errstr); } if (fcntl(sfd, F_SETFL, flags | O_NONBLOCK) == -1) { std::string errstr(strerror(errno)); freeaddrinfo(result); ::close(sfd); throw std::runtime_error("TCP: Could not set O_NONBLOCK: " + errstr); } } int ret = ::connect(sfd, rp->ai_addr, rp->ai_addrlen); if (ret != -1 or (ret == -1 and errno == EINPROGRESS)) { m_sock = sfd; break; } ::close(sfd); } if (m_sock != INVALID_SOCKET) { #if defined(HAVE_SO_NOSIGPIPE) int val = 1; if (setsockopt(m_sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)) == SOCKET_ERROR) { throw std::runtime_error("Can't set SO_NOSIGPIPE"); } #endif } freeaddrinfo(result); /* No longer needed */ if (rp == nullptr) { throw runtime_error("Could not connect"); } } void TCPSocket::enable_keepalive(int time, int intvl, int probes) { if (m_sock == INVALID_SOCKET) { throw std::logic_error("You may not call enable_keepalive on invalid socket"); } int optval = 1; auto optlen = sizeof(optval); if (setsockopt(m_sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not set SO_KEEPALIVE: " + errstr); } optval = time; if (setsockopt(m_sock, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not set TCP_KEEPIDLE: " + errstr); } optval = intvl; if (setsockopt(m_sock, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not set TCP_KEEPINTVL: " + errstr); } optval = probes; if (setsockopt(m_sock, SOL_TCP, TCP_KEEPCNT, &optval, optlen) < 0) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP: Could not set TCP_KEEPCNT: " + errstr); } } void TCPSocket::listen(int port, const string& name) { if (m_sock != INVALID_SOCKET) { throw std::logic_error("You may only listen with an invalid TCPSocket"); } char service[NI_MAXSERV]; snprintf(service, NI_MAXSERV-1, "%d", port); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hints.ai_protocol = 0; hints.ai_canonname = nullptr; hints.ai_addr = nullptr; hints.ai_next = nullptr; struct addrinfo *result, *rp; int s = getaddrinfo(name.empty() ? nullptr : name.c_str(), service, &hints, &result); if (s != 0) { throw runtime_error(string("getaddrinfo failed: ") + gai_strerror(s)); } /* getaddrinfo() returns a list of address structures. Try each address until we successfully bind(2). If socket(2) (or bind(2)) fails, we (close the socket and) try the next address. */ for (rp = result; rp != nullptr; rp = rp->ai_next) { int sfd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) { continue; } int reuse_setting = 1; if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse_setting, sizeof(reuse_setting)) == -1) { throw runtime_error("Can't reuse address"); } if (::bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) { m_sock = sfd; break; } ::close(sfd); } freeaddrinfo(result); if (m_sock != INVALID_SOCKET) { #if defined(HAVE_SO_NOSIGPIPE) int val = 1; if (setsockopt(m_sock, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)) < 0) { throw std::runtime_error("Can't set SO_NOSIGPIPE"); } #endif int ret = ::listen(m_sock, 0); if (ret == -1) { throw std::runtime_error(string("Could not listen: ") + strerror(errno)); } } if (rp == nullptr) { throw runtime_error("Could not bind"); } } void TCPSocket::close() { ::close(m_sock); m_sock = -1; } TCPSocket TCPSocket::accept(int timeout_ms) { if (timeout_ms == 0) { InetAddress remote_addr; socklen_t client_len = sizeof(remote_addr.addr); int sockfd = ::accept(m_sock, remote_addr.as_sockaddr(), &client_len); TCPSocket s(sockfd, remote_addr); return s; } else { struct pollfd fds[1]; fds[0].fd = m_sock; fds[0].events = POLLIN; int retval = poll(fds, 1, timeout_ms); if (retval == -1) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP Socket accept error: " + errstr); } else if (retval > 0) { InetAddress remote_addr; socklen_t client_len = sizeof(remote_addr.addr); int sockfd = ::accept(m_sock, remote_addr.as_sockaddr(), &client_len); TCPSocket s(sockfd, remote_addr); return s; } else { TCPSocket s(-1); return s; } } } ssize_t TCPSocket::sendall(const void *buffer, size_t buflen) { uint8_t *buf = (uint8_t*)buffer; while (buflen > 0) { /* On Linux, the MSG_NOSIGNAL flag ensures that the process * would not receive a SIGPIPE and die. * Other systems have SO_NOSIGPIPE set on the socket for the * same effect. */ #if defined(HAVE_MSG_NOSIGNAL) const int flags = MSG_NOSIGNAL; #else const int flags = 0; #endif ssize_t sent = ::send(m_sock, buf, buflen, flags); if (sent < 0) { return -1; } else { buf += sent; buflen -= sent; } } return buflen; } ssize_t TCPSocket::send(const void* data, size_t size, int timeout_ms) { if (timeout_ms) { struct pollfd fds[1]; fds[0].fd = m_sock; fds[0].events = POLLOUT; const int retval = poll(fds, 1, timeout_ms); if (retval == -1) { throw std::runtime_error(string("TCP Socket send error on poll(): ") + strerror(errno)); } else if (retval == 0) { // Timed out return 0; } } /* On Linux, the MSG_NOSIGNAL flag ensures that the process would not * receive a SIGPIPE and die. * Other systems have SO_NOSIGPIPE set on the socket for the same effect. */ #if defined(HAVE_MSG_NOSIGNAL) const int flags = MSG_NOSIGNAL; #else const int flags = 0; #endif const ssize_t ret = ::send(m_sock, (const char*)data, size, flags); if (ret == SOCKET_ERROR) { throw std::runtime_error(string("TCP Socket send error: ") + strerror(errno)); } return ret; } ssize_t TCPSocket::recv(void *buffer, size_t length, int flags) { ssize_t ret = ::recv(m_sock, buffer, length, flags); if (ret == -1) { if (errno == EINTR) { throw Interrupted(); } else { std::string errstr(strerror(errno)); throw std::runtime_error("TCP receive error: " + errstr); } } return ret; } ssize_t TCPSocket::recv(void *buffer, size_t length, int flags, int timeout_ms) { struct pollfd fds[1]; fds[0].fd = m_sock; fds[0].events = POLLIN; int retval = poll(fds, 1, timeout_ms); if (retval == -1 and errno == EINTR) { throw Interrupted(); } else if (retval == -1) { std::string errstr(strerror(errno)); throw std::runtime_error("TCP receive with poll() error: " + errstr); } else if (retval > 0 and (fds[0].revents & POLLIN)) { ssize_t ret = ::recv(m_sock, buffer, length, flags); if (ret == -1) { if (errno == ECONNREFUSED) { return 0; } std::string errstr(strerror(errno)); throw std::runtime_error("TCP receive after poll() error: " + errstr); } return ret; } else { throw Timeout(); } } TCPSocket::TCPSocket(int sockfd) : m_sock(sockfd), m_remote_address() { } TCPSocket::TCPSocket(int sockfd, InetAddress remote_address) : m_sock(sockfd), m_remote_address(remote_address) { } void TCPClient::connect(const std::string& hostname, int port) { m_hostname = hostname; m_port = port; reconnect(); } ssize_t TCPClient::recv(void *buffer, size_t length, int flags, int timeout_ms) { try { ssize_t ret = m_sock.recv(buffer, length, flags, timeout_ms); if (ret == 0) { m_sock.close(); reconnect(); } m_last_received_packet_ts = chrono::steady_clock::now(); return ret; } catch (const TCPSocket::Interrupted&) { return -1; } catch (const TCPSocket::Timeout&) { const auto timeout = chrono::milliseconds(timeout_ms * 5); if (m_last_received_packet_ts.has_value() and chrono::steady_clock::now() - *m_last_received_packet_ts > timeout) { // This is to catch half-closed TCP connections reconnect(); } return 0; } throw std::logic_error("unreachable"); } void TCPClient::reconnect() { TCPSocket newsock; m_sock = std::move(newsock); m_last_received_packet_ts = nullopt; m_sock.connect(m_hostname, m_port, true); } TCPConnection::TCPConnection(TCPSocket&& sock) : queue(), m_running(true), m_sender_thread(), m_sock(std::move(sock)) { #if MISSING_OWN_ADDR auto own_addr = m_sock.getOwnAddress(); auto addr = m_sock.getRemoteAddress(); etiLog.level(debug) << "New TCP Connection on port " << own_addr.getPort() << " from " << addr.getHostAddress() << ":" << addr.getPort(); #endif m_sender_thread = std::thread(&TCPConnection::process, this); } TCPConnection::~TCPConnection() { m_running = false; vector termination_marker; queue.push(termination_marker); if (m_sender_thread.joinable()) { m_sender_thread.join(); } } void TCPConnection::process() { while (m_running) { vector data; queue.wait_and_pop(data); if (data.empty()) { // empty vector is the termination marker m_running = false; break; } try { ssize_t remaining = data.size(); const uint8_t *buf = reinterpret_cast(data.data()); const int timeout_ms = 10; // Less than one ETI frame while (m_running and remaining > 0) { const ssize_t sent = m_sock.send(buf, remaining, timeout_ms); if (sent < 0 or sent > remaining) { throw std::logic_error("Invalid TCPSocket::send() return value"); } remaining -= sent; buf += sent; } } catch (const std::runtime_error& e) { m_running = false; } } #if MISSING_OWN_ADDR auto own_addr = m_sock.getOwnAddress(); auto addr = m_sock.getRemoteAddress(); etiLog.level(debug) << "Dropping TCP Connection on port " << own_addr.getPort() << " from " << addr.getHostAddress() << ":" << addr.getPort(); #endif } TCPDataDispatcher::TCPDataDispatcher(size_t max_queue_size, size_t buffers_to_preroll) : m_max_queue_size(max_queue_size), m_buffers_to_preroll(buffers_to_preroll) { } TCPDataDispatcher::~TCPDataDispatcher() { m_running = false; m_connections.clear(); m_listener_socket.close(); if (m_listener_thread.joinable()) { m_listener_thread.join(); } } void TCPDataDispatcher::start(int port, const string& address) { m_listener_socket.listen(port, address); m_running = true; m_listener_thread = std::thread(&TCPDataDispatcher::process, this); } void TCPDataDispatcher::write(const vector& data) { if (not m_running) { throw runtime_error(m_exception_data); } auto lock = unique_lock(m_mutex); if (m_buffers_to_preroll > 0) { m_preroll_queue.push_back(data); if (m_preroll_queue.size() > m_buffers_to_preroll) { m_preroll_queue.pop_front(); } } for (auto& connection : m_connections) { connection.queue.push(data); } m_connections.remove_if( [&](const TCPConnection& conn){ return conn.queue.size() > m_max_queue_size; }); } void TCPDataDispatcher::process() { try { const int timeout_ms = 1000; while (m_running) { // Add a new TCPConnection to the list, constructing it from the client socket auto sock = m_listener_socket.accept(timeout_ms); if (sock.valid()) { auto lock = unique_lock(m_mutex); m_connections.emplace(m_connections.begin(), std::move(sock)); if (m_buffers_to_preroll > 0) { for (const auto& buf : m_preroll_queue) { m_connections.front().queue.push(buf); } } } } } catch (const std::runtime_error& e) { m_exception_data = string("TCPDataDispatcher error: ") + e.what(); m_running = false; } } TCPReceiveServer::TCPReceiveServer(size_t blocksize) : m_blocksize(blocksize) { } void TCPReceiveServer::start(int listen_port, const std::string& address) { m_listener_socket.listen(listen_port, address); m_running = true; m_listener_thread = std::thread(&TCPReceiveServer::process, this); } TCPReceiveServer::~TCPReceiveServer() { m_running = false; if (m_listener_thread.joinable()) { m_listener_thread.join(); } } shared_ptr TCPReceiveServer::receive() { shared_ptr buffer = make_shared(); m_queue.try_pop(buffer); // we can ignore try_pop()'s return value, because // if it is unsuccessful the buffer is not touched. return buffer; } void TCPReceiveServer::process() { constexpr int timeout_ms = 1000; constexpr int disconnect_timeout_ms = 10000; constexpr int max_num_timeouts = disconnect_timeout_ms / timeout_ms; while (m_running) { auto sock = m_listener_socket.accept(timeout_ms); int num_timeouts = 0; while (m_running and sock.valid()) { try { vector buf(m_blocksize); ssize_t r = sock.recv(buf.data(), buf.size(), 0, timeout_ms); if (r < 0) { throw logic_error("Invalid recv return value"); } else if (r == 0) { sock.close(); m_queue.push(make_shared()); break; } else { buf.resize(r); m_queue.push(make_shared(std::move(buf))); } } catch (const TCPSocket::Interrupted&) { break; } catch (const TCPSocket::Timeout&) { num_timeouts++; } catch (const runtime_error& e) { sock.close(); // TODO replace fprintf fprintf(stderr, "TCP Receiver restarted after error: %s\n", e.what()); m_queue.push(make_shared()); } if (num_timeouts > max_num_timeouts) { sock.close(); m_queue.push(make_shared()); } } } } TCPSendClient::TCPSendClient(const std::string& hostname, int port) : m_hostname(hostname), m_port(port), m_running(true) { m_sender_thread = std::thread(&TCPSendClient::process, this); } TCPSendClient::~TCPSendClient() { m_running = false; m_queue.trigger_wakeup(); if (m_sender_thread.joinable()) { m_sender_thread.join(); } } TCPSendClient::ErrorStats TCPSendClient::sendall(const std::vector& buffer) { if (not m_running) { throw runtime_error(m_exception_data); } m_queue.push(buffer); if (m_queue.size() > MAX_QUEUE_SIZE) { vector discard; m_queue.try_pop(discard); } TCPSendClient::ErrorStats es; es.num_reconnects = m_num_reconnects.load(); es.has_seen_new_errors = es.num_reconnects != m_num_reconnects_prev; m_num_reconnects_prev = es.num_reconnects; auto lock = unique_lock(m_error_mutex); es.last_error = m_last_error; return es; } void TCPSendClient::process() { try { while (m_running) { if (m_is_connected) { try { vector incoming; m_queue.wait_and_pop(incoming); if (m_sock.sendall(incoming.data(), incoming.size()) == -1) { m_is_connected = false; m_sock = TCPSocket(); } } catch (const ThreadsafeQueueWakeup&) { break; } } else { try { m_num_reconnects.fetch_add(1, std::memory_order_seq_cst); m_sock.connect(m_hostname, m_port); m_is_connected = true; } catch (const runtime_error& e) { m_is_connected = false; this_thread::sleep_for(chrono::seconds(1)); auto lock = unique_lock(m_error_mutex); m_last_error = e.what(); } } } } catch (const runtime_error& e) { m_exception_data = e.what(); m_running = false; } } } Opendigitalradio-ODR-DabMod-f7eedef/lib/Socket.h000066400000000000000000000274001475762153200215730ustar00rootroot00000000000000/* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "ThreadsafeQueue.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 namespace Socket { struct InetAddress { struct sockaddr_storage addr = {}; struct sockaddr *as_sockaddr() { return reinterpret_cast(&addr); }; void resolveUdpDestination(const std::string& destination, int port); std::string to_string() const; }; /** This class represents a UDP packet. * * A UDP packet contains a payload (sequence of bytes) and an address. For * outgoing packets, the address is the destination address. For incoming * packets, the address tells the user from what source the packet arrived from. */ class UDPPacket { public: UDPPacket(); UDPPacket(size_t initSize); std::vector buffer; InetAddress address; }; /** * This class represents a socket for sending and receiving UDP packets. * * A UDP socket is the sending or receiving point for a packet delivery service. * Each packet sent or received on a datagram socket is individually * addressed and routed. Multiple packets sent from one machine to another may * be routed differently, and may arrive in any order. */ class UDPSocket { public: /** Create a new socket that will not be bound to any port. To be used * for data output. */ UDPSocket(); /** Create a new socket. * @param port The port number on which the socket will be bound */ UDPSocket(int port); /** Create a new socket. * @param port The port number on which the socket will be bound * @param name The IP address on which the socket will be bound. * It is used to bind the socket on a specific interface if * the computer have many NICs. */ UDPSocket(int port, const std::string& name); ~UDPSocket(); UDPSocket(const UDPSocket& other) = delete; const UDPSocket& operator=(const UDPSocket& other) = delete; UDPSocket(UDPSocket&& other); const UDPSocket& operator=(UDPSocket&& other); /** Close the already open socket, and create a new one. Throws a runtime_error on error. */ void reinit(int port); void reinit(int port, const std::string& name); void init_receive_multicast(int port, const std::string& local_if_addr, const std::string& mcastaddr); void close(void); void send(UDPPacket& packet); void send(const std::vector& data, InetAddress destination); void send(const std::string& data, InetAddress destination); UDPPacket receive(size_t max_size); void setMulticastSource(const char* source_addr); void setMulticastTTL(int ttl); /** Set blocking mode. By default, the socket is blocking. * throws a runtime_error on error. */ void setBlocking(bool block); SOCKET getNativeSocket() const; int getPort() const; private: void join_group(const char* groupname, const char* if_addr = nullptr); void post_init(); protected: SOCKET m_sock = INVALID_SOCKET; int m_port = 0; std::string m_multicast_source = ""; }; /* UDP packet receiver supporting receiving from several ports at once */ class UDPReceiver { public: void add_receive_port(int port, const std::string& bindto, const std::string& mcastaddr); struct ReceivedPacket { std::vector packetdata; InetAddress received_from; int port_received_on; }; class Interrupted {}; class Timeout {}; /* Returns one or several packets, * throws a Timeout on timeout, Interrupted on EINTR, a runtime_error * on error. */ std::vector receive(int timeout_ms); private: void m_run(void); std::vector m_sockets; }; class TCPSocket { public: TCPSocket(); ~TCPSocket(); TCPSocket(const TCPSocket& other) = delete; TCPSocket& operator=(const TCPSocket& other) = delete; TCPSocket(TCPSocket&& other); TCPSocket& operator=(TCPSocket&& other); bool valid(void) const; void connect(const std::string& hostname, int port, bool nonblock = false); void connect(const std::string& hostname, int port, int timeout_ms); void listen(int port, const std::string& name); void close(void); /* Enable TCP keepalive. See * https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html */ void enable_keepalive(int time, int intvl, int probes); /* throws a runtime_error on failure, an invalid socket on timeout */ TCPSocket accept(int timeout_ms); /* returns -1 on error, doesn't work on nonblocking sockets */ ssize_t sendall(const void *buffer, size_t buflen); /** Send data over the TCP connection. * @param data The buffer that will be sent. * @param size Number of bytes to send. * @param timeout_ms number of milliseconds before timeout, or 0 for infinite timeout * return number of bytes sent, 0 on timeout, or throws runtime_error. */ ssize_t send(const void* data, size_t size, int timeout_ms=0); class Interrupted {}; /* Returns number of bytes read, 0 on disconnect. * Throws Interrupted on EINTR, runtime_error on error */ ssize_t recv(void *buffer, size_t length, int flags); class Timeout {}; /* Returns number of bytes read, 0 on disconnect or refused connection. * Throws a Timeout on timeout, Interrupted on EINTR, a runtime_error * on error */ ssize_t recv(void *buffer, size_t length, int flags, int timeout_ms); SOCKET get_sockfd() const { return m_sock; } private: explicit TCPSocket(int sockfd); explicit TCPSocket(int sockfd, InetAddress remote_address); SOCKET m_sock = -1; InetAddress m_remote_address; friend class TCPClient; }; /* Implements a TCP receiver that auto-reconnects on errors */ class TCPClient { public: void connect(const std::string& hostname, int port); /* Returns numer of bytes read, 0 on auto-reconnect, -1 * on interruption. * Throws a runtime_error on error */ ssize_t recv(void *buffer, size_t length, int flags, int timeout_ms); private: void reconnect(void); TCPSocket m_sock; std::string m_hostname; int m_port; std::optional m_last_received_packet_ts; }; /* Helper class for TCPDataDispatcher, contains a queue of pending data and * a sender thread. */ class TCPConnection { public: TCPConnection(TCPSocket&& sock); TCPConnection(const TCPConnection&) = delete; TCPConnection& operator=(const TCPConnection&) = delete; ~TCPConnection(); ThreadsafeQueue > queue; private: std::atomic m_running; std::thread m_sender_thread; TCPSocket m_sock; void process(void); }; /* Send a TCP stream to several destinations, and automatically disconnect destinations * whose buffer overflows. */ class TCPDataDispatcher { public: TCPDataDispatcher(size_t max_queue_size, size_t buffers_to_preroll); ~TCPDataDispatcher(); TCPDataDispatcher(const TCPDataDispatcher&) = delete; TCPDataDispatcher& operator=(const TCPDataDispatcher&) = delete; void start(int port, const std::string& address); void write(const std::vector& data); private: void process(); size_t m_max_queue_size; size_t m_buffers_to_preroll; std::atomic m_running = ATOMIC_VAR_INIT(false); std::string m_exception_data; std::thread m_listener_thread; TCPSocket m_listener_socket; std::mutex m_mutex; std::deque > m_preroll_queue; std::list m_connections; }; struct TCPReceiveMessage { virtual ~TCPReceiveMessage() {}; }; struct TCPReceiveMessageDisconnected : public TCPReceiveMessage { }; struct TCPReceiveMessageEmpty : public TCPReceiveMessage { }; struct TCPReceiveMessageData : public TCPReceiveMessage { TCPReceiveMessageData(std::vector d) : data(d) {}; std::vector data; }; /* A TCP Server to receive data, which abstracts the handling of connects and disconnects. */ class TCPReceiveServer { public: TCPReceiveServer(size_t blocksize); ~TCPReceiveServer(); TCPReceiveServer(const TCPReceiveServer&) = delete; TCPReceiveServer& operator=(const TCPReceiveServer&) = delete; void start(int listen_port, const std::string& address); // Return an instance of a subclass of TCPReceiveMessage that contains up to blocksize // bytes of data, or TCPReceiveMessageEmpty if no data is available. std::shared_ptr receive(); private: void process(); size_t m_blocksize = 0; ThreadsafeQueue > m_queue; std::atomic m_running = ATOMIC_VAR_INIT(false); std::string m_exception_data; std::thread m_listener_thread; TCPSocket m_listener_socket; }; /* A TCP client that abstracts the handling of connects and disconnects. */ class TCPSendClient { public: TCPSendClient(const std::string& hostname, int port); ~TCPSendClient(); TCPSendClient(const TCPSendClient&) = delete; TCPSendClient& operator=(const TCPSendClient&) = delete; struct ErrorStats { std::string last_error = ""; size_t num_reconnects = 0; bool has_seen_new_errors = false; }; /* Throws a runtime_error when the process thread isn't running */ ErrorStats sendall(const std::vector& buffer); private: void process(); std::string m_hostname; int m_port; bool m_is_connected = false; TCPSocket m_sock; static constexpr size_t MAX_QUEUE_SIZE = 512; ThreadsafeQueue > m_queue; std::atomic m_running; std::string m_exception_data; std::thread m_sender_thread; TCPSocket m_listener_socket; std::atomic m_num_reconnects = ATOMIC_VAR_INIT(0); size_t m_num_reconnects_prev = 0; std::mutex m_error_mutex; std::string m_last_error = ""; }; } Opendigitalradio-ODR-DabMod-f7eedef/lib/ThreadsafeQueue.h000066400000000000000000000146351475762153200234240ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li An implementation for a threadsafe queue, depends on C++11 When creating a ThreadsafeQueue, one can specify the minimal number of elements it must contain before it is possible to take one element out. */ /* 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 . */ #pragma once #include #include #include #include #include /* This queue is meant to be used by two threads. One producer * that pushes elements into the queue, and one consumer that * retrieves the elements. * * The queue can make the consumer block until an element * is available, or a wakeup requested. */ /* Class thrown by blocking pop to tell the consumer * that there's a wakeup requested. */ class ThreadsafeQueueWakeup {}; template class ThreadsafeQueue { public: /* Push one element into the queue, and notify another thread that * might be waiting. * * if max_size > 0 and the queue already contains at least max_size elements, * the element gets discarded. * * returns the new queue size. */ size_t push(T const& val, size_t max_size = 0) { std::unique_lock lock(the_mutex); size_t queue_size_before = the_queue.size(); if (max_size == 0) { the_queue.push(val); } else if (queue_size_before < max_size) { the_queue.push(val); } size_t queue_size = the_queue.size(); lock.unlock(); the_rx_notification.notify_one(); return queue_size; } size_t push(T&& val, size_t max_size = 0) { std::unique_lock lock(the_mutex); size_t queue_size_before = the_queue.size(); if (max_size == 0) { the_queue.emplace(std::move(val)); } else if (queue_size_before < max_size) { the_queue.emplace(std::move(val)); } size_t queue_size = the_queue.size(); lock.unlock(); the_rx_notification.notify_one(); return queue_size; } struct push_overflow_result { bool overflowed; size_t new_size; }; /* Push one element into the queue, and if queue is * full remove one element from the other end. * * max_size == 0 is not allowed. * * returns the new queue size and a flag if overflow occurred. */ push_overflow_result push_overflow(T const& val, size_t max_size) { assert(max_size > 0); std::unique_lock lock(the_mutex); bool overflow = false; while (the_queue.size() >= max_size) { overflow = true; the_queue.pop(); } the_queue.push(val); const size_t queue_size = the_queue.size(); lock.unlock(); the_rx_notification.notify_one(); return {overflow, queue_size}; } push_overflow_result push_overflow(T&& val, size_t max_size) { assert(max_size > 0); std::unique_lock lock(the_mutex); bool overflow = false; while (the_queue.size() >= max_size) { overflow = true; the_queue.pop(); } the_queue.emplace(std::move(val)); const size_t queue_size = the_queue.size(); lock.unlock(); the_rx_notification.notify_one(); return {overflow, queue_size}; } /* Push one element into the queue, but wait until the * queue size goes below the threshold. * * returns the new queue size. */ size_t push_wait_if_full(T const& val, size_t threshold) { std::unique_lock lock(the_mutex); while (the_queue.size() >= threshold) { the_tx_notification.wait(lock); } the_queue.push(val); size_t queue_size = the_queue.size(); lock.unlock(); the_rx_notification.notify_one(); return queue_size; } /* Trigger a wakeup event on a blocking consumer, which * will receive a ThreadsafeQueueWakeup exception. */ void trigger_wakeup(void) { std::unique_lock lock(the_mutex); wakeup_requested = true; lock.unlock(); the_rx_notification.notify_one(); } /* Send a notification for the receiver thread */ void notify(void) { the_rx_notification.notify_one(); } bool empty() const { std::unique_lock lock(the_mutex); return the_queue.empty(); } size_t size() const { std::unique_lock lock(the_mutex); return the_queue.size(); } bool try_pop(T& popped_value) { std::unique_lock lock(the_mutex); if (the_queue.empty()) { return false; } popped_value = the_queue.front(); the_queue.pop(); lock.unlock(); the_tx_notification.notify_one(); return true; } void wait_and_pop(T& popped_value, size_t prebuffering = 1) { std::unique_lock lock(the_mutex); while (the_queue.size() < prebuffering and not wakeup_requested) { the_rx_notification.wait(lock); } if (wakeup_requested) { wakeup_requested = false; throw ThreadsafeQueueWakeup(); } else { std::swap(popped_value, the_queue.front()); the_queue.pop(); lock.unlock(); the_tx_notification.notify_one(); } } private: std::queue the_queue; mutable std::mutex the_mutex; std::condition_variable the_rx_notification; std::condition_variable the_tx_notification; bool wakeup_requested = false; }; Opendigitalradio-ODR-DabMod-f7eedef/lib/crc.c000066400000000000000000000241661475762153200211130ustar00rootroot00000000000000/* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMux. ODR-DabMux 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. ODR-DabMux 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 ODR-DabMux. If not, see . */ #include "crc.h" #ifndef _WIN32 # include # include #endif #include #include //#define CCITT 0x1021 uint8_t crc8tab[256] = { 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 }; uint16_t crc16tab[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; uint32_t crc32tab[256] = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 }; // This function can be used to create a new table with a different polynom void init_crc8tab(uint8_t l_code, uint8_t l_init) { unsigned i, j, msb; uint8_t nb; uint8_t crc; for (i = 0; i < 256; ++i) { crc = l_init; nb = i ^ 0xff; for (j = 0; j < 8; ++j) { msb = (nb & (0x80 >> j)) && 1; msb ^= (crc >> 7); crc <<= 1; if (msb) crc ^= l_code; } crc8tab[i] = crc; } } void init_crc16tab(uint16_t l_code, uint16_t l_init) { unsigned i, j, msb; uint8_t nb; uint16_t crc; for (i = 0; i < 256; ++i) { crc = l_init; nb = i ^ 0xff; for (j = 0; j < 8; ++j) { msb = (nb & (0x80 >> j)) && 1; msb ^= (crc >> 15); crc <<= 1; if (msb) crc ^= l_code; } crc ^= 0xff00; crc16tab[i] = crc; } } void init_crc32tab(uint32_t l_code, uint32_t l_init) { unsigned i, j, msb; uint8_t nb; uint32_t crc; for (i = 0; i < 256; ++i) { crc = l_init; nb = i ^ 0xff; for (j = 0; j < 8; ++j) { msb = (nb & (0x80 >> j)) && 1; msb ^= (crc >> 31); crc <<= 1; if (msb) crc ^= l_code; } crc ^= 0xffffff00; crc32tab[i] = crc; } } uint8_t crc8(uint8_t l_crc, const void *lp_data, unsigned l_nb) { const uint8_t* data = (const uint8_t*)lp_data; while (l_nb--) { l_crc = crc8tab[l_crc ^ *(data++)]; } return (l_crc); } uint16_t crc16(uint16_t l_crc, const void *lp_data, unsigned l_nb) { const uint8_t* data = (const uint8_t*)lp_data; while (l_nb--) { l_crc = (l_crc << 8) ^ crc16tab[(l_crc >> 8) ^ *(data++)]; } return (l_crc); } uint32_t crc32(uint32_t l_crc, const void *lp_data, unsigned l_nb) { const uint8_t* data = (const uint8_t*)lp_data; while (l_nb--) { l_crc = (l_crc << 8) ^ crc32tab[((l_crc >> 24) ^ *(data++)) & 0xff]; } return (l_crc); } Opendigitalradio-ODR-DabMod-f7eedef/lib/crc.h000066400000000000000000000031171475762153200211110ustar00rootroot00000000000000/* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMux. ODR-DabMux 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. ODR-DabMux 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 ODR-DabMux. If not, see . */ #ifndef _CRC #define _CRC #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifndef _WIN32 #include #else #include // For types... typedef BYTE uint8_t; typedef WORD uint16_t; typedef DWORD32 uint32_t; #endif #ifdef __cplusplus extern "C" { // } #endif void init_crc8tab(uint8_t l_code, uint8_t l_init); uint8_t crc8(uint8_t l_crc, const void *lp_data, unsigned l_nb); extern uint8_t crc8tab[]; void init_crc16tab(uint16_t l_code, uint16_t l_init); uint16_t crc16(uint16_t l_crc, const void *lp_data, unsigned l_nb); extern uint16_t crc16tab[]; void init_crc32tab(uint32_t l_code, uint32_t l_init); uint32_t crc32(uint32_t l_crc, const void *lp_data, unsigned l_nb); extern uint32_t crc32tab[]; #ifdef __cplusplus } #endif #endif //_CRC Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/000077500000000000000000000000001475762153200207305ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/ETIDecoder.cpp000066400000000000000000000151561475762153200233530ustar00rootroot00000000000000/* Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "ETIDecoder.hpp" #include "buffer_unpack.hpp" #include "crc.h" #include "Log.h" #include #include #include namespace EdiDecoder { using namespace std; ETIDecoder::ETIDecoder(ETIDataCollector& data_collector) : m_data_collector(data_collector), m_dispatcher(std::bind(&ETIDecoder::packet_completed, this)) { using std::placeholders::_1; using std::placeholders::_2; m_dispatcher.register_tag("*ptr", std::bind(&ETIDecoder::decode_starptr, this, _1, _2)); m_dispatcher.register_tag("deti", std::bind(&ETIDecoder::decode_deti, this, _1, _2)); m_dispatcher.register_tag("est", std::bind(&ETIDecoder::decode_estn, this, _1, _2)); m_dispatcher.register_tag("*dmy", std::bind(&ETIDecoder::decode_stardmy, this, _1, _2)); m_dispatcher.register_afpacket_handler(std::bind(&ETIDecoder::decode_afpacket, this, _1)); } void ETIDecoder::set_verbose(bool verbose) { m_dispatcher.set_verbose(verbose); } void ETIDecoder::push_bytes(const vector &buf) { m_dispatcher.push_bytes(buf); } void ETIDecoder::push_packet(Packet& pack) { m_dispatcher.push_packet(pack); } void ETIDecoder::setMaxDelay(int num_af_packets) { m_dispatcher.setMaxDelay(num_af_packets); } #define AFPACKET_HEADER_LEN 10 // includes SYNC bool ETIDecoder::decode_starptr(const std::vector& value, const tag_name_t& /*n*/) { if (value.size() != 0x40 / 8) { etiLog.log(warn, "Incorrect length %02lx for *PTR", value.size()); return false; } char protocol_sz[5]; protocol_sz[4] = '\0'; copy(value.begin(), value.begin() + 4, protocol_sz); string protocol(protocol_sz); uint16_t major = read_16b(value.begin() + 4); uint16_t minor = read_16b(value.begin() + 6); m_data_collector.update_protocol(protocol, major, minor); return true; } bool ETIDecoder::decode_deti(const std::vector& value, const tag_name_t& /*n*/) { /* uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15); packet.push_back(detiHeader >> 8); packet.push_back(detiHeader & 0xFF); */ uint16_t detiHeader = read_16b(value.begin()); eti_fc_data fc; fc.atstf = (detiHeader >> 15) & 0x1; fc.ficf = (detiHeader >> 14) & 0x1; bool rfudf = (detiHeader >> 13) & 0x1; uint8_t fcth = (detiHeader >> 8) & 0x1F; uint8_t fct = detiHeader & 0xFF; fc.dlfc = fcth * 250 + fct; // modulo 5000 counter uint32_t etiHeader = read_32b(value.begin() + 2); uint8_t stat = (etiHeader >> 24) & 0xFF; fc.mid = (etiHeader >> 22) & 0x03; fc.fp = (etiHeader >> 19) & 0x07; uint8_t rfa = (etiHeader >> 17) & 0x3; if (rfa != 0) { etiLog.log(warn, "EDI deti TAG: rfa non-zero"); } bool rfu = (etiHeader >> 16) & 0x1; uint16_t mnsc = rfu ? 0xFFFF : etiHeader & 0xFFFF; const size_t fic_length_words = (fc.ficf ? (fc.mid == 3 ? 32 : 24) : 0); const size_t fic_length = 4 * fic_length_words; const size_t expected_length = 2 + 4 + (fc.atstf ? 1 + 4 + 3 : 0) + fic_length + (rfudf ? 3 : 0); if (value.size() != expected_length) { throw std::logic_error("EDI deti: Assertion error:" "value.size() != expected_length: " + to_string(value.size()) + " " + to_string(expected_length)); } m_data_collector.update_err(stat); m_data_collector.update_mnsc(mnsc); size_t i = 2 + 4; if (fc.atstf) { uint8_t utco = value[i]; i++; uint32_t seconds = read_32b(value.begin() + i); i += 4; m_data_collector.update_edi_time(utco, seconds); m_received_tagpacket.timestamp.utco = utco; m_received_tagpacket.timestamp.seconds = seconds; fc.tsta = read_24b(value.begin() + i); i += 3; } else { // Null timestamp, ETSI ETS 300 799, C.2.2 fc.tsta = 0xFFFFFF; m_received_tagpacket.timestamp.utco = 0; m_received_tagpacket.timestamp.seconds = 0; } m_received_tagpacket.timestamp.tsta = fc.tsta; if (fc.ficf) { vector fic(fic_length); copy( value.begin() + i, value.begin() + i + fic_length, fic.begin()); i += fic_length; m_data_collector.update_fic(std::move(fic)); } if (rfudf) { uint32_t rfud = read_24b(value.begin() + i); // high 16 bits: RFU in LIDATA EOH // low 8 bits: RFU in TIST (not supported) m_data_collector.update_rfu(rfud >> 8); if ((rfud & 0xFF) != 0xFF) { etiLog.level(warn) << "EDI: RFU in TIST not supported"; } i += 3; } m_data_collector.update_fc_data(fc); return true; } bool ETIDecoder::decode_estn(const std::vector& value, const tag_name_t& name) { uint32_t sstc = read_24b(value.begin()); eti_stc_data stc; const uint8_t n = name[3]; stc.stream_index = n - 1; // n is 1-indexed stc.scid = (sstc >> 18) & 0x3F; stc.sad = (sstc >> 8) & 0x3FF; stc.tpl = (sstc >> 2) & 0x3F; uint8_t rfa = sstc & 0x3; if (rfa != 0) { etiLog.level(warn) << "EDI: rfa field in ESTn tag non-null"; } copy( value.begin() + 3, value.end(), back_inserter(stc.mst)); m_data_collector.add_subchannel(std::move(stc)); return true; } bool ETIDecoder::decode_stardmy(const std::vector&, const tag_name_t&) { return true; } bool ETIDecoder::decode_afpacket(std::vector&& value) { m_received_tagpacket.afpacket = std::move(value); return true; } void ETIDecoder::packet_completed() { m_received_tagpacket.seq = m_dispatcher.get_seq_info(); ReceivedTagPacket tp; swap(tp, m_received_tagpacket); m_data_collector.assemble(std::move(tp)); } } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/ETIDecoder.hpp000066400000000000000000000107731475762153200233600ustar00rootroot00000000000000/* Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "eti.hpp" #include "common.hpp" #include #include #include #include namespace EdiDecoder { // Information for Frame Characterisation available in // EDI. // // Number of streams is given separately, and frame length // is calculated in the ETIDataCollector struct eti_fc_data { bool atstf; uint32_t tsta; bool ficf; uint16_t dlfc; uint8_t mid; uint8_t fp; uint8_t fct(void) const { return dlfc % 250; } }; // Information for a subchannel available in EDI struct eti_stc_data { uint8_t stream_index; uint8_t scid; uint16_t sad; uint8_t tpl; std::vector mst; // Return the length of the MST in multiples of 64 bits uint16_t stl(void) const { return mst.size() / 8; } }; struct ReceivedTagPacket { std::vector afpacket; frame_timestamp_t timestamp; seq_info_t seq; }; /* A class that receives multiplex data must implement the interface described * in the ETIDataCollector. This can be e.g. a converter to ETI, or something that * prepares data structures for a modulator. */ class ETIDataCollector { public: // Tell the ETIWriter what EDI protocol we receive in *ptr. // This is not part of the ETI data, but is used as check virtual void update_protocol( const std::string& proto, uint16_t major, uint16_t minor) = 0; // Update the data for the frame characterisation virtual void update_fc_data(const eti_fc_data& fc_data) = 0; virtual void update_fic(std::vector&& fic) = 0; virtual void update_err(uint8_t err) = 0; // In addition to TSTA in ETI, EDI also transports more time // stamp information. virtual void update_edi_time(uint32_t utco, uint32_t seconds) = 0; virtual void update_mnsc(uint16_t mnsc) = 0; virtual void update_rfu(uint16_t rfu) = 0; virtual void add_subchannel(eti_stc_data&& stc) = 0; // Tell the consumer that the AFPacket is complete, and include // the raw received TAGs virtual void assemble(ReceivedTagPacket&& tagpacket) = 0; }; /* The ETIDecoder takes care of decoding the EDI TAGs related to the transport * of ETI(NI) data inside AF and PF packets. * * PF packets are handed over to the PFT decoder, which will in turn return * AF packets. AF packets are directly handled (TAG extraction) here. */ class ETIDecoder { public: ETIDecoder(ETIDataCollector& data_collector); void set_verbose(bool verbose); /* Push bytes into the decoder. The buf can contain more * than a single packet. This is useful when reading from streams * (files, TCP) */ void push_bytes(const std::vector &buf); /* Push a complete packet into the decoder. Useful for UDP and other * datagram-oriented protocols. */ void push_packet(Packet &pack); /* Set the maximum delay in number of AF Packets before we * abandon decoding a given pseq. */ void setMaxDelay(int num_af_packets); private: bool decode_starptr(const std::vector& value, const tag_name_t& n); bool decode_deti(const std::vector& value, const tag_name_t& n); bool decode_estn(const std::vector& value, const tag_name_t& n); bool decode_stardmy(const std::vector& value, const tag_name_t& n); bool decode_afpacket(std::vector&& value); void packet_completed(); ETIDataCollector& m_data_collector; TagDispatcher m_dispatcher; ReceivedTagPacket m_received_tagpacket; }; } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/PFT.cpp000066400000000000000000000450661475762153200221000ustar00rootroot00000000000000/* ------------------------------------------------------------------ * Copyright (C) 2017 AVT GmbH - Fabien Vercasson * Copyright (C) 2021 Matthias P. Braendli * matthias.braendli@mpb.li * * http://opendigitalradio.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * See the License for the specific language governing permissions * and limitations under the License. * ------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include "crc.h" #include "PFT.hpp" #include "Log.h" #include "buffer_unpack.hpp" extern "C" { #include "fec/fec.h" } namespace EdiDecoder { namespace PFT { using namespace std; const findex_t NUM_AFBUILDERS_TO_KEEP = 10; static bool checkCRC(const uint8_t *buf, size_t size) { const uint16_t crc_from_packet = read_16b(buf + size - 2); uint16_t crc_calc = 0xffff; crc_calc = crc16(crc_calc, buf, size - 2); crc_calc ^= 0xffff; return crc_from_packet == crc_calc; } class FECDecoder { public: FECDecoder() { m_rs_handler = init_rs_char( symsize, gfPoly, firstRoot, primElem, nroots, pad); } FECDecoder(const FECDecoder& other) = delete; FECDecoder& operator=(const FECDecoder& other) = delete; ~FECDecoder() { free_rs_char(m_rs_handler); } // return -1 in case of failure, non-negative value if errors // were corrected. // Known positions of erasures should be given in eras_pos to // improve decoding probability. After calling this function // eras_pos will contain the positions of the corrected errors. int decode(vector &data, vector &eras_pos) { assert(data.size() == N); const size_t no_eras = eras_pos.size(); eras_pos.resize(nroots); int num_err = decode_rs_char(m_rs_handler, data.data(), eras_pos.data(), no_eras); if (num_err > 0) { eras_pos.resize(num_err); } return num_err; } // return -1 in case of failure, non-negative value if errors // were corrected. No known erasures. int decode(vector &data) { assert(data.size() == N); int num_err = decode_rs_char(m_rs_handler, data.data(), nullptr, 0); return num_err; } private: void* m_rs_handler; const int firstRoot = 1; // Discovered by analysing EDI dump const int gfPoly = 0x11d; // The encoding has to be 255, 207 always, because the chunk has to // be padded at the end, and not at the beginning as libfec would // do const size_t N = 255; const size_t K = 207; const int primElem = 1; const int symsize = 8; const size_t nroots = N - K; // For EDI PFT, this must be 48 const size_t pad = ((1 << symsize) - 1) - N; // is 255-N }; size_t Fragment::loadData(const std::vector &buf) { return loadData(buf, 0); } size_t Fragment::loadData(const std::vector &buf, int received_on_port) { const size_t header_len = 14; if (buf.size() < header_len) { return 0; } this->received_on_port = received_on_port; size_t index = 0; // Parse PFT Fragment Header (ETSI TS 102 821 V1.4.1 ch7.1) if (not (buf[0] == 'P' and buf[1] == 'F') ) { throw runtime_error("Invalid PFT SYNC bytes"); } index += 2; // Psync _Pseq = read_16b(buf.begin()+index); index += 2; _Findex = read_24b(buf.begin()+index); index += 3; _Fcount = read_24b(buf.begin()+index); index += 3; _FEC = unpack1bit(buf[index], 0); _Addr = unpack1bit(buf[index], 1); _Plen = read_16b(buf.begin()+index) & 0x3FFF; index += 2; const size_t required_len = header_len + (_FEC ? 1 : 0) + (_Addr ? 2 : 0) + 2; // CRC if (buf.size() < required_len) { return 0; } // Optional RS Header _RSk = 0; _RSz = 0; if (_FEC) { _RSk = buf[index]; index += 1; _RSz = buf[index]; index += 1; } // Optional transport header _Source = 0; _Dest = 0; if (_Addr) { _Source = read_16b(buf.begin()+index); index += 2; _Dest = read_16b(buf.begin()+index); index += 2; } index += 2; const bool crc_valid = checkCRC(buf.data(), index); const bool buf_has_enough_data = (buf.size() >= index + _Plen); if (not buf_has_enough_data) { return 0; } _valid = ((not _FEC) or crc_valid) and buf_has_enough_data; #if 0 if (!_valid) { stringstream ss; ss << "Invalid PF fragment: "; if (_FEC) { ss << " RSk=" << (uint32_t)_RSk << " RSz=" << (uint32_t)_RSz; } if (_Addr) { ss << " Source=" << _Source << " Dest=" << _Dest; } etiLog.log(debug, "%s\n", ss.str().c_str()); } #endif _payload.clear(); if (_valid) { copy( buf.begin()+index, buf.begin()+index+_Plen, back_inserter(_payload)); index += _Plen; } return index; } AFBuilder::AFBuilder(pseq_t Pseq, findex_t Fcount, size_t lifetime) { _Pseq = Pseq; _Fcount = Fcount; assert(lifetime > 0); lifeTime = lifetime; } void AFBuilder::pushPFTFrag(const Fragment &frag) { if (_Pseq != frag.Pseq()) { throw logic_error("Invalid PFT fragment Pseq"); } if (_Fcount != frag.Fcount()) { etiLog.level(warn) << "Discarding fragment with invalid fcount"; } else { const auto Findex = frag.Findex(); const bool fragment_already_received = _fragments.count(Findex); if (not fragment_already_received) { bool consistent = true; if (_fragments.size() > 0) { consistent = frag.checkConsistency(_fragments.cbegin()->second); } if (consistent) { _fragments[Findex] = frag; } else { etiLog.level(warn) << "Discard fragment"; } } } } bool Fragment::checkConsistency(const Fragment& other) const { /* Consistency check, TS 102 821 Clause 7.3.2. * * Every PFT Fragment produced from a single AF or RS Packet shall have * the same values in all of the PFT Header fields except for the Findex, * Plen and HCRC fields. */ return other._Fcount == _Fcount and other._FEC == _FEC and other._RSk == _RSk and other._RSz == _RSz and other._Addr == _Addr and other._Source == _Source and other._Dest == _Dest and /* The Plen field of all fragments shall be the s for the initial f-1 * fragments and s - (L%f) for the final fragment. * Note that when Reed Solomon has been used, all fragments will be of * length s. */ (_FEC ? other._Plen == _Plen : true); } AFBuilder::decode_attempt_result_t AFBuilder::canAttemptToDecode() { if (_fragments.empty()) { return AFBuilder::decode_attempt_result_t::no; } if (_fragments.size() == _Fcount) { return AFBuilder::decode_attempt_result_t::yes; } /* Check that all fragments are consistent */ const Fragment& first = _fragments.begin()->second; if (not std::all_of(_fragments.begin(), _fragments.end(), [&](const pair& pair) { const Fragment& frag = pair.second; return first.checkConsistency(frag) and _Pseq == frag.Pseq(); }) ) { _fragments.clear(); throw runtime_error("Inconsistent PFT fragments"); } // Calculate the minimum number of fragments necessary to apply FEC. // This can't be done with the last fragment that may have a // smaller size // ETSI TS 102 821 V1.4.1 ch 7.4.4 auto frag_it = _fragments.begin(); if (frag_it->second.Fcount() == _Fcount - 1) { frag_it++; if (frag_it == _fragments.end()) { return AFBuilder::decode_attempt_result_t::no; } } const Fragment& frag = frag_it->second; if ( frag.FEC() ) { const uint16_t _Plen = frag.Plen(); /* max number of RS chunks that may have been sent */ const uint32_t _cmax = (_Fcount*_Plen) / (frag.RSk()+48); assert(_cmax > 0); /* Receiving _rxmin fragments does not guarantee that decoding * will succeed! */ const uint32_t _rxmin = _Fcount - (_cmax*48)/_Plen; if (_fragments.size() >= _rxmin) { return AFBuilder::decode_attempt_result_t::maybe; } } return AFBuilder::decode_attempt_result_t::no; } std::vector AFBuilder::extractAF() { if (not _af_packet.empty()) { return _af_packet; } bool ok = false; if (canAttemptToDecode() != AFBuilder::decode_attempt_result_t::no) { auto frag_it = _fragments.begin(); if (frag_it->second.Fcount() == _Fcount - 1) { frag_it++; if (frag_it == _fragments.end()) { throw runtime_error("Invalid attempt at extracting AF"); } } const Fragment& ref_frag = frag_it->second; const auto RSk = ref_frag.RSk(); const auto RSz = ref_frag.RSz(); const auto Plen = ref_frag.Plen(); if ( ref_frag.FEC() ) { const uint32_t cmax = (_Fcount*Plen) / (RSk+48); // Keep track of erasures (missing fragments) for // every chunk map > erasures; // Assemble fragments into a RS block, immediately // deinterleaving it. vector rs_block(Plen * _Fcount); for (size_t j = 0; j < _Fcount; j++) { const bool fragment_present = _fragments.count(j); if (fragment_present) { const auto& fragment = _fragments.at(j).payload(); if (j != _Fcount - 1 and fragment.size() != Plen) { _fragments.clear(); throw runtime_error("Incorrect fragment length " + to_string(fragment.size()) + " " + to_string(Plen)); } if (j == _Fcount - 1 and fragment.size() > Plen) { _fragments.clear(); throw runtime_error("Incorrect last fragment length " + to_string(fragment.size()) + " " + to_string(Plen)); } size_t k = 0; for (; k < fragment.size(); k++) { rs_block[k * _Fcount + j] = fragment[k]; } for (; k < Plen; k++) { rs_block[k * _Fcount + j] = 0x00; } } else { // fill with zeros if fragment is missing for (size_t k = 0; k < Plen; k++) { rs_block[k * _Fcount + j] = 0x00; const size_t chunk_ix = (k * _Fcount + j) / (RSk + 48); const size_t chunk_offset = (k * _Fcount + j) % (RSk + 48); erasures[chunk_ix].push_back(chunk_offset); } } } // The RS block is a concatenation of chunks of RSk bytes + 48 parity // followed by RSz padding FECDecoder fec; for (size_t i = 0; i < cmax; i++) { // We need to pad the chunk ourself vector chunk(255); const auto& block_begin = rs_block.begin() + (RSk + 48) * i; copy(block_begin, block_begin + RSk, chunk.begin()); // bytes between RSk and 207 are 0x00 already copy(block_begin + RSk, block_begin + RSk + 48, chunk.begin() + 207); int errors_corrected = -1; if (erasures.count(i)) { errors_corrected = fec.decode(chunk, erasures[i]); } else { errors_corrected = fec.decode(chunk); } if (errors_corrected == -1) { _af_packet.clear(); return {}; } #if 0 if (errors_corrected > 0) { etiLog.log(debug, "Corrected %d errors at ", errors_corrected); for (const auto &index : erasures[i]) { etiLog.log(debug, " %d", index); } etiLog.log(debug, "\n"); } #endif _af_packet.insert(_af_packet.end(), chunk.begin(), chunk.begin() + RSk); } _af_packet.resize(_af_packet.size() - RSz); } else { // No FEC: just assemble fragments for (size_t j = 0; j < _Fcount; ++j) { const bool fragment_present = _fragments.count(j); if (fragment_present) { const auto& fragment = _fragments.at(j); _af_packet.insert(_af_packet.end(), fragment.payload().begin(), fragment.payload().end()); } else { throw logic_error("Missing fragment"); } } } // EDI specific, must have a CRC. if (_af_packet.size() >= 12) { ok = checkCRC(_af_packet.data(), _af_packet.size()); if (not ok) { etiLog.log(debug, "CRC error after AF reconstruction from %zu/%u" " PFT fragments\n", _fragments.size(), _Fcount); } } } if (not ok) { _af_packet.clear(); } return _af_packet; } std::string AFBuilder::visualise() { stringstream ss; ss << "|"; for (size_t i = 0; i < _Fcount; i++) { if (_fragments.count(i)) { ss << "."; } else { ss << " "; } } ss << "| " << AFBuilder::dar_to_string(canAttemptToDecode()) << " " << lifeTime; return ss.str(); } std::string AFBuilder::visualise_fragment_origins() const { stringstream ss; if (_fragments.size() == 0) { return "No fragments"; } else { ss << _fragments.size() << " fragments: "; } std::map port_count; for (const auto& f : _fragments) { port_count[f.second.received_on_port]++; } for (const auto& p : port_count) { ss << "p" << p.first << " " << std::round(100.0 * ((double)p.second) / (double)_fragments.size()) << "% "; } ss << "\n"; return ss.str(); } void PFT::pushPFTFrag(const Fragment &fragment) { // Start decoding the first pseq we receive. In normal // operation without interruptions, the map should // never become empty if (m_afbuilders.empty()) { m_next_pseq = fragment.Pseq(); etiLog.log(debug,"Initialise next_pseq to %u\n", m_next_pseq); } if (m_afbuilders.count(fragment.Pseq()) == 0) { // The AFBuilder wants to know the lifetime in number of fragments, // we know the delay in number of AF packets. Every AF packet // is cut into Fcount fragments. const size_t lifetime = fragment.Fcount() * m_max_delay; // Build the afbuilder in the map in-place m_afbuilders.emplace(std::piecewise_construct, /* key */ std::forward_as_tuple(fragment.Pseq()), /* builder */ std::forward_as_tuple(fragment.Pseq(), fragment.Fcount(), lifetime)); } auto& p = m_afbuilders.at(fragment.Pseq()); p.pushPFTFrag(fragment); if (m_verbose) { etiLog.log(debug, "Got frag %u:%u, afbuilders: ", fragment.Pseq(), fragment.Findex()); for (auto &k : m_afbuilders) { const bool isNextPseq = (m_next_pseq == k.first); etiLog.level(debug) << (isNextPseq ? "->" : " ") << k.first << " " << k.second.visualise(); } } } afpacket_pft_t PFT::getNextAFPacket() { afpacket_pft_t af; if (m_afbuilders.count(m_next_pseq) == 0) { if (m_afbuilders.size() > m_max_delay) { m_afbuilders.clear(); etiLog.level(debug) << " Reinit"; } return af; } auto &builder = m_afbuilders.at(m_next_pseq); using dar_t = AFBuilder::decode_attempt_result_t; if (builder.canAttemptToDecode() == dar_t::yes) { auto afpacket = builder.extractAF(); // Empty AF Packet can happen if CRC is wrong if (m_verbose) { etiLog.level(debug) << "Fragment origin stats: " << builder.visualise_fragment_origins(); } af.pseq = m_next_pseq; af.af_packet = afpacket; incrementNextPseq(); } else if (builder.canAttemptToDecode() == dar_t::maybe) { if (builder.lifeTime > 0) { builder.lifeTime--; } if (builder.lifeTime == 0) { // Attempt Reed-Solomon decoding auto afpacket = builder.extractAF(); if (afpacket.empty()) { etiLog.log(debug, "pseq %d timed out after RS", m_next_pseq); } if (m_verbose) { etiLog.level(debug) << "Fragment origin stats: " << builder.visualise_fragment_origins(); } af.pseq = m_next_pseq; af.af_packet = afpacket; incrementNextPseq(); } } else { if (builder.lifeTime > 0) { builder.lifeTime--; } if (builder.lifeTime == 0) { etiLog.log(debug, "pseq %d timed out\n", m_next_pseq); incrementNextPseq(); } } return af; } void PFT::setMaxDelay(size_t num_af_packets) { m_max_delay = num_af_packets; } void PFT::setVerbose(bool enable) { m_verbose = enable; } void PFT::incrementNextPseq() { if (m_afbuilders.count(m_next_pseq - NUM_AFBUILDERS_TO_KEEP) > 0) { m_afbuilders.erase(m_next_pseq - NUM_AFBUILDERS_TO_KEEP); } m_next_pseq++; } } } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/PFT.hpp000066400000000000000000000124141475762153200220740ustar00rootroot00000000000000/* ------------------------------------------------------------------ * Copyright (C) 2017 AVT GmbH - Fabien Vercasson * Copyright (C) 2021 Matthias P. Braendli * matthias.braendli@mpb.li * * http://opendigitalradio.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. * See the License for the specific language governing permissions * and limitations under the License. * ------------------------------------------------------------------- */ #pragma once #include #include #include #include #include namespace EdiDecoder { namespace PFT { using pseq_t = uint16_t; using findex_t = uint32_t; // findex is a 24-bit value class Fragment { public: int received_on_port = 0; // Load the data for one fragment from buf into // the Fragment. // \returns the number of bytes of useful data found in buf // A non-zero return value doesn't imply a valid fragment // the isValid() method must be used to verify this. size_t loadData(const std::vector &buf, int received_on_port); size_t loadData(const std::vector &buf); bool isValid() const { return _valid; } pseq_t Pseq() const { return _Pseq; } findex_t Findex() const { return _Findex; } findex_t Fcount() const { return _Fcount; } bool FEC() const { return _FEC; } uint16_t Plen() const { return _Plen; } uint8_t RSk() const { return _RSk; } uint8_t RSz() const { return _RSz; } const std::vector& payload() const { return _payload; } bool checkConsistency(const Fragment& other) const; private: std::vector _payload; pseq_t _Pseq = 0; findex_t _Findex = 0; findex_t _Fcount = 0; bool _FEC = false; bool _Addr = false; uint16_t _Plen = 0; uint8_t _RSk = 0; uint8_t _RSz = 0; uint16_t _Source = 0; uint16_t _Dest = 0; bool _valid = false; }; /* The AFBuilder collects Fragments and builds an Application Frame * out of them. It does error correction if necessary */ class AFBuilder { public: enum class decode_attempt_result_t { yes, // The AF packet can be build because all fragments are present maybe, // RS decoding may correctly decode the AF packet no, // Not enough fragments present to permit RS }; static std::string dar_to_string(decode_attempt_result_t dar) { switch (dar) { case decode_attempt_result_t::yes: return "y"; case decode_attempt_result_t::no: return "n"; case decode_attempt_result_t::maybe: return "m"; } return "?"; } AFBuilder(pseq_t Pseq, findex_t Fcount, size_t lifetime); void pushPFTFrag(const Fragment &frag); /* Assess if it may be possible to decode this AF packet */ decode_attempt_result_t canAttemptToDecode(); /* Try to build the AF with received fragments. * Apply error correction if necessary (missing packets/CRC errors) * \return an empty vector if building the AF is not possible */ std::vector extractAF(); std::pair numberOfFragments(void) const { return {_fragments.size(), _Fcount}; } std::string visualise(); std::string visualise_fragment_origins() const; /* The user of this instance can keep track of the lifetime of this * builder */ size_t lifeTime; private: // A map from fragment index to fragment std::map _fragments; // cached version of decoded AF packet mutable std::vector _af_packet; pseq_t _Pseq; findex_t _Fcount; }; struct afpacket_pft_t { // validity of the struct is given by af_packet begin empty or not. std::vector af_packet; pseq_t pseq = 0; }; class PFT { public: void pushPFTFrag(const Fragment &fragment); /* Try to build the AF packet for the next pseq. This might * skip one or more pseq according to the maximum delay setting. * * \return an empty vector if building the AF is not possible */ afpacket_pft_t getNextAFPacket(); /* Set the maximum delay in number of AF Packets before we * abandon decoding a given pseq. */ void setMaxDelay(size_t num_af_packets); /* Enable verbose fprintf */ void setVerbose(bool enable); private: void incrementNextPseq(); pseq_t m_next_pseq; size_t m_max_delay = 10; // in AF packets // Keep one AFBuilder for each Pseq std::map m_afbuilders; bool m_verbose = 0; }; } } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/README.md000066400000000000000000000000741475762153200222100ustar00rootroot00000000000000These files are copied from the common ODR code repository. Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/buffer_unpack.hpp000066400000000000000000000031011475762153200242460ustar00rootroot00000000000000/* Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include namespace EdiDecoder { template uint16_t read_16b(T buf) { uint16_t value = 0; value = (uint16_t)(buf[0]) << 8; value |= (uint16_t)(buf[1]); return value; } template uint32_t read_24b(T buf) { uint32_t value = 0; value = (uint32_t)(buf[0]) << 16; value |= (uint32_t)(buf[1]) << 8; value |= (uint32_t)(buf[2]); return value; } template uint32_t read_32b(T buf) { uint32_t value = 0; value = (uint32_t)(buf[0]) << 24; value |= (uint32_t)(buf[1]) << 16; value |= (uint32_t)(buf[2]) << 8; value |= (uint32_t)(buf[3]); return value; } inline uint32_t unpack1bit(uint8_t byte, int bitpos) { return (byte & 1 << (7-bitpos)) > (7-bitpos); } } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/common.cpp000066400000000000000000000333641475762153200227350ustar00rootroot00000000000000/* Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "common.hpp" #include "buffer_unpack.hpp" #include "Log.h" #include "crc.h" #include #include #include #include #include #include namespace EdiDecoder { using namespace std; bool frame_timestamp_t::is_valid() const { return tsta != 0xFFFFFF and seconds != 0; } string frame_timestamp_t::to_string() const { const time_t seconds_in_unix_epoch = to_unix_epoch(); stringstream ss; if (is_valid()) { ss << "Timestamp: "; } else { ss << "Timestamp not valid: "; } char timestr[100]; if (std::strftime(timestr, sizeof(timestr), "%Y-%m-%dZ%H:%M:%S", std::gmtime(&seconds_in_unix_epoch))) { ss << timestr << " + " << ((double)tsta / 16384000.0); } else { ss << "unknown"; } return ss.str(); } time_t frame_timestamp_t::to_unix_epoch() const { // EDI epoch: 2000-01-01T00:00:00Z // Convert using // TZ=UTC python -c 'import datetime; print(datetime.datetime(2000,1,1,0,0,0,0).strftime("%s"))' return 946684800 + seconds - utco; } double frame_timestamp_t::diff_s(const frame_timestamp_t& other) const { const double lhs = (double)seconds + (tsta / 16384000.0); const double rhs = (double)other.seconds + (other.tsta / 16384000.0); return lhs - rhs; } frame_timestamp_t& frame_timestamp_t::operator+=(const std::chrono::milliseconds& ms) { tsta += (ms.count() % 1000) << 14; // Shift ms by 14 to Timestamp level 2 if (tsta > 0xf9FFff) { tsta -= 0xfa0000; // Substract 16384000, corresponding to one second seconds += 1; } seconds += (ms.count() / 1000); return *this; } frame_timestamp_t frame_timestamp_t::from_unix_epoch(std::time_t time, uint32_t tai_utc_offset, uint32_t tsta) { frame_timestamp_t ts; const std::time_t posix_timestamp_1_jan_2000 = 946684800; ts.utco = tai_utc_offset - 32; ts.seconds = time - posix_timestamp_1_jan_2000 + ts.utco; ts.tsta = tsta; return ts; } std::chrono::system_clock::time_point frame_timestamp_t::to_system_clock() const { auto ts = chrono::system_clock::from_time_t(to_unix_epoch()); // PPS offset in seconds = tsta / 16384000 // We cannot use nanosecond resolution because not all platforms use a // system_clock that has nanosecond precision. It's not really important, // as this function is only used for debugging. ts += chrono::microseconds(std::lrint(tsta / 16.384)); return ts; } std::string tag_name_to_human_readable(const tag_name_t& name) { std::string s; for (const uint8_t c : name) { if (isprint(c)) { s += (char)c; } else { char escaped[5]; snprintf(escaped, 5, "\\x%02x", c); s += escaped; } } return s; } TagDispatcher::TagDispatcher(std::function&& af_packet_completed) : m_af_packet_completed(std::move(af_packet_completed)), m_afpacket_handler([](std::vector&& /*ignore*/){}) { } void TagDispatcher::set_verbose(bool verbose) { m_pft.setVerbose(verbose); } void TagDispatcher::push_bytes(const vector &buf) { if (buf.empty()) { m_input_data.clear(); m_last_sequences.seq_valid = false; return; } copy(buf.begin(), buf.end(), back_inserter(m_input_data)); while (m_input_data.size() > 2) { if (m_input_data[0] == 'A' and m_input_data[1] == 'F') { const auto r = decode_afpacket(m_input_data); bool leave_loop = false; switch (r.st) { case decode_state_e::Ok: m_last_sequences.pseq_valid = false; m_af_packet_completed(); break; case decode_state_e::MissingData: /* Continue filling buffer */ leave_loop = true; break; case decode_state_e::Error: m_last_sequences.pseq_valid = false; leave_loop = true; break; } if (r.num_bytes_consumed) { vector remaining_data; copy(m_input_data.begin() + r.num_bytes_consumed, m_input_data.end(), back_inserter(remaining_data)); m_input_data = remaining_data; } if (leave_loop) { break; } } else if (m_input_data[0] == 'P' and m_input_data[1] == 'F') { PFT::Fragment fragment; const size_t fragment_bytes = fragment.loadData(m_input_data); if (fragment_bytes == 0) { // We need to refill our buffer break; } vector remaining_data; copy(m_input_data.begin() + fragment_bytes, m_input_data.end(), back_inserter(remaining_data)); m_input_data = remaining_data; if (fragment.isValid()) { m_pft.pushPFTFrag(fragment); } auto af = m_pft.getNextAFPacket(); if (not af.af_packet.empty()) { const auto r = decode_afpacket(af.af_packet); switch (r.st) { case decode_state_e::Ok: m_last_sequences.pseq = af.pseq; m_last_sequences.pseq_valid = true; m_af_packet_completed(); break; case decode_state_e::MissingData: etiLog.level(error) << "ETI MissingData on PFT push_bytes"; m_last_sequences.pseq_valid = false; break; case decode_state_e::Error: m_last_sequences.pseq_valid = false; break; } } } else { etiLog.log(warn, "Unknown 0x%02x!", *m_input_data.data()); m_input_data.erase(m_input_data.begin()); } } } void TagDispatcher::push_packet(const Packet &packet) { auto& buf = packet.buf; if (buf.size() < 2) { throw std::invalid_argument("Not enough bytes to read EDI packet header"); } if (buf[0] == 'A' and buf[1] == 'F') { const auto r = decode_afpacket(buf); m_last_sequences.pseq_valid = false; if (r.st == decode_state_e::Ok) { m_af_packet_completed(); } } else if (buf[0] == 'P' and buf[1] == 'F') { PFT::Fragment fragment; fragment.loadData(buf, packet.received_on_port); if (fragment.isValid()) { m_pft.pushPFTFrag(fragment); } auto af = m_pft.getNextAFPacket(); if (not af.af_packet.empty()) { const auto r = decode_afpacket(af.af_packet); if (r.st == decode_state_e::Ok) { m_last_sequences.pseq = af.pseq; m_last_sequences.pseq_valid = true; m_af_packet_completed(); } } } else { std::stringstream ss; ss << "Unknown EDI packet " << std::hex << (int)buf[0] << " " << (int)buf[1]; m_ignored_tags.clear(); throw invalid_argument(ss.str()); } } void TagDispatcher::setMaxDelay(int num_af_packets) { m_pft.setMaxDelay(num_af_packets); } TagDispatcher::decode_result_t TagDispatcher::decode_afpacket( const std::vector &input_data) { if (input_data.size() < AFPACKET_HEADER_LEN) { return {decode_state_e::MissingData, 0}; } // read length from packet uint32_t taglength = read_32b(input_data.begin() + 2); uint16_t seq = read_16b(input_data.begin() + 6); const size_t crclength = 2; if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) { return {decode_state_e::MissingData, 0}; } // SEQ wraps at 0xFFFF, unsigned integer overflow is intentional if (m_last_sequences.seq_valid) { const uint16_t expected_seq = m_last_sequences.seq + 1; if (expected_seq != seq) { etiLog.level(warn) << "EDI AF Packet sequence error, " << seq; m_ignored_tags.clear(); } } else { etiLog.level(info) << "EDI AF Packet initial sequence number: " << seq; m_last_sequences.seq_valid = true; } m_last_sequences.seq = seq; const size_t crclen = 2; bool has_crc = (input_data[8] & 0x80) ? true : false; uint8_t major_revision = (input_data[8] & 0x70) >> 4; uint8_t minor_revision = input_data[8] & 0x0F; if (major_revision != 1 or minor_revision != 0) { etiLog.level(warn) << "EDI AF Packet has wrong revision " << (int)major_revision << "." << (int)minor_revision; } if (not has_crc) { etiLog.level(warn) << "AF packet not supported, has no CRC"; return {decode_state_e::Error, AFPACKET_HEADER_LEN + taglength}; } uint8_t pt = input_data[9]; if (pt != 'T') { // only support Tag return {decode_state_e::Error, AFPACKET_HEADER_LEN + taglength + crclen}; } uint16_t crc = 0xffff; for (size_t i = 0; i < AFPACKET_HEADER_LEN + taglength; i++) { crc = crc16(crc, &input_data[i], 1); } crc ^= 0xffff; uint16_t packet_crc = read_16b(input_data.begin() + AFPACKET_HEADER_LEN + taglength); if (packet_crc != crc) { etiLog.level(warn) << "AF Packet crc wrong"; return {decode_state_e::Error, AFPACKET_HEADER_LEN + taglength + crclen}; } else { vector afpacket(AFPACKET_HEADER_LEN + taglength + crclen); copy(input_data.begin(), input_data.begin() + AFPACKET_HEADER_LEN + taglength + crclen, afpacket.begin()); m_afpacket_handler(std::move(afpacket)); vector payload(taglength); copy(input_data.begin() + AFPACKET_HEADER_LEN, input_data.begin() + AFPACKET_HEADER_LEN + taglength, payload.begin()); auto result = decode_tagpacket(payload) ? decode_state_e::Ok : decode_state_e::Error; return {result, AFPACKET_HEADER_LEN + taglength + crclen}; } } void TagDispatcher::register_tag(const std::string& tag, tag_handler&& h) { m_handlers[tag] = std::move(h); } void TagDispatcher::register_afpacket_handler(afpacket_handler&& h) { m_afpacket_handler = std::move(h); } bool TagDispatcher::decode_tagpacket(const vector &payload) { size_t length = 0; bool success = true; for (size_t i = 0; i + 8 < payload.size(); i += 8 + length) { char tag_sz[5]; tag_sz[4] = '\0'; copy(payload.begin() + i, payload.begin() + i + 4, tag_sz); string tag(tag_sz); uint32_t taglength = read_32b(payload.begin() + i + 4); if (taglength % 8 != 0) { etiLog.log(warn, "Invalid EDI tag length, not multiple of 8!"); break; } taglength /= 8; length = taglength; const size_t calculated_length = i + 8 + taglength; if (calculated_length > payload.size()) { etiLog.log(warn, "Invalid EDI tag length: tag larger %zu than tagpacket %zu!", calculated_length, payload.size()); break; } const array tag_name({ (uint8_t)tag_sz[0], (uint8_t)tag_sz[1], (uint8_t)tag_sz[2], (uint8_t)tag_sz[3] }); vector tag_value(taglength); copy( payload.begin() + i+8, payload.begin() + i+8+taglength, tag_value.begin()); bool tagsuccess = true; bool found = false; for (auto tag_handler : m_handlers) { if ( (tag_handler.first.size() == 4 and tag == tag_handler.first) or (tag_handler.first.size() == 3 and tag.substr(0, 3) == tag_handler.first) or (tag_handler.first.size() == 2 and tag.substr(0, 2) == tag_handler.first) or (tag_handler.first.size() == 1 and tag.substr(0, 1) == tag_handler.first)) { found = true; tagsuccess &= tag_handler.second(tag_value, tag_name); } } if (not found) { if (std::find(m_ignored_tags.begin(), m_ignored_tags.end(), tag) == m_ignored_tags.end()) { etiLog.log(warn, "Ignoring unknown TAG %s", tag.c_str()); m_ignored_tags.push_back(tag); } break; } if (not tagsuccess) { etiLog.log(warn, "Error decoding TAG %s", tag.c_str()); success = tagsuccess; break; } } return success; } odr_version_data parse_odr_version_data(const std::vector& data) { if (data.size() < sizeof(uint32_t)) { return {}; } const size_t versionstr_length = data.size() - sizeof(uint32_t); string version(data.begin(), data.begin() + versionstr_length); uint32_t uptime_s = read_32b(data.begin() + versionstr_length); return {version, uptime_s}; } } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/common.hpp000066400000000000000000000133631475762153200227370ustar00rootroot00000000000000/* Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include "PFT.hpp" #include #include #include #include #include #include #include #include namespace EdiDecoder { constexpr size_t AFPACKET_HEADER_LEN = 10; // includes SYNC struct frame_timestamp_t { uint32_t seconds = 0; uint32_t utco = 0; uint32_t tsta = 0xFFFFFF; // According to EN 300 797 Annex B bool is_valid() const; std::string to_string() const; std::time_t to_unix_epoch() const; std::chrono::system_clock::time_point to_system_clock() const; double diff_s(const frame_timestamp_t& other) const; frame_timestamp_t& operator+=(const std::chrono::milliseconds& ms); static frame_timestamp_t from_unix_epoch(std::time_t time, uint32_t tai_utc_offset, uint32_t tsta); friend bool operator==(const frame_timestamp_t& l, const frame_timestamp_t& r) { return (l.seconds - l.utco) == (r.seconds - r.utco) and l.tsta == r.tsta; } friend bool operator!=(const frame_timestamp_t& l, const frame_timestamp_t& r) { return not (l == r); } friend bool operator< (const frame_timestamp_t& l, const frame_timestamp_t& r) { if (l.seconds - l.utco == r.seconds - r.utco) { return l.tsta < r.tsta; } return l.seconds - l.utco < r.seconds - r.utco; } friend bool operator<= (const frame_timestamp_t& l, const frame_timestamp_t& r) { return l < r or l == r; } friend bool operator> (const frame_timestamp_t& l, const frame_timestamp_t& r) { return r < l; } friend bool operator>= (const frame_timestamp_t& l, const frame_timestamp_t& r) { return l > r or l == r; } }; using tag_name_t = std::array; std::string tag_name_to_human_readable(const tag_name_t& name); struct Packet { std::vector buf; int received_on_port; Packet(std::vector&& b) : buf(b), received_on_port(0) { } Packet() {} }; struct seq_info_t { bool seq_valid = false; uint16_t seq = 0; bool pseq_valid = false; uint16_t pseq = 0; }; /* The TagDispatcher takes care of decoding EDI, with or without PFT, and * will call functions when TAGs are encountered. * * PF packets are handed over to the PFT decoder, which will in turn return * AF packets. AF packets are directly dispatched to the TAG functions. */ class TagDispatcher { public: TagDispatcher(std::function&& af_packet_completed); void set_verbose(bool verbose); /* Push bytes into the decoder. The buf can contain more * than a single packet. This is useful when reading from streams * (files, TCP). Pushing an empty buf will clear the internal decoder * state to ensure realignment (e.g. on stream reconnection) */ void push_bytes(const std::vector &buf); /* Push a complete packet into the decoder. Useful for UDP and other * datagram-oriented protocols. */ void push_packet(const Packet &packet); /* Set the maximum delay in number of AF Packets before we * abandon decoding a given pseq. */ void setMaxDelay(int num_af_packets); /* Handler function for a tag. The first argument contains the tag value, * the second argument contains the tag name */ using tag_handler = std::function&, const tag_name_t&)>; /* Register a handler for a tag. If the tag string can be length 0, 1, 2, 3 or 4. * If is shorter than 4, it will perform a longest match on the tag name. */ void register_tag(const std::string& tag, tag_handler&& h); /* The complete AF packet can also be retrieved */ using afpacket_handler = std::function&&)>; void register_afpacket_handler(afpacket_handler&& h); seq_info_t get_seq_info() const { return m_last_sequences; } private: enum class decode_state_e { Ok, MissingData, Error }; struct decode_result_t { decode_result_t(decode_state_e _st, size_t _num_bytes_consumed) : st(_st), num_bytes_consumed(_num_bytes_consumed) {} decode_state_e st; size_t num_bytes_consumed; }; decode_result_t decode_afpacket(const std::vector &input_data); bool decode_tagpacket(const std::vector &payload); PFT::PFT m_pft; seq_info_t m_last_sequences; std::vector m_input_data; std::map m_handlers; std::function m_af_packet_completed; afpacket_handler m_afpacket_handler; std::vector m_ignored_tags; }; // Data carried inside the ODRv EDI TAG struct odr_version_data { std::string version; uint32_t uptime_s; }; odr_version_data parse_odr_version_data(const std::vector& data); } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/eti.cpp000066400000000000000000000032441475762153200222200ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "eti.hpp" namespace EdiDecoder { //definitions des structures des champs du ETI(NI, G703) uint16_t eti_FC::getFrameLength() { return (uint16_t)((FL_high << 8) | FL_low); } void eti_FC::setFrameLength(uint16_t length) { FL_high = (length >> 8) & 0x07; FL_low = length & 0xff; } void eti_STC::setSTL(uint16_t length) { STL_high = length >> 8; STL_low = length & 0xff; } uint16_t eti_STC::getSTL() { return (uint16_t)((STL_high << 8) + STL_low); } void eti_STC::setStartAddress(uint16_t address) { startAddress_high = address >> 8; startAddress_low = address & 0xff; } uint16_t eti_STC::getStartAddress() { return (uint16_t)((startAddress_high << 8) + startAddress_low); } } Opendigitalradio-ODR-DabMod-f7eedef/lib/edi/eti.hpp000066400000000000000000000050021475762153200222170ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #pragma once #include #define PACKED __attribute__ ((packed)) #include namespace EdiDecoder { struct eti_SYNC { uint32_t ERR:8; uint32_t FSYNC:24; } PACKED; struct eti_FC { uint32_t FCT:8; uint32_t NST:7; uint32_t FICF:1; uint32_t FL_high:3; uint32_t MID:2; uint32_t FP:3; uint32_t FL_low:8; uint16_t getFrameLength(); void setFrameLength(uint16_t length); } PACKED; struct eti_STC { uint32_t startAddress_high:2; uint32_t SCID:6; uint32_t startAddress_low:8; uint32_t STL_high:2; uint32_t TPL:6; uint32_t STL_low:8; void setSTL(uint16_t length); uint16_t getSTL(); void setStartAddress(uint16_t address); uint16_t getStartAddress(); } PACKED; struct eti_EOH { uint16_t MNSC; uint16_t CRC; } PACKED; struct eti_EOF { uint16_t CRC; uint16_t RFU; } PACKED; struct eti_TIST { uint32_t TIST; } PACKED; struct eti_MNSC_TIME_0 { uint32_t type:4; uint32_t identifier:4; uint32_t rfa:8; } PACKED; struct eti_MNSC_TIME_1 { uint32_t second_unit:4; uint32_t second_tens:3; uint32_t accuracy:1; uint32_t minute_unit:4; uint32_t minute_tens:3; uint32_t sync_to_frame:1; } PACKED; struct eti_MNSC_TIME_2 { uint32_t hour_unit:4; uint32_t hour_tens:4; uint32_t day_unit:4; uint32_t day_tens:4; } PACKED; struct eti_MNSC_TIME_3 { uint32_t month_unit:4; uint32_t month_tens:4; uint32_t year_unit:4; uint32_t year_tens:4; } PACKED; struct eti_extension_TIME { uint32_t TIME_SECONDS; } PACKED; } Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/000077500000000000000000000000001475762153200207245ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/LICENSE000066400000000000000000000635061475762153200217430ustar00rootroot00000000000000GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. (This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.) Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. {signature of Ty Coon}, 1 April 1990 Ty Coon, President of Vice That's all there is to it! Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/README.md000066400000000000000000000006331475762153200222050ustar00rootroot00000000000000FEC routines from KA9Q's libfec =============================== This folder contains part of the libfec library by KA9Q. Only the char-sized Reed-Solomon encoder and decoder is here. The files have been copied from the libfec fork at https://github.com/Opendigitalradio/ka9q-fec Original code is at http://www.ka9q.net/code/fec/ All files in this folder are licenced under the LGPL v2.1, please see LICENCE Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/char.h000066400000000000000000000010321475762153200220060ustar00rootroot00000000000000/* Stuff specific to the 8-bit symbol version of the general purpose RS codecs * * Copyright 2003, Phil Karn, KA9Q * May be used under the terms of the GNU Lesser General Public License (LGPL) */ typedef unsigned char data_t; #define MODNN(x) modnn(rs,x) #define MM (rs->mm) #define NN (rs->nn) #define ALPHA_TO (rs->alpha_to) #define INDEX_OF (rs->index_of) #define GENPOLY (rs->genpoly) #define NROOTS (rs->nroots) #define FCR (rs->fcr) #define PRIM (rs->prim) #define IPRIM (rs->iprim) #define PAD (rs->pad) #define A0 (NN) Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/decode_rs.h000066400000000000000000000203751475762153200230330ustar00rootroot00000000000000/* The guts of the Reed-Solomon decoder, meant to be #included * into a function body with the following typedefs, macros and variables supplied * according to the code parameters: * data_t - a typedef for the data symbol * data_t data[] - array of NN data and parity symbols to be corrected in place * retval - an integer lvalue into which the decoder's return code is written * NROOTS - the number of roots in the RS code generator polynomial, * which is the same as the number of parity symbols in a block. Integer variable or literal. * NN - the total number of symbols in a RS block. Integer variable or literal. * PAD - the number of pad symbols in a block. Integer variable or literal. * ALPHA_TO - The address of an array of NN elements to convert Galois field * elements in index (log) form to polynomial form. Read only. * INDEX_OF - The address of an array of NN elements to convert Galois field * elements in polynomial form to index (log) form. Read only. * MODNN - a function to reduce its argument modulo NN. May be inline or a macro. * FCR - An integer literal or variable specifying the first consecutive root of the * Reed-Solomon generator polynomial. Integer variable or literal. * PRIM - The primitive root of the generator poly. Integer variable or literal. * DEBUG - If set to 1 or more, do various internal consistency checking. Leave this * undefined for production code * The memset(), memmove(), and memcpy() functions are used. The appropriate header * file declaring these functions (usually ) must be included by the calling * program. */ #if !defined(NROOTS) #error "NROOTS not defined" #endif #if !defined(NN) #error "NN not defined" #endif #if !defined(PAD) #error "PAD not defined" #endif #if !defined(ALPHA_TO) #error "ALPHA_TO not defined" #endif #if !defined(INDEX_OF) #error "INDEX_OF not defined" #endif #if !defined(MODNN) #error "MODNN not defined" #endif #if !defined(FCR) #error "FCR not defined" #endif #if !defined(PRIM) #error "PRIM not defined" #endif #if !defined(NULL) #define NULL ((void *)0) #endif #undef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #undef A0 #define A0 (NN) { int deg_lambda, el, deg_omega; int i, j, r,k; data_t u,q,tmp,num1,num2,den,discr_r; data_t lambda[NROOTS+1], s[NROOTS]; /* Err+Eras Locator poly * and syndrome poly */ data_t b[NROOTS+1], t[NROOTS+1], omega[NROOTS+1]; data_t root[NROOTS], reg[NROOTS+1], loc[NROOTS]; int syn_error, count; /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */ for(i=0;i 0) { /* Init lambda to be the erasure locator polynomial */ lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))]; for (i = 1; i < no_eras; i++) { u = MODNN(PRIM*(NN-1-eras_pos[i])); for (j = i+1; j > 0; j--) { tmp = INDEX_OF[lambda[j - 1]]; if(tmp != A0) lambda[j] ^= ALPHA_TO[MODNN(u + tmp)]; } } #if DEBUG >= 1 /* Test code that verifies the erasure locator polynomial just constructed Needed only for decoder debugging. */ /* find roots of the erasure location polynomial */ for(i=1;i<=no_eras;i++) reg[i] = INDEX_OF[lambda[i]]; count = 0; for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) { q = 1; for (j = 1; j <= no_eras; j++) if (reg[j] != A0) { reg[j] = MODNN(reg[j] + j); q ^= ALPHA_TO[reg[j]]; } if (q != 0) continue; /* store root and error location number indices */ root[count] = i; loc[count] = k; count++; } if (count != no_eras) { printf("count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras); count = -1; goto finish; } #if DEBUG >= 2 printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n"); for (i = 0; i < count; i++) printf("%d ", loc[i]); printf("\n"); #endif #endif } for(i=0;i 0; j--){ if (reg[j] != A0) { reg[j] = MODNN(reg[j] + j); q ^= ALPHA_TO[reg[j]]; } } if (q != 0) continue; /* Not a root */ /* store root (index-form) and error location number */ #if DEBUG>=2 printf("count %d root %d loc %d\n",count,i,k); #endif root[count] = i; loc[count] = k; /* If we've already found max possible roots, * abort the search to save time */ if(++count == deg_lambda) break; } if (deg_lambda != count) { /* * deg(lambda) unequal to number of roots => uncorrectable * error detected */ count = -1; goto finish; } /* * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo * x**NROOTS). in index form. Also find deg(omega). */ deg_omega = deg_lambda-1; for (i = 0; i <= deg_omega;i++){ tmp = 0; for(j=i;j >= 0; j--){ if ((s[i - j] != A0) && (lambda[j] != A0)) tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])]; } omega[i] = INDEX_OF[tmp]; } /* * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form */ for (j = count-1; j >=0; j--) { num1 = 0; for (i = deg_omega; i >= 0; i--) { if (omega[i] != A0) num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])]; } num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)]; den = 0; /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ for (i = MIN(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { if(lambda[i+1] != A0) den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])]; } #if DEBUG >= 1 if (den == 0) { printf("\n ERROR: denominator = 0\n"); count = -1; goto finish; } #endif /* Apply error to data */ if (num1 != 0 && loc[j] >= PAD) { data[loc[j]-PAD] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])]; } } finish: if(eras_pos != NULL){ for(i=0;i #endif #include #include "char.h" #include "rs-common.h" int decode_rs_char(void *p, data_t *data, int *eras_pos, int no_eras){ int retval; struct rs *rs = (struct rs *)p; #include "decode_rs.h" return retval; } Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/encode_rs.h000066400000000000000000000044511475762153200230420ustar00rootroot00000000000000/* The guts of the Reed-Solomon encoder, meant to be #included * into a function body with the following typedefs, macros and variables supplied * according to the code parameters: * data_t - a typedef for the data symbol * data_t data[] - array of NN-NROOTS-PAD and type data_t to be encoded * data_t parity[] - an array of NROOTS and type data_t to be written with parity symbols * NROOTS - the number of roots in the RS code generator polynomial, * which is the same as the number of parity symbols in a block. Integer variable or literal. * * NN - the total number of symbols in a RS block. Integer variable or literal. * PAD - the number of pad symbols in a block. Integer variable or literal. * ALPHA_TO - The address of an array of NN elements to convert Galois field * elements in index (log) form to polynomial form. Read only. * INDEX_OF - The address of an array of NN elements to convert Galois field * elements in polynomial form to index (log) form. Read only. * MODNN - a function to reduce its argument modulo NN. May be inline or a macro. * GENPOLY - an array of NROOTS+1 elements containing the generator polynomial in index form * The memset() and memmove() functions are used. The appropriate header * file declaring these functions (usually ) must be included by the calling * program. * Copyright 2004, Phil Karn, KA9Q * May be used under the terms of the GNU Lesser General Public License (LGPL) */ #undef A0 #define A0 (NN) /* Special reserved value encoding zero in index form */ { int i, j; data_t feedback; memset(parity,0,NROOTS*sizeof(data_t)); for(i=0;i #include "char.h" #include "rs-common.h" void encode_rs_char(void *p,data_t *data, data_t *parity){ struct rs *rs = (struct rs *)p; #include "encode_rs.h" } Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/fec.h000066400000000000000000000015451475762153200216370ustar00rootroot00000000000000/* Main header for reduced libfec. * * The FEC code in this folder is * Copyright 2003 Phil Karn, KA9Q * May be used under the terms of the GNU Lesser General Public License (LGPL) */ #pragma once #include #include "char.h" #include "rs-common.h" /* Initialize a Reed-Solomon codec * symsize = symbol size, bits * gfpoly = Field generator polynomial coefficients * fcr = first root of RS code generator polynomial, index form * prim = primitive element to generate polynomial roots * nroots = RS code generator polynomial degree (number of roots) * pad = padding bytes at front of shortened block */ void *init_rs_char(int symsize,int gfpoly,int fcr,int prim,int nroots,int pad); int decode_rs_char(void *p, data_t *data, int *eras_pos, int no_eras); void encode_rs_char(void *p,data_t *data, data_t *parity); void free_rs_char(void *p); Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/init_rs.h000066400000000000000000000052131475762153200225450ustar00rootroot00000000000000/* Common code for intializing a Reed-Solomon control block (char or int symbols) * Copyright 2004 Phil Karn, KA9Q * May be used under the terms of the GNU Lesser General Public License (LGPL) */ #undef NULL #define NULL ((void *)0) { int i, j, sr,root,iprim; rs = NULL; /* Check parameter ranges */ if(symsize < 0 || symsize > 8*sizeof(data_t)){ goto done; } if(fcr < 0 || fcr >= (1<= (1<= (1<= ((1<mm = symsize; rs->nn = (1<pad = pad; rs->alpha_to = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); if(rs->alpha_to == NULL){ free(rs); rs = NULL; goto done; } rs->index_of = (data_t *)malloc(sizeof(data_t)*(rs->nn+1)); if(rs->index_of == NULL){ free(rs->alpha_to); free(rs); rs = NULL; goto done; } /* Generate Galois field lookup tables */ rs->index_of[0] = A0; /* log(zero) = -inf */ rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ sr = 1; for(i=0;inn;i++){ rs->index_of[sr] = i; rs->alpha_to[i] = sr; sr <<= 1; if(sr & (1<nn; } if(sr != 1){ /* field generator polynomial is not primitive! */ free(rs->alpha_to); free(rs->index_of); free(rs); rs = NULL; goto done; } /* Form RS code generator polynomial from its roots */ rs->genpoly = (data_t *)malloc(sizeof(data_t)*(nroots+1)); if(rs->genpoly == NULL){ free(rs->alpha_to); free(rs->index_of); free(rs); rs = NULL; goto done; } rs->fcr = fcr; rs->prim = prim; rs->nroots = nroots; /* Find prim-th root of 1, used in decoding */ for(iprim=1;(iprim % prim) != 0;iprim += rs->nn) ; rs->iprim = iprim / prim; rs->genpoly[0] = 1; for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) { rs->genpoly[i+1] = 1; /* Multiply rs->genpoly[] by @**(root + x) */ for (j = i; j > 0; j--){ if (rs->genpoly[j] != 0) rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)]; else rs->genpoly[j] = rs->genpoly[j-1]; } /* rs->genpoly[0] can never be zero */ rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)]; } /* convert rs->genpoly[] to index form for quicker encoding */ for (i = 0; i <= nroots; i++) rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; done:; } Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/init_rs_char.c000066400000000000000000000015201475762153200235320ustar00rootroot00000000000000/* Initialize a RS codec * * Copyright 2002 Phil Karn, KA9Q * May be used under the terms of the GNU Lesser General Public License (LGPL) */ #include #include "char.h" #include "rs-common.h" void free_rs_char(void *p){ struct rs *rs = (struct rs *)p; free(rs->alpha_to); free(rs->index_of); free(rs->genpoly); free(rs); } /* Initialize a Reed-Solomon codec * symsize = symbol size, bits * gfpoly = Field generator polynomial coefficients * fcr = first root of RS code generator polynomial, index form * prim = primitive element to generate polynomial roots * nroots = RS code generator polynomial degree (number of roots) * pad = padding bytes at front of shortened block */ void *init_rs_char(int symsize,int gfpoly,int fcr,int prim, int nroots,int pad){ struct rs *rs; #include "init_rs.h" return rs; } Opendigitalradio-ODR-DabMod-f7eedef/lib/fec/rs-common.h000066400000000000000000000016471475762153200230170ustar00rootroot00000000000000/* Stuff common to all the general-purpose Reed-Solomon codecs * Copyright 2004 Phil Karn, KA9Q * May be used under the terms of the GNU Lesser General Public License (LGPL) */ /* Reed-Solomon codec control block */ struct rs { int mm; /* Bits per symbol */ int nn; /* Symbols per block (= (1<= rs->nn) { x -= rs->nn; x = (x >> rs->mm) + (x & rs->nn); } return x; } Opendigitalradio-ODR-DabMod-f7eedef/lib/zmq.hpp000066400000000000000000001617151475762153200215220ustar00rootroot00000000000000/* Copyright (c) 2016-2017 ZeroMQ community Copyright (c) 2009-2011 250bpm s.r.o. Copyright (c) 2011 Botond Ballo Copyright (c) 2007-2009 iMatix Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ZMQ_HPP_INCLUDED__ #define __ZMQ_HPP_INCLUDED__ // macros defined if has a specific standard or greater #if (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(_MSC_VER) && _MSC_VER >= 1900) #define ZMQ_CPP11 #endif #if (defined(__cplusplus) && __cplusplus >= 201402L) || \ (defined(_HAS_CXX14) && _HAS_CXX14 == 1) || \ (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // _HAS_CXX14 might not be defined when using C++17 on MSVC #define ZMQ_CPP14 #endif #if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) #define ZMQ_CPP17 #endif #if defined(ZMQ_CPP14) #define ZMQ_DEPRECATED(msg) [[deprecated(msg)]] #elif defined(_MSC_VER) #define ZMQ_DEPRECATED(msg) __declspec(deprecated(msg)) #elif defined(__GNUC__) #define ZMQ_DEPRECATED(msg) __attribute__((deprecated(msg))) #endif #if defined(ZMQ_CPP17) #define ZMQ_NODISCARD [[nodiscard]] #else #define ZMQ_NODISCARD #endif #if defined(ZMQ_CPP11) #define ZMQ_NOTHROW noexcept #define ZMQ_EXPLICIT explicit #define ZMQ_OVERRIDE override #define ZMQ_NULLPTR nullptr #define ZMQ_CONSTEXPR_FN constexpr #define ZMQ_CONSTEXPR_VAR constexpr #else #define ZMQ_NOTHROW throw() #define ZMQ_EXPLICIT #define ZMQ_OVERRIDE #define ZMQ_NULLPTR 0 #define ZMQ_CONSTEXPR_FN #define ZMQ_CONSTEXPR_VAR const #endif #include #include #include #include #include #include #include #include #include #ifdef ZMQ_CPP11 #include #include #include #include #endif #ifdef ZMQ_CPP17 #ifdef __has_include #if __has_include() #include #define ZMQ_HAS_OPTIONAL 1 #endif #if __has_include() #include #define ZMQ_HAS_STRING_VIEW 1 #endif #endif #endif /* Version macros for compile-time API version detection */ #define CPPZMQ_VERSION_MAJOR 4 #define CPPZMQ_VERSION_MINOR 6 #define CPPZMQ_VERSION_PATCH 0 #define CPPZMQ_VERSION \ ZMQ_MAKE_VERSION(CPPZMQ_VERSION_MAJOR, CPPZMQ_VERSION_MINOR, \ CPPZMQ_VERSION_PATCH) // Detect whether the compiler supports C++11 rvalue references. #if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 2)) \ && defined(__GXX_EXPERIMENTAL_CXX0X__)) #define ZMQ_HAS_RVALUE_REFS #define ZMQ_DELETED_FUNCTION = delete #elif defined(__clang__) #if __has_feature(cxx_rvalue_references) #define ZMQ_HAS_RVALUE_REFS #endif #if __has_feature(cxx_deleted_functions) #define ZMQ_DELETED_FUNCTION = delete #else #define ZMQ_DELETED_FUNCTION #endif #elif defined(_MSC_VER) && (_MSC_VER >= 1900) #define ZMQ_HAS_RVALUE_REFS #define ZMQ_DELETED_FUNCTION = delete #elif defined(_MSC_VER) && (_MSC_VER >= 1600) #define ZMQ_HAS_RVALUE_REFS #define ZMQ_DELETED_FUNCTION #else #define ZMQ_DELETED_FUNCTION #endif #if defined(ZMQ_CPP11) && !defined(__llvm__) && !defined(__INTEL_COMPILER) \ && defined(__GNUC__) && __GNUC__ < 5 #define ZMQ_CPP11_PARTIAL #elif defined(__GLIBCXX__) && __GLIBCXX__ < 20160805 //the date here is the last date of gcc 4.9.4, which // effectively means libstdc++ from gcc 5.5 and higher won't trigger this branch #define ZMQ_CPP11_PARTIAL #endif #ifdef ZMQ_CPP11 #ifdef ZMQ_CPP11_PARTIAL #define ZMQ_IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) #else #include #define ZMQ_IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value #endif #endif #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 3, 0) #define ZMQ_NEW_MONITOR_EVENT_LAYOUT #endif #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) #define ZMQ_HAS_PROXY_STEERABLE /* Socket event data */ typedef struct { uint16_t event; // id of the event as bitfield int32_t value; // value is either error code, fd or reconnect interval } zmq_event_t; #endif // Avoid using deprecated message receive function when possible #if ZMQ_VERSION < ZMQ_MAKE_VERSION(3, 2, 0) #define zmq_msg_recv(msg, socket, flags) zmq_recvmsg(socket, msg, flags) #endif // In order to prevent unused variable warnings when building in non-debug // mode use this macro to make assertions. #ifndef NDEBUG #define ZMQ_ASSERT(expression) assert(expression) #else #define ZMQ_ASSERT(expression) (void) (expression) #endif namespace zmq { #ifdef ZMQ_CPP11 namespace detail { namespace ranges { using std::begin; using std::end; template auto begin(T&& r) -> decltype(begin(std::forward(r))) { return begin(std::forward(r)); } template auto end(T&& r) -> decltype(end(std::forward(r))) { return end(std::forward(r)); } } // namespace ranges template using void_t = void; template using iter_value_t = typename std::iterator_traits::value_type; template using range_iter_t = decltype( ranges::begin(std::declval::type &>())); template using range_value_t = iter_value_t>; template struct is_range : std::false_type { }; template struct is_range< T, void_t::type &>()) == ranges::end(std::declval::type &>()))>> : std::true_type { }; } // namespace detail #endif typedef zmq_free_fn free_fn; typedef zmq_pollitem_t pollitem_t; class error_t : public std::exception { public: error_t() : errnum(zmq_errno()) {} virtual const char *what() const ZMQ_NOTHROW ZMQ_OVERRIDE { return zmq_strerror(errnum); } int num() const { return errnum; } private: int errnum; }; inline int poll(zmq_pollitem_t *items_, size_t nitems_, long timeout_ = -1) { int rc = zmq_poll(items_, static_cast(nitems_), timeout_); if (rc < 0) throw error_t(); return rc; } ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") inline int poll(zmq_pollitem_t const *items_, size_t nitems_, long timeout_ = -1) { return poll(const_cast(items_), nitems_, timeout_); } #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") inline int poll(zmq_pollitem_t const *items, size_t nitems, std::chrono::milliseconds timeout) { return poll(const_cast(items), nitems, static_cast(timeout.count())); } ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") inline int poll(std::vector const &items, std::chrono::milliseconds timeout) { return poll(const_cast(items.data()), items.size(), static_cast(timeout.count())); } ZMQ_DEPRECATED("from 4.3.1, use poll taking non-const items") inline int poll(std::vector const &items, long timeout_ = -1) { return poll(const_cast(items.data()), items.size(), timeout_); } inline int poll(zmq_pollitem_t *items, size_t nitems, std::chrono::milliseconds timeout) { return poll(items, nitems, static_cast(timeout.count())); } inline int poll(std::vector &items, std::chrono::milliseconds timeout) { return poll(items.data(), items.size(), static_cast(timeout.count())); } inline int poll(std::vector &items, long timeout_ = -1) { return poll(items.data(), items.size(), timeout_); } #endif inline void version(int *major_, int *minor_, int *patch_) { zmq_version(major_, minor_, patch_); } #ifdef ZMQ_CPP11 inline std::tuple version() { std::tuple v; zmq_version(&std::get<0>(v), &std::get<1>(v), &std::get<2>(v)); return v; } #endif class message_t { public: message_t() ZMQ_NOTHROW { int rc = zmq_msg_init(&msg); ZMQ_ASSERT(rc == 0); } explicit message_t(size_t size_) { int rc = zmq_msg_init_size(&msg, size_); if (rc != 0) throw error_t(); } template message_t(ForwardIter first, ForwardIter last) { typedef typename std::iterator_traits::value_type value_t; assert(std::distance(first, last) >= 0); size_t const size_ = static_cast(std::distance(first, last)) * sizeof(value_t); int const rc = zmq_msg_init_size(&msg, size_); if (rc != 0) throw error_t(); std::copy(first, last, data()); } message_t(const void *data_, size_t size_) { int rc = zmq_msg_init_size(&msg, size_); if (rc != 0) throw error_t(); memcpy(data(), data_, size_); } message_t(void *data_, size_t size_, free_fn *ffn_, void *hint_ = ZMQ_NULLPTR) { int rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); if (rc != 0) throw error_t(); } #if defined(ZMQ_CPP11) && !defined(ZMQ_CPP11_PARTIAL) template::value && ZMQ_IS_TRIVIALLY_COPYABLE(detail::range_value_t) && !std::is_same::value>::type> explicit message_t(const Range &rng) : message_t(detail::ranges::begin(rng), detail::ranges::end(rng)) { } #endif #ifdef ZMQ_HAS_RVALUE_REFS message_t(message_t &&rhs) ZMQ_NOTHROW : msg(rhs.msg) { int rc = zmq_msg_init(&rhs.msg); ZMQ_ASSERT(rc == 0); } message_t &operator=(message_t &&rhs) ZMQ_NOTHROW { std::swap(msg, rhs.msg); return *this; } #endif ~message_t() ZMQ_NOTHROW { int rc = zmq_msg_close(&msg); ZMQ_ASSERT(rc == 0); } void rebuild() { int rc = zmq_msg_close(&msg); if (rc != 0) throw error_t(); rc = zmq_msg_init(&msg); ZMQ_ASSERT(rc == 0); } void rebuild(size_t size_) { int rc = zmq_msg_close(&msg); if (rc != 0) throw error_t(); rc = zmq_msg_init_size(&msg, size_); if (rc != 0) throw error_t(); } void rebuild(const void *data_, size_t size_) { int rc = zmq_msg_close(&msg); if (rc != 0) throw error_t(); rc = zmq_msg_init_size(&msg, size_); if (rc != 0) throw error_t(); memcpy(data(), data_, size_); } void rebuild(void *data_, size_t size_, free_fn *ffn_, void *hint_ = ZMQ_NULLPTR) { int rc = zmq_msg_close(&msg); if (rc != 0) throw error_t(); rc = zmq_msg_init_data(&msg, data_, size_, ffn_, hint_); if (rc != 0) throw error_t(); } ZMQ_DEPRECATED("from 4.3.1, use move taking non-const reference instead") void move(message_t const *msg_) { int rc = zmq_msg_move(&msg, const_cast(msg_->handle())); if (rc != 0) throw error_t(); } void move(message_t &msg_) { int rc = zmq_msg_move(&msg, msg_.handle()); if (rc != 0) throw error_t(); } ZMQ_DEPRECATED("from 4.3.1, use copy taking non-const reference instead") void copy(message_t const *msg_) { int rc = zmq_msg_copy(&msg, const_cast(msg_->handle())); if (rc != 0) throw error_t(); } void copy(message_t &msg_) { int rc = zmq_msg_copy(&msg, msg_.handle()); if (rc != 0) throw error_t(); } bool more() const ZMQ_NOTHROW { int rc = zmq_msg_more(const_cast(&msg)); return rc != 0; } void *data() ZMQ_NOTHROW { return zmq_msg_data(&msg); } const void *data() const ZMQ_NOTHROW { return zmq_msg_data(const_cast(&msg)); } size_t size() const ZMQ_NOTHROW { return zmq_msg_size(const_cast(&msg)); } ZMQ_NODISCARD bool empty() const ZMQ_NOTHROW { return size() == 0u; } template T *data() ZMQ_NOTHROW { return static_cast(data()); } template T const *data() const ZMQ_NOTHROW { return static_cast(data()); } ZMQ_DEPRECATED("from 4.3.0, use operator== instead") bool equal(const message_t *other) const ZMQ_NOTHROW { return *this == *other; } bool operator==(const message_t &other) const ZMQ_NOTHROW { const size_t my_size = size(); return my_size == other.size() && 0 == memcmp(data(), other.data(), my_size); } bool operator!=(const message_t &other) const ZMQ_NOTHROW { return !(*this == other); } #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(3, 2, 0) int get(int property_) { int value = zmq_msg_get(&msg, property_); if (value == -1) throw error_t(); return value; } #endif #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 1, 0) const char *gets(const char *property_) { const char *value = zmq_msg_gets(&msg, property_); if (value == ZMQ_NULLPTR) throw error_t(); return value; } #endif #if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) uint32_t routing_id() const { return zmq_msg_routing_id(const_cast(&msg)); } void set_routing_id(uint32_t routing_id) { int rc = zmq_msg_set_routing_id(&msg, routing_id); if (rc != 0) throw error_t(); } const char* group() const { return zmq_msg_group(const_cast(&msg)); } void set_group(const char* group) { int rc = zmq_msg_set_group(&msg, group); if (rc != 0) throw error_t(); } #endif // interpret message content as a string std::string to_string() const { return std::string(static_cast(data()), size()); } #ifdef ZMQ_CPP17 // interpret message content as a string std::string_view to_string_view() const noexcept { return std::string_view(static_cast(data()), size()); } #endif /** Dump content to string for debugging. * Ascii chars are readable, the rest is printed as hex. * Probably ridiculously slow. * Use to_string() or to_string_view() for * interpreting the message as a string. */ std::string str() const { // Partly mutuated from the same method in zmq::multipart_t std::stringstream os; const unsigned char *msg_data = this->data(); unsigned char byte; size_t size = this->size(); int is_ascii[2] = {0, 0}; os << "zmq::message_t [size " << std::dec << std::setw(3) << std::setfill('0') << size << "] ("; // Totally arbitrary if (size >= 1000) { os << "... too big to print)"; } else { while (size--) { byte = *msg_data++; is_ascii[1] = (byte >= 32 && byte < 127); if (is_ascii[1] != is_ascii[0]) os << " "; // Separate text/non text if (is_ascii[1]) { os << byte; } else { os << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << static_cast(byte); } is_ascii[0] = is_ascii[1]; } os << ")"; } return os.str(); } void swap(message_t &other) ZMQ_NOTHROW { // this assumes zmq::msg_t from libzmq is trivially relocatable std::swap(msg, other.msg); } ZMQ_NODISCARD zmq_msg_t *handle() ZMQ_NOTHROW { return &msg; } ZMQ_NODISCARD const zmq_msg_t *handle() const ZMQ_NOTHROW { return &msg; } private: // The underlying message zmq_msg_t msg; // Disable implicit message copying, so that users won't use shared // messages (less efficient) without being aware of the fact. message_t(const message_t &) ZMQ_DELETED_FUNCTION; void operator=(const message_t &) ZMQ_DELETED_FUNCTION; }; inline void swap(message_t &a, message_t &b) ZMQ_NOTHROW { a.swap(b); } class context_t { public: context_t() { ptr = zmq_ctx_new(); if (ptr == ZMQ_NULLPTR) throw error_t(); } explicit context_t(int io_threads_, int max_sockets_ = ZMQ_MAX_SOCKETS_DFLT) { ptr = zmq_ctx_new(); if (ptr == ZMQ_NULLPTR) throw error_t(); int rc = zmq_ctx_set(ptr, ZMQ_IO_THREADS, io_threads_); ZMQ_ASSERT(rc == 0); rc = zmq_ctx_set(ptr, ZMQ_MAX_SOCKETS, max_sockets_); ZMQ_ASSERT(rc == 0); } #ifdef ZMQ_HAS_RVALUE_REFS context_t(context_t &&rhs) ZMQ_NOTHROW : ptr(rhs.ptr) { rhs.ptr = ZMQ_NULLPTR; } context_t &operator=(context_t &&rhs) ZMQ_NOTHROW { close(); std::swap(ptr, rhs.ptr); return *this; } #endif int setctxopt(int option_, int optval_) { int rc = zmq_ctx_set(ptr, option_, optval_); ZMQ_ASSERT(rc == 0); return rc; } int getctxopt(int option_) { return zmq_ctx_get(ptr, option_); } ~context_t() ZMQ_NOTHROW { close(); } void close() ZMQ_NOTHROW { if (ptr == ZMQ_NULLPTR) return; int rc; do { rc = zmq_ctx_destroy(ptr); } while (rc == -1 && errno == EINTR); ZMQ_ASSERT(rc == 0); ptr = ZMQ_NULLPTR; } // Be careful with this, it's probably only useful for // using the C api together with an existing C++ api. // Normally you should never need to use this. ZMQ_EXPLICIT operator void *() ZMQ_NOTHROW { return ptr; } ZMQ_EXPLICIT operator void const *() const ZMQ_NOTHROW { return ptr; } operator bool() const ZMQ_NOTHROW { return ptr != ZMQ_NULLPTR; } void swap(context_t &other) ZMQ_NOTHROW { std::swap(ptr, other.ptr); } private: void *ptr; context_t(const context_t &) ZMQ_DELETED_FUNCTION; void operator=(const context_t &) ZMQ_DELETED_FUNCTION; }; inline void swap(context_t &a, context_t &b) ZMQ_NOTHROW { a.swap(b); } #ifdef ZMQ_CPP11 struct recv_buffer_size { size_t size; // number of bytes written to buffer size_t untruncated_size; // untruncated message size in bytes ZMQ_NODISCARD bool truncated() const noexcept { return size != untruncated_size; } }; #if defined(ZMQ_HAS_OPTIONAL) && (ZMQ_HAS_OPTIONAL > 0) using send_result_t = std::optional; using recv_result_t = std::optional; using recv_buffer_result_t = std::optional; #else namespace detail { // A C++11 type emulating the most basic // operations of std::optional for trivial types template class trivial_optional { public: static_assert(std::is_trivial::value, "T must be trivial"); using value_type = T; trivial_optional() = default; trivial_optional(T value) noexcept : _value(value), _has_value(true) {} const T *operator->() const noexcept { assert(_has_value); return &_value; } T *operator->() noexcept { assert(_has_value); return &_value; } const T &operator*() const noexcept { assert(_has_value); return _value; } T &operator*() noexcept { assert(_has_value); return _value; } T &value() { if (!_has_value) throw std::exception(); return _value; } const T &value() const { if (!_has_value) throw std::exception(); return _value; } explicit operator bool() const noexcept { return _has_value; } bool has_value() const noexcept { return _has_value; } private: T _value{}; bool _has_value{false}; }; } // namespace detail using send_result_t = detail::trivial_optional; using recv_result_t = detail::trivial_optional; using recv_buffer_result_t = detail::trivial_optional; #endif namespace detail { template constexpr T enum_bit_or(T a, T b) noexcept { static_assert(std::is_enum::value, "must be enum"); using U = typename std::underlying_type::type; return static_cast(static_cast(a) | static_cast(b)); } template constexpr T enum_bit_and(T a, T b) noexcept { static_assert(std::is_enum::value, "must be enum"); using U = typename std::underlying_type::type; return static_cast(static_cast(a) & static_cast(b)); } template constexpr T enum_bit_xor(T a, T b) noexcept { static_assert(std::is_enum::value, "must be enum"); using U = typename std::underlying_type::type; return static_cast(static_cast(a) ^ static_cast(b)); } template constexpr T enum_bit_not(T a) noexcept { static_assert(std::is_enum::value, "must be enum"); using U = typename std::underlying_type::type; return static_cast(~static_cast(a)); } } // namespace detail // partially satisfies named requirement BitmaskType enum class send_flags : int { none = 0, dontwait = ZMQ_DONTWAIT, sndmore = ZMQ_SNDMORE }; constexpr send_flags operator|(send_flags a, send_flags b) noexcept { return detail::enum_bit_or(a, b); } constexpr send_flags operator&(send_flags a, send_flags b) noexcept { return detail::enum_bit_and(a, b); } constexpr send_flags operator^(send_flags a, send_flags b) noexcept { return detail::enum_bit_xor(a, b); } constexpr send_flags operator~(send_flags a) noexcept { return detail::enum_bit_not(a); } // partially satisfies named requirement BitmaskType enum class recv_flags : int { none = 0, dontwait = ZMQ_DONTWAIT }; constexpr recv_flags operator|(recv_flags a, recv_flags b) noexcept { return detail::enum_bit_or(a, b); } constexpr recv_flags operator&(recv_flags a, recv_flags b) noexcept { return detail::enum_bit_and(a, b); } constexpr recv_flags operator^(recv_flags a, recv_flags b) noexcept { return detail::enum_bit_xor(a, b); } constexpr recv_flags operator~(recv_flags a) noexcept { return detail::enum_bit_not(a); } // mutable_buffer, const_buffer and buffer are based on // the Networking TS specification, draft: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf class mutable_buffer { public: constexpr mutable_buffer() noexcept : _data(nullptr), _size(0) {} constexpr mutable_buffer(void *p, size_t n) noexcept : _data(p), _size(n) { #ifdef ZMQ_CPP14 assert(p != nullptr || n == 0); #endif } constexpr void *data() const noexcept { return _data; } constexpr size_t size() const noexcept { return _size; } mutable_buffer &operator+=(size_t n) noexcept { // (std::min) is a workaround for when a min macro is defined const auto shift = (std::min)(n, _size); _data = static_cast(_data) + shift; _size -= shift; return *this; } private: void *_data; size_t _size; }; inline mutable_buffer operator+(const mutable_buffer &mb, size_t n) noexcept { return mutable_buffer(static_cast(mb.data()) + (std::min)(n, mb.size()), mb.size() - (std::min)(n, mb.size())); } inline mutable_buffer operator+(size_t n, const mutable_buffer &mb) noexcept { return mb + n; } class const_buffer { public: constexpr const_buffer() noexcept : _data(nullptr), _size(0) {} constexpr const_buffer(const void *p, size_t n) noexcept : _data(p), _size(n) { #ifdef ZMQ_CPP14 assert(p != nullptr || n == 0); #endif } constexpr const_buffer(const mutable_buffer &mb) noexcept : _data(mb.data()), _size(mb.size()) { } constexpr const void *data() const noexcept { return _data; } constexpr size_t size() const noexcept { return _size; } const_buffer &operator+=(size_t n) noexcept { const auto shift = (std::min)(n, _size); _data = static_cast(_data) + shift; _size -= shift; return *this; } private: const void *_data; size_t _size; }; inline const_buffer operator+(const const_buffer &cb, size_t n) noexcept { return const_buffer(static_cast(cb.data()) + (std::min)(n, cb.size()), cb.size() - (std::min)(n, cb.size())); } inline const_buffer operator+(size_t n, const const_buffer &cb) noexcept { return cb + n; } // buffer creation constexpr mutable_buffer buffer(void* p, size_t n) noexcept { return mutable_buffer(p, n); } constexpr const_buffer buffer(const void* p, size_t n) noexcept { return const_buffer(p, n); } constexpr mutable_buffer buffer(const mutable_buffer& mb) noexcept { return mb; } inline mutable_buffer buffer(const mutable_buffer& mb, size_t n) noexcept { return mutable_buffer(mb.data(), (std::min)(mb.size(), n)); } constexpr const_buffer buffer(const const_buffer& cb) noexcept { return cb; } inline const_buffer buffer(const const_buffer& cb, size_t n) noexcept { return const_buffer(cb.data(), (std::min)(cb.size(), n)); } namespace detail { template struct is_buffer { static constexpr bool value = std::is_same::value || std::is_same::value; }; template struct is_pod_like { // NOTE: The networking draft N4771 section 16.11 requires // T in the buffer functions below to be // trivially copyable OR standard layout. // Here we decide to be conservative and require both. static constexpr bool value = ZMQ_IS_TRIVIALLY_COPYABLE(T) && std::is_standard_layout::value; }; template constexpr auto seq_size(const C &c) noexcept -> decltype(c.size()) { return c.size(); } template constexpr size_t seq_size(const T (&/*array*/)[N]) noexcept { return N; } template auto buffer_contiguous_sequence(Seq &&seq) noexcept -> decltype(buffer(std::addressof(*std::begin(seq)), size_t{})) { using T = typename std::remove_cv< typename std::remove_reference::type>::type; static_assert(detail::is_pod_like::value, "T must be POD"); const auto size = seq_size(seq); return buffer(size != 0u ? std::addressof(*std::begin(seq)) : nullptr, size * sizeof(T)); } template auto buffer_contiguous_sequence(Seq &&seq, size_t n_bytes) noexcept -> decltype(buffer_contiguous_sequence(seq)) { using T = typename std::remove_cv< typename std::remove_reference::type>::type; static_assert(detail::is_pod_like::value, "T must be POD"); const auto size = seq_size(seq); return buffer(size != 0u ? std::addressof(*std::begin(seq)) : nullptr, (std::min)(size * sizeof(T), n_bytes)); } } // namespace detail // C array template mutable_buffer buffer(T (&data)[N]) noexcept { return detail::buffer_contiguous_sequence(data); } template mutable_buffer buffer(T (&data)[N], size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } template const_buffer buffer(const T (&data)[N]) noexcept { return detail::buffer_contiguous_sequence(data); } template const_buffer buffer(const T (&data)[N], size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } // std::array template mutable_buffer buffer(std::array &data) noexcept { return detail::buffer_contiguous_sequence(data); } template mutable_buffer buffer(std::array &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } template const_buffer buffer(std::array &data) noexcept { return detail::buffer_contiguous_sequence(data); } template const_buffer buffer(std::array &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } template const_buffer buffer(const std::array &data) noexcept { return detail::buffer_contiguous_sequence(data); } template const_buffer buffer(const std::array &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } // std::vector template mutable_buffer buffer(std::vector &data) noexcept { return detail::buffer_contiguous_sequence(data); } template mutable_buffer buffer(std::vector &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } template const_buffer buffer(const std::vector &data) noexcept { return detail::buffer_contiguous_sequence(data); } template const_buffer buffer(const std::vector &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } // std::basic_string template mutable_buffer buffer(std::basic_string &data) noexcept { return detail::buffer_contiguous_sequence(data); } template mutable_buffer buffer(std::basic_string &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } template const_buffer buffer(const std::basic_string &data) noexcept { return detail::buffer_contiguous_sequence(data); } template const_buffer buffer(const std::basic_string &data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } #if defined(ZMQ_HAS_STRING_VIEW) && (ZMQ_HAS_STRING_VIEW > 0) // std::basic_string_view template const_buffer buffer(std::basic_string_view data) noexcept { return detail::buffer_contiguous_sequence(data); } template const_buffer buffer(std::basic_string_view data, size_t n_bytes) noexcept { return detail::buffer_contiguous_sequence(data, n_bytes); } #endif // Buffer for a string literal (null terminated) // where the buffer size excludes the terminating character. // Equivalent to zmq::buffer(std::string_view("...")). template constexpr const_buffer str_buffer(const Char (&data)[N]) noexcept { static_assert(detail::is_pod_like::value, "Char must be POD"); #ifdef ZMQ_CPP14 assert(data[N - 1] == Char{0}); #endif return const_buffer(static_cast(data), (N - 1) * sizeof(Char)); } namespace literals { constexpr const_buffer operator"" _zbuf(const char* str, size_t len) noexcept { return const_buffer(str, len * sizeof(char)); } constexpr const_buffer operator"" _zbuf(const wchar_t* str, size_t len) noexcept { return const_buffer(str, len * sizeof(wchar_t)); } constexpr const_buffer operator"" _zbuf(const char16_t* str, size_t len) noexcept { return const_buffer(str, len * sizeof(char16_t)); } constexpr const_buffer operator"" _zbuf(const char32_t* str, size_t len) noexcept { return const_buffer(str, len * sizeof(char32_t)); } } #endif // ZMQ_CPP11 namespace detail { class socket_base { public: socket_base() ZMQ_NOTHROW : _handle(ZMQ_NULLPTR) {} ZMQ_EXPLICIT socket_base(void *handle) ZMQ_NOTHROW : _handle(handle) {} template void setsockopt(int option_, T const &optval) { setsockopt(option_, &optval, sizeof(T)); } void setsockopt(int option_, const void *optval_, size_t optvallen_) { int rc = zmq_setsockopt(_handle, option_, optval_, optvallen_); if (rc != 0) throw error_t(); } void getsockopt(int option_, void *optval_, size_t *optvallen_) const { int rc = zmq_getsockopt(_handle, option_, optval_, optvallen_); if (rc != 0) throw error_t(); } template T getsockopt(int option_) const { T optval; size_t optlen = sizeof(T); getsockopt(option_, &optval, &optlen); return optval; } void bind(std::string const &addr) { bind(addr.c_str()); } void bind(const char *addr_) { int rc = zmq_bind(_handle, addr_); if (rc != 0) throw error_t(); } void unbind(std::string const &addr) { unbind(addr.c_str()); } void unbind(const char *addr_) { int rc = zmq_unbind(_handle, addr_); if (rc != 0) throw error_t(); } void connect(std::string const &addr) { connect(addr.c_str()); } void connect(const char *addr_) { int rc = zmq_connect(_handle, addr_); if (rc != 0) throw error_t(); } void disconnect(std::string const &addr) { disconnect(addr.c_str()); } void disconnect(const char *addr_) { int rc = zmq_disconnect(_handle, addr_); if (rc != 0) throw error_t(); } bool connected() const ZMQ_NOTHROW { return (_handle != ZMQ_NULLPTR); } #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.3.1, use send taking a const_buffer and send_flags") #endif size_t send(const void *buf_, size_t len_, int flags_ = 0) { int nbytes = zmq_send(_handle, buf_, len_, flags_); if (nbytes >= 0) return static_cast(nbytes); if (zmq_errno() == EAGAIN) return 0; throw error_t(); } #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.3.1, use send taking message_t and send_flags") #endif bool send(message_t &msg_, int flags_ = 0) // default until removed { int nbytes = zmq_msg_send(msg_.handle(), _handle, flags_); if (nbytes >= 0) return true; if (zmq_errno() == EAGAIN) return false; throw error_t(); } template #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.4.1, use send taking message_t or buffer (for contiguous ranges), and send_flags") #endif bool send(T first, T last, int flags_ = 0) { zmq::message_t msg(first, last); int nbytes = zmq_msg_send(msg.handle(), _handle, flags_); if (nbytes >= 0) return true; if (zmq_errno() == EAGAIN) return false; throw error_t(); } #ifdef ZMQ_HAS_RVALUE_REFS #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.3.1, use send taking message_t and send_flags") #endif bool send(message_t &&msg_, int flags_ = 0) // default until removed { #ifdef ZMQ_CPP11 return send(msg_, static_cast(flags_)).has_value(); #else return send(msg_, flags_); #endif } #endif #ifdef ZMQ_CPP11 send_result_t send(const_buffer buf, send_flags flags = send_flags::none) { const int nbytes = zmq_send(_handle, buf.data(), buf.size(), static_cast(flags)); if (nbytes >= 0) return static_cast(nbytes); if (zmq_errno() == EAGAIN) return {}; throw error_t(); } send_result_t send(message_t &msg, send_flags flags) { int nbytes = zmq_msg_send(msg.handle(), _handle, static_cast(flags)); if (nbytes >= 0) return static_cast(nbytes); if (zmq_errno() == EAGAIN) return {}; throw error_t(); } send_result_t send(message_t &&msg, send_flags flags) { return send(msg, flags); } #endif #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.3.1, use recv taking a mutable_buffer and recv_flags") #endif size_t recv(void *buf_, size_t len_, int flags_ = 0) { int nbytes = zmq_recv(_handle, buf_, len_, flags_); if (nbytes >= 0) return static_cast(nbytes); if (zmq_errno() == EAGAIN) return 0; throw error_t(); } #ifdef ZMQ_CPP11 ZMQ_DEPRECATED("from 4.3.1, use recv taking a reference to message_t and recv_flags") #endif bool recv(message_t *msg_, int flags_ = 0) { int nbytes = zmq_msg_recv(msg_->handle(), _handle, flags_); if (nbytes >= 0) return true; if (zmq_errno() == EAGAIN) return false; throw error_t(); } #ifdef ZMQ_CPP11 ZMQ_NODISCARD recv_buffer_result_t recv(mutable_buffer buf, recv_flags flags = recv_flags::none) { const int nbytes = zmq_recv(_handle, buf.data(), buf.size(), static_cast(flags)); if (nbytes >= 0) { return recv_buffer_size{(std::min)(static_cast(nbytes), buf.size()), static_cast(nbytes)}; } if (zmq_errno() == EAGAIN) return {}; throw error_t(); } ZMQ_NODISCARD recv_result_t recv(message_t &msg, recv_flags flags = recv_flags::none) { const int nbytes = zmq_msg_recv(msg.handle(), _handle, static_cast(flags)); if (nbytes >= 0) { assert(msg.size() == static_cast(nbytes)); return static_cast(nbytes); } if (zmq_errno() == EAGAIN) return {}; throw error_t(); } #endif #if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) void join(const char* group) { int rc = zmq_join(_handle, group); if (rc != 0) throw error_t(); } void leave(const char* group) { int rc = zmq_leave(_handle, group); if (rc != 0) throw error_t(); } #endif ZMQ_NODISCARD void *handle() ZMQ_NOTHROW { return _handle; } ZMQ_NODISCARD const void *handle() const ZMQ_NOTHROW { return _handle; } ZMQ_EXPLICIT operator bool() const ZMQ_NOTHROW { return _handle != ZMQ_NULLPTR; } // note: non-const operator bool can be removed once // operator void* is removed from socket_t ZMQ_EXPLICIT operator bool() ZMQ_NOTHROW { return _handle != ZMQ_NULLPTR; } protected: void *_handle; }; } // namespace detail #ifdef ZMQ_CPP11 enum class socket_type : int { req = ZMQ_REQ, rep = ZMQ_REP, dealer = ZMQ_DEALER, router = ZMQ_ROUTER, pub = ZMQ_PUB, sub = ZMQ_SUB, xpub = ZMQ_XPUB, xsub = ZMQ_XSUB, push = ZMQ_PUSH, pull = ZMQ_PULL, #if defined(ZMQ_BUILD_DRAFT_API) && ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 0) server = ZMQ_SERVER, client = ZMQ_CLIENT, radio = ZMQ_RADIO, dish = ZMQ_DISH, #endif #if ZMQ_VERSION_MAJOR >= 4 stream = ZMQ_STREAM, #endif pair = ZMQ_PAIR }; #endif struct from_handle_t { struct _private {}; // disabling use other than with from_handle ZMQ_CONSTEXPR_FN ZMQ_EXPLICIT from_handle_t(_private /*p*/) ZMQ_NOTHROW {} }; ZMQ_CONSTEXPR_VAR from_handle_t from_handle = from_handle_t(from_handle_t::_private()); // A non-owning nullable reference to a socket. // The reference is invalidated on socket close or destruction. class socket_ref : public detail::socket_base { public: socket_ref() ZMQ_NOTHROW : detail::socket_base() {} #ifdef ZMQ_CPP11 socket_ref(std::nullptr_t) ZMQ_NOTHROW : detail::socket_base() {} #endif socket_ref(from_handle_t /*fh*/, void *handle) ZMQ_NOTHROW : detail::socket_base(handle) {} }; #ifdef ZMQ_CPP11 inline bool operator==(socket_ref sr, std::nullptr_t /*p*/) ZMQ_NOTHROW { return sr.handle() == nullptr; } inline bool operator==(std::nullptr_t /*p*/, socket_ref sr) ZMQ_NOTHROW { return sr.handle() == nullptr; } inline bool operator!=(socket_ref sr, std::nullptr_t /*p*/) ZMQ_NOTHROW { return !(sr == nullptr); } inline bool operator!=(std::nullptr_t /*p*/, socket_ref sr) ZMQ_NOTHROW { return !(sr == nullptr); } #endif inline bool operator==(socket_ref a, socket_ref b) ZMQ_NOTHROW { return std::equal_to()(a.handle(), b.handle()); } inline bool operator!=(socket_ref a, socket_ref b) ZMQ_NOTHROW { return !(a == b); } inline bool operator<(socket_ref a, socket_ref b) ZMQ_NOTHROW { return std::less()(a.handle(), b.handle()); } inline bool operator>(socket_ref a, socket_ref b) ZMQ_NOTHROW { return b < a; } inline bool operator<=(socket_ref a, socket_ref b) ZMQ_NOTHROW { return !(a > b); } inline bool operator>=(socket_ref a, socket_ref b) ZMQ_NOTHROW { return !(a < b); } } // namespace zmq #ifdef ZMQ_CPP11 namespace std { template<> struct hash { size_t operator()(zmq::socket_ref sr) const ZMQ_NOTHROW { return hash()(sr.handle()); } }; } // namespace std #endif namespace zmq { class socket_t : public detail::socket_base { friend class monitor_t; public: socket_t() ZMQ_NOTHROW : detail::socket_base(ZMQ_NULLPTR) , ctxptr(ZMQ_NULLPTR) { } socket_t(context_t &context_, int type_) : detail::socket_base(zmq_socket(static_cast(context_), type_)) , ctxptr(static_cast(context_)) { if (_handle == ZMQ_NULLPTR) throw error_t(); } #ifdef ZMQ_CPP11 socket_t(context_t &context_, socket_type type_) : socket_t(context_, static_cast(type_)) { } #endif #ifdef ZMQ_HAS_RVALUE_REFS socket_t(socket_t &&rhs) ZMQ_NOTHROW : detail::socket_base(rhs._handle), ctxptr(rhs.ctxptr) { rhs._handle = ZMQ_NULLPTR; rhs.ctxptr = ZMQ_NULLPTR; } socket_t &operator=(socket_t &&rhs) ZMQ_NOTHROW { close(); std::swap(_handle, rhs._handle); return *this; } #endif ~socket_t() ZMQ_NOTHROW { close(); } operator void *() ZMQ_NOTHROW { return _handle; } operator void const *() const ZMQ_NOTHROW { return _handle; } void close() ZMQ_NOTHROW { if (_handle == ZMQ_NULLPTR) // already closed return; int rc = zmq_close(_handle); ZMQ_ASSERT(rc == 0); _handle = ZMQ_NULLPTR; } void swap(socket_t &other) ZMQ_NOTHROW { std::swap(_handle, other._handle); std::swap(ctxptr, other.ctxptr); } operator socket_ref() ZMQ_NOTHROW { return socket_ref(from_handle, _handle); } private: void *ctxptr; socket_t(const socket_t &) ZMQ_DELETED_FUNCTION; void operator=(const socket_t &) ZMQ_DELETED_FUNCTION; // used by monitor_t socket_t(void *context_, int type_) : detail::socket_base(zmq_socket(context_, type_)) , ctxptr(context_) { if (_handle == ZMQ_NULLPTR) throw error_t(); } }; inline void swap(socket_t &a, socket_t &b) ZMQ_NOTHROW { a.swap(b); } ZMQ_DEPRECATED("from 4.3.1, use proxy taking socket_t objects") inline void proxy(void *frontend, void *backend, void *capture) { int rc = zmq_proxy(frontend, backend, capture); if (rc != 0) throw error_t(); } inline void proxy(socket_ref frontend, socket_ref backend, socket_ref capture = socket_ref()) { int rc = zmq_proxy(frontend.handle(), backend.handle(), capture.handle()); if (rc != 0) throw error_t(); } #ifdef ZMQ_HAS_PROXY_STEERABLE ZMQ_DEPRECATED("from 4.3.1, use proxy_steerable taking socket_t objects") inline void proxy_steerable(void *frontend, void *backend, void *capture, void *control) { int rc = zmq_proxy_steerable(frontend, backend, capture, control); if (rc != 0) throw error_t(); } inline void proxy_steerable(socket_ref frontend, socket_ref backend, socket_ref capture, socket_ref control) { int rc = zmq_proxy_steerable(frontend.handle(), backend.handle(), capture.handle(), control.handle()); if (rc != 0) throw error_t(); } #endif class monitor_t { public: monitor_t() : _socket(), _monitor_socket() {} virtual ~monitor_t() { close(); } #ifdef ZMQ_HAS_RVALUE_REFS monitor_t(monitor_t &&rhs) ZMQ_NOTHROW : _socket(), _monitor_socket() { std::swap(_socket, rhs._socket); std::swap(_monitor_socket, rhs._monitor_socket); } monitor_t &operator=(monitor_t &&rhs) ZMQ_NOTHROW { close(); _socket = socket_ref(); std::swap(_socket, rhs._socket); std::swap(_monitor_socket, rhs._monitor_socket); return *this; } #endif void monitor(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) { monitor(socket, addr.c_str(), events); } void monitor(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) { init(socket, addr_, events); while (true) { check_event(-1); } } void init(socket_t &socket, std::string const &addr, int events = ZMQ_EVENT_ALL) { init(socket, addr.c_str(), events); } void init(socket_t &socket, const char *addr_, int events = ZMQ_EVENT_ALL) { int rc = zmq_socket_monitor(socket.handle(), addr_, events); if (rc != 0) throw error_t(); _socket = socket; _monitor_socket = socket_t(socket.ctxptr, ZMQ_PAIR); _monitor_socket.connect(addr_); on_monitor_started(); } bool check_event(int timeout = 0) { assert(_monitor_socket); zmq_msg_t eventMsg; zmq_msg_init(&eventMsg); zmq::pollitem_t items[] = { {_monitor_socket.handle(), 0, ZMQ_POLLIN, 0}, }; zmq::poll(&items[0], 1, timeout); if (items[0].revents & ZMQ_POLLIN) { int rc = zmq_msg_recv(&eventMsg, _monitor_socket.handle(), 0); if (rc == -1 && zmq_errno() == ETERM) return false; assert(rc != -1); } else { zmq_msg_close(&eventMsg); return false; } #if ZMQ_VERSION_MAJOR >= 4 const char *data = static_cast(zmq_msg_data(&eventMsg)); zmq_event_t msgEvent; memcpy(&msgEvent.event, data, sizeof(uint16_t)); data += sizeof(uint16_t); memcpy(&msgEvent.value, data, sizeof(int32_t)); zmq_event_t *event = &msgEvent; #else zmq_event_t *event = static_cast(zmq_msg_data(&eventMsg)); #endif #ifdef ZMQ_NEW_MONITOR_EVENT_LAYOUT zmq_msg_t addrMsg; zmq_msg_init(&addrMsg); int rc = zmq_msg_recv(&addrMsg, _monitor_socket.handle(), 0); if (rc == -1 && zmq_errno() == ETERM) { zmq_msg_close(&eventMsg); return false; } assert(rc != -1); const char *str = static_cast(zmq_msg_data(&addrMsg)); std::string address(str, str + zmq_msg_size(&addrMsg)); zmq_msg_close(&addrMsg); #else // Bit of a hack, but all events in the zmq_event_t union have the same layout so this will work for all event types. std::string address = event->data.connected.addr; #endif #ifdef ZMQ_EVENT_MONITOR_STOPPED if (event->event == ZMQ_EVENT_MONITOR_STOPPED) { zmq_msg_close(&eventMsg); return false; } #endif switch (event->event) { case ZMQ_EVENT_CONNECTED: on_event_connected(*event, address.c_str()); break; case ZMQ_EVENT_CONNECT_DELAYED: on_event_connect_delayed(*event, address.c_str()); break; case ZMQ_EVENT_CONNECT_RETRIED: on_event_connect_retried(*event, address.c_str()); break; case ZMQ_EVENT_LISTENING: on_event_listening(*event, address.c_str()); break; case ZMQ_EVENT_BIND_FAILED: on_event_bind_failed(*event, address.c_str()); break; case ZMQ_EVENT_ACCEPTED: on_event_accepted(*event, address.c_str()); break; case ZMQ_EVENT_ACCEPT_FAILED: on_event_accept_failed(*event, address.c_str()); break; case ZMQ_EVENT_CLOSED: on_event_closed(*event, address.c_str()); break; case ZMQ_EVENT_CLOSE_FAILED: on_event_close_failed(*event, address.c_str()); break; case ZMQ_EVENT_DISCONNECTED: on_event_disconnected(*event, address.c_str()); break; #ifdef ZMQ_BUILD_DRAFT_API #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) case ZMQ_EVENT_HANDSHAKE_FAILED_NO_DETAIL: on_event_handshake_failed_no_detail(*event, address.c_str()); break; case ZMQ_EVENT_HANDSHAKE_FAILED_PROTOCOL: on_event_handshake_failed_protocol(*event, address.c_str()); break; case ZMQ_EVENT_HANDSHAKE_FAILED_AUTH: on_event_handshake_failed_auth(*event, address.c_str()); break; case ZMQ_EVENT_HANDSHAKE_SUCCEEDED: on_event_handshake_succeeded(*event, address.c_str()); break; #elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) case ZMQ_EVENT_HANDSHAKE_FAILED: on_event_handshake_failed(*event, address.c_str()); break; case ZMQ_EVENT_HANDSHAKE_SUCCEED: on_event_handshake_succeed(*event, address.c_str()); break; #endif #endif default: on_event_unknown(*event, address.c_str()); break; } zmq_msg_close(&eventMsg); return true; } #ifdef ZMQ_EVENT_MONITOR_STOPPED void abort() { if (_socket) zmq_socket_monitor(_socket.handle(), ZMQ_NULLPTR, 0); _socket = socket_ref(); } #endif virtual void on_monitor_started() {} virtual void on_event_connected(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_connect_delayed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_connect_retried(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_listening(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_bind_failed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_accepted(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_accept_failed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_closed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_close_failed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_disconnected(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) virtual void on_event_handshake_failed_no_detail(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_handshake_failed_protocol(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_handshake_failed_auth(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_handshake_succeeded(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } #elif ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 1) virtual void on_event_handshake_failed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } virtual void on_event_handshake_succeed(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } #endif virtual void on_event_unknown(const zmq_event_t &event_, const char *addr_) { (void) event_; (void) addr_; } private: monitor_t(const monitor_t &) ZMQ_DELETED_FUNCTION; void operator=(const monitor_t &) ZMQ_DELETED_FUNCTION; socket_ref _socket; socket_t _monitor_socket; void close() ZMQ_NOTHROW { if (_socket) zmq_socket_monitor(_socket.handle(), ZMQ_NULLPTR, 0); _monitor_socket.close(); } }; #if defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) // polling events enum class event_flags : short { none = 0, pollin = ZMQ_POLLIN, pollout = ZMQ_POLLOUT, pollerr = ZMQ_POLLERR, pollpri = ZMQ_POLLPRI }; constexpr event_flags operator|(event_flags a, event_flags b) noexcept { return detail::enum_bit_or(a, b); } constexpr event_flags operator&(event_flags a, event_flags b) noexcept { return detail::enum_bit_and(a, b); } constexpr event_flags operator^(event_flags a, event_flags b) noexcept { return detail::enum_bit_xor(a, b); } constexpr event_flags operator~(event_flags a) noexcept { return detail::enum_bit_not(a); } struct no_user_data; // layout compatible with zmq_poller_event_t template struct poller_event { socket_ref socket; #ifdef _WIN32 SOCKET fd; #else int fd; #endif T *user_data; event_flags events; }; template class poller_t { public: using event_type = poller_event; poller_t() : poller_ptr(zmq_poller_new()) { if (!poller_ptr) throw error_t(); } template< typename Dummy = void, typename = typename std::enable_if::value, Dummy>::type> void add(zmq::socket_ref socket, event_flags events, T *user_data) { add_impl(socket, events, user_data); } void add(zmq::socket_ref socket, event_flags events) { add_impl(socket, events, nullptr); } void remove(zmq::socket_ref socket) { if (0 != zmq_poller_remove(poller_ptr.get(), socket.handle())) { throw error_t(); } } void modify(zmq::socket_ref socket, event_flags events) { if (0 != zmq_poller_modify(poller_ptr.get(), socket.handle(), static_cast(events))) { throw error_t(); } } size_t wait_all(std::vector &poller_events, const std::chrono::milliseconds timeout) { int rc = zmq_poller_wait_all( poller_ptr.get(), reinterpret_cast(poller_events.data()), static_cast(poller_events.size()), static_cast(timeout.count())); if (rc > 0) return static_cast(rc); #if ZMQ_VERSION >= ZMQ_MAKE_VERSION(4, 2, 3) if (zmq_errno() == EAGAIN) #else if (zmq_errno() == ETIMEDOUT) #endif return 0; throw error_t(); } private: struct destroy_poller_t { void operator()(void *ptr) noexcept { int rc = zmq_poller_destroy(&ptr); ZMQ_ASSERT(rc == 0); } }; std::unique_ptr poller_ptr; void add_impl(zmq::socket_ref socket, event_flags events, T *user_data) { if (0 != zmq_poller_add(poller_ptr.get(), socket.handle(), user_data, static_cast(events))) { throw error_t(); } } }; #endif // defined(ZMQ_BUILD_DRAFT_API) && defined(ZMQ_CPP11) && defined(ZMQ_HAVE_POLLER) inline std::ostream &operator<<(std::ostream &os, const message_t &msg) { return os << msg.str(); } } // namespace zmq #endif // __ZMQ_HPP_INCLUDED__ Opendigitalradio-ODR-DabMod-f7eedef/m4/000077500000000000000000000000001475762153200177415ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/m4/ax_boost_base.m4000066400000000000000000000330061475762153200230150ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_boost_base.html # =========================================================================== # # SYNOPSIS # # AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # DESCRIPTION # # Test for the Boost C++ libraries of a particular version (or newer) # # If no path to the installed boost library is given the macro searchs # under /usr, /usr/local, /opt and /opt/local and evaluates the # $BOOST_ROOT environment variable. Further documentation is available at # . # # This macro calls: # # AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS) # # And sets: # # HAVE_BOOST # # LICENSE # # Copyright (c) 2008 Thomas Porschberg # Copyright (c) 2009 Peter Adolphs # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 47 # example boost program (need to pass version) m4_define([_AX_BOOST_BASE_PROGRAM], [AC_LANG_PROGRAM([[ #include ]],[[ (void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))])); ]])]) AC_DEFUN([AX_BOOST_BASE], [ AC_ARG_WITH([boost], [AS_HELP_STRING([--with-boost@<:@=ARG@:>@], [use Boost library from a standard location (ARG=yes), from the specified location (ARG=), or disable it (ARG=no) @<:@ARG=yes@:>@ ])], [ AS_CASE([$withval], [no],[want_boost="no";_AX_BOOST_BASE_boost_path=""], [yes],[want_boost="yes";_AX_BOOST_BASE_boost_path=""], [want_boost="yes";_AX_BOOST_BASE_boost_path="$withval"]) ], [want_boost="yes"]) AC_ARG_WITH([boost-libdir], [AS_HELP_STRING([--with-boost-libdir=LIB_DIR], [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.])], [ AS_IF([test -d "$withval"], [_AX_BOOST_BASE_boost_lib_path="$withval"], [AC_MSG_ERROR([--with-boost-libdir expected directory name])]) ], [_AX_BOOST_BASE_boost_lib_path=""]) BOOST_LDFLAGS="" BOOST_CPPFLAGS="" AS_IF([test "x$want_boost" = "xyes"], [_AX_BOOST_BASE_RUNDETECT([$1],[$2],[$3])]) AC_SUBST(BOOST_CPPFLAGS) AC_SUBST(BOOST_LDFLAGS) ]) # convert a version string in $2 to numeric and affect to polymorphic var $1 AC_DEFUN([_AX_BOOST_BASE_TONUMERICVERSION],[ AS_IF([test "x$2" = "x"],[_AX_BOOST_BASE_TONUMERICVERSION_req="1.20.0"],[_AX_BOOST_BASE_TONUMERICVERSION_req="$2"]) _AX_BOOST_BASE_TONUMERICVERSION_req_shorten=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([[0-9]]*\.[[0-9]]*\)'` _AX_BOOST_BASE_TONUMERICVERSION_req_major=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '\([[0-9]]*\)'` AS_IF([test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_major" = "x"], [AC_MSG_ERROR([You should at least specify libboost major version])]) _AX_BOOST_BASE_TONUMERICVERSION_req_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\.\([[0-9]]*\)'` AS_IF([test "x$_AX_BOOST_BASE_TONUMERICVERSION_req_minor" = "x"], [_AX_BOOST_BASE_TONUMERICVERSION_req_minor="0"]) _AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'` AS_IF([test "X$_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor" = "X"], [_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor="0"]) _AX_BOOST_BASE_TONUMERICVERSION_RET=`expr $_AX_BOOST_BASE_TONUMERICVERSION_req_major \* 100000 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_minor \* 100 \+ $_AX_BOOST_BASE_TONUMERICVERSION_req_sub_minor` AS_VAR_SET($1,$_AX_BOOST_BASE_TONUMERICVERSION_RET) ]) dnl Run the detection of boost should be run only if $want_boost AC_DEFUN([_AX_BOOST_BASE_RUNDETECT],[ _AX_BOOST_BASE_TONUMERICVERSION(WANT_BOOST_VERSION,[$1]) succeeded=no AC_REQUIRE([AC_CANONICAL_HOST]) dnl On 64-bit systems check for system libraries in both lib64 and lib. dnl The former is specified by FHS, but e.g. Debian does not adhere to dnl this (as it rises problems for generic multi-arch support). dnl The last entry in the list is chosen by default when no libraries dnl are found, e.g. when only header-only libraries are installed! AS_CASE([${host_cpu}], [x86_64],[libsubdirs="lib64 libx32 lib lib64"], [mips*64*],[libsubdirs="lib64 lib32 lib lib64"], [ppc64|powerpc64|s390x|sparc64|aarch64|ppc64le|powerpc64le|riscv64],[libsubdirs="lib64 lib lib64"], [libsubdirs="lib"] ) dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give dnl them priority over the other paths since, if libs are found there, they dnl are almost assuredly the ones desired. AS_CASE([${host_cpu}], [i?86],[multiarch_libsubdir="lib/i386-${host_os}"], [armv7l],[multiarch_libsubdir="lib/arm-${host_os}"], [multiarch_libsubdir="lib/${host_cpu}-${host_os}"] ) dnl first we check the system location for boost libraries dnl this location ist chosen if boost libraries are installed with the --layout=system option dnl or if you install boost with RPM AS_IF([test "x$_AX_BOOST_BASE_boost_path" != "x"],[ AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) includes in "$_AX_BOOST_BASE_boost_path/include"]) AS_IF([test -d "$_AX_BOOST_BASE_boost_path/include" && test -r "$_AX_BOOST_BASE_boost_path/include"],[ AC_MSG_RESULT([yes]) BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include" for _AX_BOOST_BASE_boost_path_tmp in $multiarch_libsubdir $libsubdirs; do AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION) lib path in "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp"]) AS_IF([test -d "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" && test -r "$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp" ],[ AC_MSG_RESULT([yes]) BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$_AX_BOOST_BASE_boost_path_tmp"; break; ], [AC_MSG_RESULT([no])]) done],[ AC_MSG_RESULT([no])]) ],[ if test X"$cross_compiling" = Xyes; then search_libsubdirs=$multiarch_libsubdir else search_libsubdirs="$multiarch_libsubdir $libsubdirs" fi for _AX_BOOST_BASE_boost_path_tmp in /usr /usr/local /opt /opt/local ; do if test -d "$_AX_BOOST_BASE_boost_path_tmp/include/boost" && test -r "$_AX_BOOST_BASE_boost_path_tmp/include/boost" ; then for libsubdir in $search_libsubdirs ; do if ls "$_AX_BOOST_BASE_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path_tmp/$libsubdir" BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path_tmp/include" break; fi done ]) dnl overwrite ld flags if we have required special directory with dnl --with-boost-libdir parameter AS_IF([test "x$_AX_BOOST_BASE_boost_lib_path" != "x"], [BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_lib_path"]) AC_MSG_CHECKING([for boostlib >= $1 ($WANT_BOOST_VERSION)]) CPPFLAGS_SAVED="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS_SAVED="$LDFLAGS" LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_REQUIRE([AC_PROG_CXX]) AC_LANG_PUSH(C++) AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[ AC_MSG_RESULT(yes) succeeded=yes found_system=yes ],[ ]) AC_LANG_POP([C++]) dnl if we found no boost with system layout we search for boost libraries dnl built and installed without the --layout=system option or for a staged(not installed) version if test "x$succeeded" != "xyes" ; then CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" BOOST_CPPFLAGS= if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then BOOST_LDFLAGS= fi _version=0 if test -n "$_AX_BOOST_BASE_boost_path" ; then if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path"; then for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do _version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` V_CHECK=`expr $_version_tmp \> $_version` if test "x$V_CHECK" = "x1" ; then _version=$_version_tmp fi VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path/include/boost-$VERSION_UNDERSCORE" done dnl if nothing found search for layout used in Windows distributions if test -z "$BOOST_CPPFLAGS"; then if test -d "$_AX_BOOST_BASE_boost_path/boost" && test -r "$_AX_BOOST_BASE_boost_path/boost"; then BOOST_CPPFLAGS="-I$_AX_BOOST_BASE_boost_path" fi fi dnl if we found something and BOOST_LDFLAGS was unset before dnl (because "$_AX_BOOST_BASE_boost_lib_path" = ""), set it here. if test -n "$BOOST_CPPFLAGS" && test -z "$BOOST_LDFLAGS"; then for libsubdir in $libsubdirs ; do if ls "$_AX_BOOST_BASE_boost_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done BOOST_LDFLAGS="-L$_AX_BOOST_BASE_boost_path/$libsubdir" fi fi else if test "x$cross_compiling" != "xyes" ; then for _AX_BOOST_BASE_boost_path in /usr /usr/local /opt /opt/local ; do if test -d "$_AX_BOOST_BASE_boost_path" && test -r "$_AX_BOOST_BASE_boost_path" ; then for i in `ls -d $_AX_BOOST_BASE_boost_path/include/boost-* 2>/dev/null`; do _version_tmp=`echo $i | sed "s#$_AX_BOOST_BASE_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'` V_CHECK=`expr $_version_tmp \> $_version` if test "x$V_CHECK" = "x1" ; then _version=$_version_tmp best_path=$_AX_BOOST_BASE_boost_path fi done fi done VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'` BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE" if test -z "$_AX_BOOST_BASE_boost_lib_path" ; then for libsubdir in $libsubdirs ; do if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done BOOST_LDFLAGS="-L$best_path/$libsubdir" fi fi if test -n "$BOOST_ROOT" ; then for libsubdir in $libsubdirs ; do if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi done if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'` stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'` stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'` V_CHECK=`expr $stage_version_shorten \>\= $_version` if test "x$V_CHECK" = "x1" && test -z "$_AX_BOOST_BASE_boost_lib_path" ; then AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT) BOOST_CPPFLAGS="-I$BOOST_ROOT" BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir" fi fi fi fi CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_LANG_PUSH(C++) AC_COMPILE_IFELSE([_AX_BOOST_BASE_PROGRAM($WANT_BOOST_VERSION)],[ AC_MSG_RESULT(yes) succeeded=yes found_system=yes ],[ ]) AC_LANG_POP([C++]) fi if test "x$succeeded" != "xyes" ; then if test "x$_version" = "x0" ; then AC_MSG_NOTICE([[We could not detect the boost libraries (version $1 or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) else AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) fi # execute ACTION-IF-NOT-FOUND (if present): ifelse([$3], , :, [$3]) else AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available]) # execute ACTION-IF-FOUND (if present): ifelse([$2], , :, [$2]) fi CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" ]) Opendigitalradio-ODR-DabMod-f7eedef/m4/ax_boost_thread.m4000066400000000000000000000143111475762153200233500ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_boost_thread.html # =========================================================================== # # SYNOPSIS # # AX_BOOST_THREAD # # DESCRIPTION # # Test for Thread library from the Boost C++ libraries. The macro requires # a preceding call to AX_BOOST_BASE. Further documentation is available at # . # # This macro calls: # # AC_SUBST(BOOST_THREAD_LIB) # # And sets: # # HAVE_BOOST_THREAD # # LICENSE # # Copyright (c) 2009 Thomas Porschberg # Copyright (c) 2009 Michael Tindal # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 33 AC_DEFUN([AX_BOOST_THREAD], [ AC_ARG_WITH([boost-thread], AS_HELP_STRING([--with-boost-thread@<:@=special-lib@:>@], [use the Thread library from boost - it is possible to specify a certain library for the linker e.g. --with-boost-thread=boost_thread-gcc-mt ]), [ if test "$withval" = "yes"; then want_boost="yes" ax_boost_user_thread_lib="" else want_boost="yes" ax_boost_user_thread_lib="$withval" fi ], [want_boost="yes"] ) if test "x$want_boost" = "xyes"; then AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_CANONICAL_BUILD]) CPPFLAGS_SAVED="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS_SAVED="$LDFLAGS" LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_CACHE_CHECK(whether the Boost::Thread library is available, ax_cv_boost_thread, [AC_LANG_PUSH([C++]) CXXFLAGS_SAVE=$CXXFLAGS case "x$host_os" in xsolaris ) CXXFLAGS="-pthreads $CXXFLAGS" break; ;; xmingw32 ) CXXFLAGS="-mthreads $CXXFLAGS" break; ;; *android* ) break; ;; * ) CXXFLAGS="-pthread $CXXFLAGS" break; ;; esac AC_COMPILE_IFELSE([ AC_LANG_PROGRAM( [[@%:@include ]], [[boost::thread_group thrds; return 0;]])], ax_cv_boost_thread=yes, ax_cv_boost_thread=no) CXXFLAGS=$CXXFLAGS_SAVE AC_LANG_POP([C++]) ]) if test "x$ax_cv_boost_thread" = "xyes"; then case "x$host_os" in xsolaris ) BOOST_CPPFLAGS="-pthreads $BOOST_CPPFLAGS" break; ;; xmingw32 ) BOOST_CPPFLAGS="-mthreads $BOOST_CPPFLAGS" break; ;; *android* ) break; ;; * ) BOOST_CPPFLAGS="-pthread $BOOST_CPPFLAGS" break; ;; esac AC_SUBST(BOOST_CPPFLAGS) AC_DEFINE(HAVE_BOOST_THREAD,, [define if the Boost::Thread library is available]) BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` LDFLAGS_SAVE=$LDFLAGS case "x$host_os" in *bsd* ) LDFLAGS="-pthread $LDFLAGS" break; ;; esac if test "x$ax_boost_user_thread_lib" = "x"; then for libextension in `ls -r $BOOSTLIBDIR/libboost_thread* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'`; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [link_thread="yes"; break], [link_thread="no"]) done if test "x$link_thread" != "xyes"; then for libextension in `ls -r $BOOSTLIBDIR/boost_thread* 2>/dev/null | sed 's,.*/,,' | sed 's,\..*,,'`; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [link_thread="yes"; break], [link_thread="no"]) done fi else for ax_lib in $ax_boost_user_thread_lib boost_thread-$ax_boost_user_thread_lib; do AC_CHECK_LIB($ax_lib, exit, [link_thread="yes"; break], [link_thread="no"]) done fi if test "x$ax_lib" = "x"; then AC_MSG_ERROR(Could not find a version of the Boost::Thread library!) fi if test "x$link_thread" = "xno"; then AC_MSG_ERROR(Could not link against $ax_lib !) else BOOST_THREAD_LIB="-l$ax_lib" case "x$host_os" in *bsd* ) BOOST_LDFLAGS="-pthread $BOOST_LDFLAGS" break; ;; xsolaris ) BOOST_THREAD_LIB="$BOOST_THREAD_LIB -lpthread" break; ;; xmingw32 ) break; ;; *android* ) break; ;; * ) BOOST_THREAD_LIB="$BOOST_THREAD_LIB -lpthread" break; ;; esac AC_SUBST(BOOST_THREAD_LIB) fi fi CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" fi ]) Opendigitalradio-ODR-DabMod-f7eedef/m4/ax_check_compile_flag.m4000066400000000000000000000040701475762153200244520ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the current language's compiler # or gives an error. (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 6 AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) AS_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS Opendigitalradio-ODR-DabMod-f7eedef/m4/ax_cxx_compile_stdcxx.m4000066400000000000000000000520731475762153200246110ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for # the respective C++ standard version. # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for no added switch, and then for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper # Copyright (c) 2020 Jason Merrill # Copyright (c) 2021 Jörn Heusipp # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 18 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], [$1], [20], [ax_cxx_compile_alternatives="20"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], [$2], [noext], [], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no m4_if([$2], [], [dnl AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, ax_cv_cxx_compile_cxx$1, [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [ax_cv_cxx_compile_cxx$1=yes], [ax_cv_cxx_compile_cxx$1=no])]) if test x$ax_cv_cxx_compile_cxx$1 = xyes; then ac_success=yes fi]) m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done fi]) m4_if([$2], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" dnl MSVC needs -std:c++NN for C++17 and later (default is C++14) for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}" MSVC; do if test x"$switch" = xMSVC; then dnl AS_TR_SH maps both `:` and `=` to `_` so -std:c++17 would collide dnl with -std=c++17. We suffix the cache variable name with _MSVC to dnl avoid this. switch=-std:c++${alternative} cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_${switch}_MSVC]) else cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) fi AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done if test x$ac_success = xyes; then break fi done fi]) AC_LANG_POP([C++]) if test x$ax_cxx_compile_cxx$1_required = xtrue; then if test x$ac_success = xno; then AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.]) fi fi if test x$ac_success = xno; then HAVE_CXX$1=0 AC_MSG_NOTICE([No compiler with C++$1 support was found]) else HAVE_CXX$1=1 AC_DEFINE(HAVE_CXX$1,1, [define if the compiler supports basic C++$1 syntax]) fi AC_SUBST(HAVE_CXX$1) ]) dnl Test body for checking C++11 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 ) dnl Test body for checking C++14 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 ) dnl Test body for checking C++17 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 ) dnl Test body for checking C++20 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 ) dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" // MSVC always sets __cplusplus to 199711L in older versions; newer versions // only set it correctly if /Zc:__cplusplus is specified as well as a // /std:c++NN switch: // https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ #elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { virtual ~Derived() override {} virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L ]]) dnl Tests for new features in C++14 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L ]]) dnl Tests for new features in C++17 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201703L && !defined _MSC_VER #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // __cplusplus < 201703L && !defined _MSC_VER ]]) dnl Tests for new features in C++20 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 202002L && !defined _MSC_VER #error "This is not a C++20 compiler" #else #include namespace cxx20 { // As C++20 supports feature test macros in the standard, there is no // immediate need to actually test for feature availability on the // Autoconf side. } // namespace cxx20 #endif // __cplusplus < 202002L && !defined _MSC_VER ]]) Opendigitalradio-ODR-DabMod-f7eedef/m4/ax_pthread.m4000066400000000000000000000527221475762153200223320ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also to link with them as well. For example, you might link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threaded programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to # that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the # PTHREAD_PRIO_INHERIT symbol is defined when compiling with # PTHREAD_CFLAGS. # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # Updated for Autoconf 2.68 by Daniel Richard G. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # Copyright (c) 2011 Daniel Richard G. # Copyright (c) 2019 Marc Stevens # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 27 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_PROG_SED]) AC_LANG_PUSH([C]) ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on Tru64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then ax_pthread_save_CC="$CC" ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = "xno"; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi CC="$ax_pthread_save_CC" CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items with a "," contain both # C compiler flags (before ",") and linker flags (after ","). Other items # starting with a "-" are C compiler flags, and remaining items are # library names, except for "none" which indicates that we try without # any flags at all, and "pthread-config" which is a program returning # the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 # (Note: HP C rejects this with "bad form for `-t' option") # -pthreads: Solaris/gcc (Note: HP C also rejects) # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads and # -D_REENTRANT too), HP C (must be checked before -lpthread, which # is present but should not be used directly; and before -mthreads, # because the compiler interprets this as "-mt" + "-hreads") # -mthreads: Mingw32/gcc, Lynx/gcc # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case $host_os in freebsd*) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) ax_pthread_flags="-kthread lthread $ax_pthread_flags" ;; hpux*) # From the cc(1) man page: "[-mt] Sets various -D flags to enable # multi-threading and also sets -lpthread." ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" ;; openedition*) # IBM z/OS requires a feature-test macro to be defined in order to # enable POSIX threads at all, so give the user a hint if this is # not set. (We don't define these ourselves, as they can affect # other portions of the system API in unpredictable ways.) AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], [ # if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) AX_PTHREAD_ZOS_MISSING # endif ], [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) ;; solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (N.B.: The stubs are missing # pthread_cleanup_push, or rather a function called by this macro, # so we could check for that, but who knows whether they'll stub # that too in a future libc.) So we'll check first for the # standard Solaris way of linking pthreads (-mt -lpthread). ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags" ;; esac # Are we compiling with Clang? AC_CACHE_CHECK([whether $CC is Clang], [ax_cv_PTHREAD_CLANG], [ax_cv_PTHREAD_CLANG=no # Note that Autoconf sets GCC=yes for Clang as well as GCC if test "x$GCC" = "xyes"; then AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ # if defined(__clang__) && defined(__llvm__) AX_PTHREAD_CC_IS_CLANG # endif ], [ax_cv_PTHREAD_CLANG=yes]) fi ]) ax_pthread_clang="$ax_cv_PTHREAD_CLANG" # GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) # Note that for GCC and Clang -pthread generally implies -lpthread, # except when -nostdlib is passed. # This is problematic using libtool to build C++ shared libraries with pthread: # [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460 # [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333 # [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555 # To solve this, first try -pthread together with -lpthread for GCC AS_IF([test "x$GCC" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"]) # Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first AS_IF([test "x$ax_pthread_clang" = "xyes"], [ax_pthread_flags="-pthread,-lpthread -pthread"]) # The presence of a feature test macro requesting re-entrant function # definitions is, on some systems, a strong hint that pthreads support is # correctly enabled case $host_os in darwin* | hpux* | linux* | osf* | solaris*) ax_pthread_check_macro="_REENTRANT" ;; aix*) ax_pthread_check_macro="_THREAD_SAFE" ;; *) ax_pthread_check_macro="--" ;; esac AS_IF([test "x$ax_pthread_check_macro" = "x--"], [ax_pthread_check_cond=0], [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) if test "x$ax_pthread_ok" = "xno"; then for ax_pthread_try_flag in $ax_pthread_flags; do case $ax_pthread_try_flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; *,*) PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"` PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"` AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) PTHREAD_CFLAGS="$ax_pthread_try_flag" ;; pthread-config) AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) PTHREAD_LIBS="-l$ax_pthread_try_flag" ;; esac ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_LINK_IFELSE([AC_LANG_PROGRAM([#include # if $ax_pthread_check_cond # error "$ax_pthread_check_macro must be defined" # endif static void *some_global = NULL; static void routine(void *a) { /* To avoid any unused-parameter or unused-but-set-parameter warning. */ some_global = a; } static void *start_routine(void *a) { return a; }], [pthread_t th; pthread_attr_t attr; pthread_create(&th, 0, start_routine, 0); pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_cleanup_pop(0) /* ; */])], [ax_pthread_ok=yes], []) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" AC_MSG_RESULT([$ax_pthread_ok]) AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Clang needs special handling, because older versions handle the -pthread # option in a rather... idiosyncratic way if test "x$ax_pthread_clang" = "xyes"; then # Clang takes -pthread; it has never supported any other flag # (Note 1: This will need to be revisited if a system that Clang # supports has POSIX threads in a separate library. This tends not # to be the way of modern systems, but it's conceivable.) # (Note 2: On some systems, notably Darwin, -pthread is not needed # to get POSIX threads support; the API is always present and # active. We could reasonably leave PTHREAD_CFLAGS empty. But # -pthread does define _REENTRANT, and while the Darwin headers # ignore this macro, third-party headers might not.) # However, older versions of Clang make a point of warning the user # that, in an invocation where only linking and no compilation is # taking place, the -pthread option has no effect ("argument unused # during compilation"). They expect -pthread to be passed in only # when source code is being compiled. # # Problem is, this is at odds with the way Automake and most other # C build frameworks function, which is that the same flags used in # compilation (CFLAGS) are also used in linking. Many systems # supported by AX_PTHREAD require exactly this for POSIX threads # support, and in fact it is often not straightforward to specify a # flag that is used only in the compilation phase and not in # linking. Such a scenario is extremely rare in practice. # # Even though use of the -pthread flag in linking would only print # a warning, this can be a nuisance for well-run software projects # that build with -Werror. So if the active version of Clang has # this misfeature, we search for an option to squash it. AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown # Create an alternate version of $ac_link that compiles and # links in two steps (.c -> .o, .o -> exe) instead of one # (.c -> exe), because the warning occurs only in the second # step ax_pthread_save_ac_link="$ac_link" ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" ax_pthread_save_CFLAGS="$CFLAGS" for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" ac_link="$ax_pthread_save_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [ac_link="$ax_pthread_2step_ac_link" AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], [break]) ]) done ac_link="$ax_pthread_save_ac_link" CFLAGS="$ax_pthread_save_CFLAGS" AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" ]) case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in no | unknown) ;; *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; esac fi # $ax_pthread_clang = yes # Various other checks: if test "x$ax_pthread_ok" = "xyes"; then ax_pthread_save_CFLAGS="$CFLAGS" ax_pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_CACHE_CHECK([for joinable pthread attribute], [ax_cv_PTHREAD_JOINABLE_ATTR], [ax_cv_PTHREAD_JOINABLE_ATTR=unknown for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], [int attr = $ax_pthread_attr; return attr /* ; */])], [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], []) done ]) AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ test "x$ax_pthread_joinable_attr_defined" != "xyes"], [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$ax_cv_PTHREAD_JOINABLE_ATTR], [Define to necessary symbol if this constant uses a non-standard name on your system.]) ax_pthread_joinable_attr_defined=yes ]) AC_CACHE_CHECK([whether more special flags are required for pthreads], [ax_cv_PTHREAD_SPECIAL_FLAGS], [ax_cv_PTHREAD_SPECIAL_FLAGS=no case $host_os in solaris*) ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" ;; esac ]) AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ test "x$ax_pthread_special_flags_added" != "xyes"], [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" ax_pthread_special_flags_added=yes]) AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], [ax_cv_PTHREAD_PRIO_INHERIT], [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT; return i;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ test "x$ax_pthread_prio_inherit_defined" != "xyes"], [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) ax_pthread_prio_inherit_defined=yes ]) CFLAGS="$ax_pthread_save_CFLAGS" LIBS="$ax_pthread_save_LIBS" # More AIX lossage: compile with *_r variant if test "x$GCC" != "xyes"; then case $host_os in aix*) AS_CASE(["x/$CC"], [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], [#handle absolute path differently from PATH based program lookup AS_CASE(["x$CC"], [x/*], [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test "x$ax_pthread_ok" = "xyes"; then ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_POP ])dnl AX_PTHREAD Opendigitalradio-ODR-DabMod-f7eedef/m4/pkg.m4000066400000000000000000000171671475762153200210000ustar00rootroot00000000000000# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # serial 1 (pkg-config-0.24) # # Copyright © 2004 Scott James Remnant . # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) # only at the first occurence in configure.ac, so if the first place # it's called might be skipped (such as if it is within an "if", you # have to call PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_default([$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$$1"; then pkg_cv_[]$1="$$1" elif test -n "$PKG_CONFIG"; then PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` test "x$?" != "x0" && pkg_failed=yes ], [pkg_failed=yes]) else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then AC_MSG_RESULT([no]) _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` else $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD m4_default([$4], [AC_MSG_ERROR( [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT])[]dnl ]) elif test $pkg_failed = untried; then AC_MSG_RESULT([no]) m4_default([$4], [AC_MSG_FAILURE( [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])[]dnl ]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) $3 fi[]dnl ])# PKG_CHECK_MODULES # PKG_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable pkgconfigdir as the location where a module # should install pkg-config .pc files. By default the directory is # $libdir/pkgconfig, but the default can be changed by passing # DIRECTORY. The user can override through the --with-pkgconfigdir # parameter. AC_DEFUN([PKG_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([pkgconfigdir], [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, [with_pkgconfigdir=]pkg_default) AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_INSTALLDIR # PKG_NOARCH_INSTALLDIR(DIRECTORY) # ------------------------- # Substitutes the variable noarch_pkgconfigdir as the location where a # module should install arch-independent pkg-config .pc files. By # default the directory is $datadir/pkgconfig, but the default can be # changed by passing DIRECTORY. The user can override through the # --with-noarch-pkgconfigdir parameter. AC_DEFUN([PKG_NOARCH_INSTALLDIR], [m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) m4_pushdef([pkg_description], [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) AC_ARG_WITH([noarch-pkgconfigdir], [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, [with_noarch_pkgconfigdir=]pkg_default) AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) m4_popdef([pkg_default]) m4_popdef([pkg_description]) ]) dnl PKG_NOARCH_INSTALLDIR # PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # ------------------------------------------- # Retrieves the value of the pkg-config variable for the given module. AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])# PKG_CHECK_VAR Opendigitalradio-ODR-DabMod-f7eedef/man/000077500000000000000000000000001475762153200201745ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/man/odr-dabmod.1000066400000000000000000000052351475762153200222730ustar00rootroot00000000000000.TH ODR-DABMOD "1" "April 2022" "" "User Commands" .SH NAME odr-dabmod \- DAB modulator compliant to ETSI EN 300 401 .SH SYNOPSIS .SY odr-dabmod .YS .SY odr-dabmod input (\fB\-f\fR filename \fB\-F\fR format | \fB\-u\fR uhddevice \fB\-F\fR frequency) [\fB\-o\fR offset][\fB\-G\fR txgain] [\fB\-T\fR filter_taps_file] [\fB\-a\fR gain] [\fB\-c\fR clockrate] [\fB\-g\fR gainMode] [\fB\-m\fR dabMode] [\fB\-r\fR samplingRate] [\fB\-l\fR] [\fB\-h\fR] .YS .PP Where input is: .IP ETI input filename (default: stdin), or .IP tcp://source:port for ETI\-over\-TCP input, or .IP zmq+tcp://source:port for ZMQ input, or .IP udp://:port for EDI input. .SH DESCRIPTION ODR-DabMod is a software-defined DAB modulator that receives or reads ETI, and generates modulated I/Q data usable for transmission. It can directly interface the Ettus USRP devices, and can also be used with other SDR signal sources. .PP This I/Q data which is encoded as complex floats (32bits per complex sample) can be written to a file or pipe, sent to a USRP device using the integrated output for the open-source USRP Hardware Driver (UHD) or to other software-defined radio (SDR) devices using the SoapySDR5 library. .PP The output of the modulator can also be sent to a GNURadio flow-graph for further processing, conversion or analysis using a ZeroMQ network connection. .SH OPTIONS .TP \fB\-f\fR Use file output with given filename. (use \fI\,/dev/stdout\/\fP for standard output) .TP \fB\-F\fR Set the output format (see doc/example.ini for formats) for the file output. .TP \fB\-o\fR Set the timestamp offset added to the timestamp in the ETI. The offset is a double. .IP Specifying this option has two implications: It enables synchronous transmission, requiring an external REFCLK and PPS signal and frames that do not contain a valid timestamp get muted. .TP \fB\-u\fR Use UHD output with given device string. (use for default device) .TP \fB\-F\fR Set the transmit frequency when using UHD output. (mandatory option when using UHD) .TP \fB\-G\fR Set the transmit gain for the UHD driver (default: 0) .TP \fB\-T\fR Enable filtering before the output, using the specified file containing the filter taps. .IP Use 'default' as taps_file to use the internal taps. .TP \fB\-a\fR Apply digital amplitude gain. .TP \fB\-c\fR Set the DAC clock rate and enable Cic Equalisation. .TP \fB\-g\fR Set computation gain mode: fix, max or var .TP \fB\-m\fR Set DAB mode: (0: auto, 1\-4: force). .TP \fB\-r\fR Set output sampling rate (default: 2048000). .TP \fB\-l\fR Loop file when reach end of file. .TP \fB\-h\fR Print this help. .SH SEE ALSO odr-dabmux(1) A user guide for the mmbTools is available http://www.opendigitalradio.org/ Opendigitalradio-ODR-DabMod-f7eedef/src/000077500000000000000000000000001475762153200202105ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/src/BlockPartitioner.cpp000066400000000000000000000077421475762153200242010ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "BlockPartitioner.h" #include "PcDebug.h" #include "Log.h" #include #include #include #include #include BlockPartitioner::BlockPartitioner(unsigned mode) : ModMux(), ModMetadata(), d_mode(mode) { PDEBUG("BlockPartitioner::BlockPartitioner(%i)\n", mode); switch (mode) { case 1: d_ficSize = 2304 / 8; d_cifCount = 4; d_outputFramesize = 3072 / 8; d_outputFramecount = 72; break; case 2: d_ficSize = 2304 / 8; d_cifCount = 1; d_outputFramesize = 768 / 8; d_outputFramecount = 72; break; case 3: d_ficSize = 3072 / 8; d_cifCount = 1; d_outputFramesize = 384 / 8; d_outputFramecount = 144; break; case 4: d_ficSize = 2304 / 8; d_cifCount = 2; d_outputFramesize = 1536 / 8; d_outputFramecount = 72; break; default: throw std::runtime_error( "BlockPartitioner::BlockPartitioner invalid mode"); break; } } // dataIn[0] -> FIC // dataIn[1] -> CIF int BlockPartitioner::process(std::vector dataIn, Buffer* dataOut) { assert(dataIn.size() == 2); dataOut->setLength(d_cifCount * (d_ficSize + d_cifSize)); #ifdef TRACE fprintf(stderr, "BlockPartitioner::process(dataIn:"); for (size_t i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %p", dataIn[i]); } fprintf(stderr, ", sizeIn:"); for (size_t i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %zu", dataIn[i]->getLength()); } fprintf(stderr, ", dataOut: %p, sizeOut: %zu)\n", dataOut, dataOut->getLength()); #endif if (dataIn[0]->getLength() != d_ficSize) { fprintf(stderr, "FIC is length %zu, should be %zu\n", dataIn[0]->getLength(), d_ficSize); throw std::runtime_error( "BlockPartitioner::process input 0 size not valid!"); } if (dataIn[1]->getLength() != d_cifSize) { throw std::runtime_error( "BlockPartitioner::process input 1 size not valid!"); } uint8_t* fic = reinterpret_cast(dataIn[0]->getData()); uint8_t* cif = reinterpret_cast(dataIn[1]->getData()); uint8_t* out = reinterpret_cast(dataOut->getData()); // Copy FIC data PDEBUG("Writing FIC %zu bytes to %zu\n", d_ficSize, d_cifNb * d_ficSize); memcpy(out + (d_cifNb * d_ficSize), fic, d_ficSize); // Copy CIF data PDEBUG("Writing CIF %u bytes to %zu\n", 864 * 8, (d_cifCount * d_ficSize) + (d_cifNb * 864 * 8)); memcpy(out + (d_cifCount * d_ficSize) + (d_cifNb * 864 * 8), cif, 864 * 8); if (++d_cifNb == d_cifCount) { d_cifNb = 0; } return d_cifNb == 0; } meta_vec_t BlockPartitioner::process_metadata(const meta_vec_t& metadataIn) { if (d_cifNb == 1) { d_meta.clear(); } std::copy(metadataIn.begin(), metadataIn.end(), std::back_inserter(d_meta)); if (d_cifNb == 0) { return d_meta; } else { return {}; } } Opendigitalradio-ODR-DabMod-f7eedef/src/BlockPartitioner.h000066400000000000000000000032011475762153200236300ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include class BlockPartitioner : public ModMux, public ModMetadata { public: BlockPartitioner(unsigned mode); int process(std::vector dataIn, Buffer* dataOut); const char* name() { return "BlockPartitioner"; } // The implementation assumes process_metadata is always called after process virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn); protected: int d_mode; size_t d_ficSize; size_t d_cifCount; size_t d_cifNb = 0; const size_t d_cifSize = 864 * 8; size_t d_outputFramesize; size_t d_outputFramecount; meta_vec_t d_meta; }; Opendigitalradio-ODR-DabMod-f7eedef/src/Buffer.cpp000066400000000000000000000073121475762153200221300ustar00rootroot00000000000000/* Copyright (C) 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "Buffer.h" #include "PcDebug.h" #include #include #include #include #include Buffer::Buffer(size_t len, const void *data) { PDEBUG("Buffer::Buffer(%zu, %p)\n", len, data); m_len = 0; m_capacity = 0; m_data = nullptr; setData(data, len); } Buffer::Buffer(const Buffer& other) { setData(other.m_data, other.m_len); } Buffer::Buffer(Buffer&& other) { m_len = other.m_len; m_capacity = other.m_capacity; m_data = other.m_data; other.m_len = 0; other.m_capacity = 0; other.m_data = nullptr; } Buffer::Buffer(const std::vector& vec) { PDEBUG("Buffer::Buffer(vector [%zu])\n", vec.size()); m_len = 0; m_capacity = 0; m_data = nullptr; setData(vec.data(), vec.size()); } Buffer::~Buffer() { PDEBUG("Buffer::~Buffer() len=%zu, data=%p\n", m_len, m_data); if (m_data) { free(m_data); } } void Buffer::swap(Buffer& other) { std::swap(m_len, other.m_len); std::swap(m_capacity, other.m_capacity); std::swap(m_data, other.m_data); } Buffer& Buffer::operator=(const Buffer& other) { if (&other != this) { setData(other.m_data, other.m_len); } return *this; } Buffer& Buffer::operator=(Buffer&& other) { if (&other != this) { m_len = other.m_len; m_capacity = other.m_capacity; if (m_data != nullptr) { free(m_data); } m_data = other.m_data; other.m_len = 0; other.m_capacity = 0; other.m_data = nullptr; } return *this; } Buffer& Buffer::operator=(const std::vector& buf) { setData(buf.data(), buf.size()); return *this; } Buffer& Buffer::operator+=(const Buffer& other) { appendData(other.m_data, other.m_len); return *this; } void Buffer::setLength(size_t len) { if (len > m_capacity) { void *tmp = m_data; /* Align to 32-byte boundary for AVX. */ const int ret = posix_memalign(&m_data, 32, len); if (ret != 0) { throw std::runtime_error("memory allocation failed: " + std::to_string(ret)); } if (tmp != nullptr) { memcpy(m_data, tmp, m_len); free(tmp); } m_capacity = len; } m_len = len; } void Buffer::setData(const void *data, size_t len) { setLength(0); appendData(data, len); } uint8_t Buffer::operator[](size_t i) const { if (i >= m_len) { throw std::out_of_range("index out of range"); } return reinterpret_cast(m_data)[i]; } void Buffer::appendData(const void *data, size_t len) { size_t offset = m_len; setLength(m_len + len); if (data != nullptr) { memcpy((char*)m_data + offset, data, len); } } void swap(Buffer& buf1, Buffer& buf2) { buf1.swap(buf2); } Opendigitalradio-ODR-DabMod-f7eedef/src/Buffer.h000066400000000000000000000054121475762153200215740ustar00rootroot00000000000000/* Copyright (C) 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include "fpm/fixed.hpp" typedef std::complex complexf; using fixed_16 = fpm::fixed; typedef std::complex complexfix; typedef std::complex complexfix_wide; /* Buffer is a container for a byte array, which is memory-aligned * to 32 bytes for SIMD performance. * * The allocation/freeing of the data is handled internally. */ class Buffer { public: using sptr = std::shared_ptr; Buffer(size_t len = 0, const void *data = nullptr); Buffer(const Buffer& other); Buffer(Buffer&& other); Buffer(const std::vector& vec); ~Buffer(); void swap(Buffer& other); /* Resize the buffer, reallocate memory if needed */ void setLength(size_t len); /* Replace the data in the Buffer by the new data given. * Reallocates memory if needed. */ void setData(const void *data, size_t len); Buffer& operator=(const Buffer& other); Buffer& operator=(Buffer&& other); Buffer& operator=(const std::vector& buf); uint8_t operator[](size_t i) const; /* Concatenate the current data with the new data given. * Reallocates memory if needed. */ void appendData(const void *data, size_t len); Buffer& operator+=(const Buffer& other); size_t getLength() const { return m_len; } void* getData() const { return m_data; } private: /* Current length of the data in the Buffer */ size_t m_len; /* Allocated size of the Buffer */ size_t m_capacity; /* Pointer to the data. Memory allocation is entirely * handled by setLength. */ void *m_data; }; void swap(Buffer& buf1, Buffer& buf2); Opendigitalradio-ODR-DabMod-f7eedef/src/CharsetTools.cpp000066400000000000000000000127721475762153200233370ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Most parts of this file are taken from dablin, Copyright (C) 2015-2022 Stefan Pöschel Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #include #include #include #include #include #include #include #include "CharsetTools.h" // --- CharsetTools ----------------------------------------------------------------- const char* CharsetTools::no_char = ""; const char* CharsetTools::ebu_values_0x00_to_0x1F[] = { no_char , "\u0118", "\u012E", "\u0172", "\u0102", "\u0116", "\u010E", "\u0218", "\u021A", "\u010A", no_char , no_char , "\u0120", "\u0139" , "\u017B", "\u0143", "\u0105", "\u0119", "\u012F", "\u0173", "\u0103", "\u0117", "\u010F", "\u0219", "\u021B", "\u010B", "\u0147", "\u011A", "\u0121", "\u013A", "\u017C", no_char }; const char* CharsetTools::ebu_values_0x7B_to_0xFF[] = { /* starting some chars earlier than 0x80 -----> */ "\u00AB", "\u016F", "\u00BB", "\u013D", "\u0126", "\u00E1", "\u00E0", "\u00E9", "\u00E8", "\u00ED", "\u00EC", "\u00F3", "\u00F2", "\u00FA", "\u00F9", "\u00D1", "\u00C7", "\u015E", "\u00DF", "\u00A1", "\u0178", "\u00E2", "\u00E4", "\u00EA", "\u00EB", "\u00EE", "\u00EF", "\u00F4", "\u00F6", "\u00FB", "\u00FC", "\u00F1", "\u00E7", "\u015F", "\u011F", "\u0131", "\u00FF", "\u0136", "\u0145", "\u00A9", "\u0122", "\u011E", "\u011B", "\u0148", "\u0151", "\u0150", "\u20AC", "\u00A3", "\u0024", "\u0100", "\u0112", "\u012A", "\u016A", "\u0137", "\u0146", "\u013B", "\u0123", "\u013C", "\u0130", "\u0144", "\u0171", "\u0170", "\u00BF", "\u013E", "\u00B0", "\u0101", "\u0113", "\u012B", "\u016B", "\u00C1", "\u00C0", "\u00C9", "\u00C8", "\u00CD", "\u00CC", "\u00D3", "\u00D2", "\u00DA", "\u00D9", "\u0158", "\u010C", "\u0160", "\u017D", "\u00D0", "\u013F", "\u00C2", "\u00C4", "\u00CA", "\u00CB", "\u00CE", "\u00CF", "\u00D4", "\u00D6", "\u00DB", "\u00DC", "\u0159", "\u010D", "\u0161", "\u017E", "\u0111", "\u0140", "\u00C3", "\u00C5", "\u00C6", "\u0152", "\u0177", "\u00DD", "\u00D5", "\u00D8", "\u00DE", "\u014A", "\u0154", "\u0106", "\u015A", "\u0179", "\u0164", "\u00F0", "\u00E3", "\u00E5", "\u00E6", "\u0153", "\u0175", "\u00FD", "\u00F5", "\u00F8", "\u00FE", "\u014B", "\u0155", "\u0107", "\u015B", "\u017A", "\u0165", "\u0127" }; std::string CharsetTools::ConvertCharEBUToUTF8(const uint8_t value) { // convert via LUT if(value <= 0x1F) return ebu_values_0x00_to_0x1F[value]; if(value >= 0x7B) return ebu_values_0x7B_to_0xFF[value - 0x7B]; // convert by hand (avoiding a LUT with mostly 1:1 mapping) switch(value) { case 0x24: return "\u0142"; case 0x5C: return "\u016E"; case 0x5E: return "\u0141"; case 0x60: return "\u0104"; } // leave untouched return std::string((char*) &value, 1); } std::string CharsetTools::ConvertTextToUTF8(const uint8_t *data, size_t len, int charset, std::string* charset_name) { // remove undesired chars std::vector cleaned_data; for(size_t i = 0; i < len; i++) { switch(data[i]) { case 0x00: // NULL case 0x0A: // PLB case 0x0B: // EoH case 0x1F: // PWB continue; default: cleaned_data.push_back(data[i]); } } // convert characters if(charset == 0b0000) { // EBU Latin based if(charset_name) *charset_name = "EBU Latin based"; std::string result; for(const uint8_t& c : cleaned_data) result += ConvertCharEBUToUTF8(c); return result; } if(charset == 0b1111) { // UTF-8 if(charset_name) *charset_name = "UTF-8"; return std::string((char*) &cleaned_data[0], cleaned_data.size()); } // ignore unsupported charset return ""; } size_t StringTools::UTF8CharsLen(const std::string &s, size_t chars) { size_t result; for(result = 0; result < s.size(); result++) { // if not a continuation byte, handle counter if((s[result] & 0xC0) != 0x80) { if(chars == 0) break; chars--; } } return result; } size_t StringTools::UTF8Len(const std::string &s) { // ignore continuation bytes return std::count_if(s.cbegin(), s.cend(), [](const char c){return (c & 0xC0) != 0x80;}); } std::string StringTools::UTF8Substr(const std::string &s, size_t pos, size_t count) { std::string result = s; result.erase(0, UTF8CharsLen(result, pos)); result.erase(UTF8CharsLen(result, count)); return result; } Opendigitalradio-ODR-DabMod-f7eedef/src/CharsetTools.h000066400000000000000000000036051475762153200227770ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Most parts of this file are taken from dablin, Copyright (C) 2015-2022 Stefan Pöschel Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #include #include #include #include #include #include #include class CharsetTools { private: static const char* no_char; static const char* ebu_values_0x00_to_0x1F[]; static const char* ebu_values_0x7B_to_0xFF[]; static std::string ConvertCharEBUToUTF8(const uint8_t value); public: static std::string ConvertTextToUTF8(const uint8_t *data, size_t len, int charset, std::string* charset_name); }; typedef std::vector string_vector_t; // --- StringTools ----------------------------------------------------------------- class StringTools { private: static size_t UTF8CharsLen(const std::string &s, size_t chars); public: static size_t UTF8Len(const std::string &s); static std::string UTF8Substr(const std::string &s, size_t pos, size_t count); }; Opendigitalradio-ODR-DabMod-f7eedef/src/CicEqualizer.cpp000066400000000000000000000053731475762153200233040ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "CicEqualizer.h" #include "PcDebug.h" #include #include CicEqualizer::CicEqualizer(size_t nbCarriers, size_t spacing, int R) : ModCodec(), myNbCarriers(nbCarriers), mySpacing(spacing), myFilter(nbCarriers) { PDEBUG("CicEqualizer::CicEqualizer(%zu, %zu, %i) @ %p\n", nbCarriers, spacing, R, this); const int M = 1; const int N = 4; const float pi = 4.0f * atanf(1.0f); for (size_t i = 0; i < nbCarriers; ++i) { int k = i < (nbCarriers + 1) / 2 ? i + ((nbCarriers & 1) ^ 1) : i - (int)nbCarriers; float angle = pi * k / spacing; if (k == 0) { myFilter[i] = 1.0f; } else { myFilter[i] = sinf(angle / R) / sinf(angle * M); myFilter[i] = fabsf(myFilter[i]) * R * M; myFilter[i] = powf(myFilter[i], N); } PDEBUG("HCic[%zu -> %i] = %f (%f dB) -> angle: %f\n", i, k,myFilter[i], 20.0 * log10(myFilter[i]), angle); } } CicEqualizer::~CicEqualizer() { PDEBUG("CicEqualizer::~CicEqualizer() @ %p\n", this); } int CicEqualizer::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("CicEqualizer::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); dataOut->setLength(dataIn->getLength()); const complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); size_t sizeOut = dataOut->getLength() / sizeof(complexf); if ((sizeIn % myNbCarriers) != 0) { PDEBUG("%zu != %zu\n", sizeIn, myNbCarriers); throw std::runtime_error( "CicEqualizer::process input size not valid!"); } for (size_t i = 0; i < sizeOut; ) { for (size_t j = 0; j < myNbCarriers; ++j, ++i) { out[i] = in[i] * myFilter[j]; } } return sizeOut; } Opendigitalradio-ODR-DabMod-f7eedef/src/CicEqualizer.h000066400000000000000000000025431475762153200227450ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include class CicEqualizer : public ModCodec { public: CicEqualizer(size_t nbCarriers, size_t spacing, int R); virtual ~CicEqualizer(); CicEqualizer(const CicEqualizer&); CicEqualizer& operator=(const CicEqualizer&); int process(Buffer* const dataIn, Buffer* dataOut); const char* name() { return "CicEqualizer"; } protected: size_t myNbCarriers; size_t mySpacing; std::vector myFilter; }; Opendigitalradio-ODR-DabMod-f7eedef/src/ConfigParser.cpp000066400000000000000000000605271475762153200233100ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "INIReader.h" #include "ConfigParser.h" #include "Utils.h" #include "Log.h" #include "Events.h" using namespace std; static GainMode parse_gainmode(const std::string &gainMode_setting) { string gainMode_minuscule(gainMode_setting); std::transform(gainMode_minuscule.begin(), gainMode_minuscule.end(), gainMode_minuscule.begin(), ::tolower); if (gainMode_minuscule == "0" or gainMode_minuscule == "fix") { return GainMode::GAIN_FIX; } else if (gainMode_minuscule == "1" or gainMode_minuscule == "max") { return GainMode::GAIN_MAX; } else if (gainMode_minuscule == "2" or gainMode_minuscule == "var") { return GainMode::GAIN_VAR; } cerr << "Modulator gainmode setting '" << gainMode_setting << "' not recognised." << endl; throw std::runtime_error("Configuration error"); } static FFTEngine parse_fft_engine(const std::string &fft_engine_setting) { string fft_engine_minuscule(fft_engine_setting); std::transform(fft_engine_minuscule.begin(), fft_engine_minuscule.end(), fft_engine_minuscule.begin(), ::tolower); if (fft_engine_minuscule == "fftw") { return FFTEngine::FFTW; } else if (fft_engine_minuscule == "kiss") { return FFTEngine::KISS; } else if (fft_engine_minuscule == "dexter") { return FFTEngine::DEXTER; } cerr << "Modulator fft_engine setting '" << fft_engine_setting << "' not recognised." << endl; throw std::runtime_error("Configuration error"); } static void parse_configfile( const std::string& configuration_file, mod_settings_t& mod_settings) { // First read parameters from the file INIReader pt(configuration_file); int line_err = pt.ParseError(); if (line_err) { std::cerr << "Error, cannot read configuration file '" << configuration_file.c_str() << "'" << std::endl; std::cerr << "At line: " << line_err << std::endl; throw std::runtime_error("Cannot read configuration file"); } mod_settings.startupCheck = pt.Get("general.startupcheck", ""); // remote controller interfaces: if (pt.GetInteger("remotecontrol.telnet", 0) == 1) { try { int telnetport = pt.GetInteger("remotecontrol.telnetport", 0); auto telnetrc = make_shared(telnetport); rcs.add_controller(telnetrc); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " telnet remote control enabled, but no telnetport defined.\n"; throw std::runtime_error("Configuration error"); } } #if defined(HAVE_ZEROMQ) if (pt.GetInteger("remotecontrol.zmqctrl", 0) == 1) { try { std::string zmqCtrlEndpoint = pt.Get("remotecontrol.zmqctrlendpoint", ""); auto zmqrc = make_shared(zmqCtrlEndpoint); rcs.add_controller(zmqrc); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " zmq remote control enabled, but no endpoint defined.\n"; throw std::runtime_error("Configuration error"); } } #endif // input params: if (pt.GetInteger("input.loop", 0) == 1) { mod_settings.loop = true; } mod_settings.inputTransport = pt.Get("input.transport", "file"); mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0); mod_settings.inputName = pt.Get("input.source", "/dev/stdin"); // log parameters: const string events_endpoint = pt.Get("log.events_endpoint", ""); if (not events_endpoint.empty()) { #if defined(HAVE_ZEROMQ) events.bind(events_endpoint); #else throw std::runtime_error("Cannot configure events sender when compiled without zeromq"); #endif } if (pt.GetInteger("log.syslog", 0) == 1) { etiLog.register_backend(make_shared()); } if (pt.GetInteger("log.filelog", 0) == 1) { std::string logfilename; try { logfilename = pt.Get("log.filename", ""); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " Configuration enables file log, but does not specify log filename\n"; throw std::runtime_error("Configuration error"); } etiLog.register_backend(make_shared(logfilename)); } std::string trace_filename = pt.Get("log.trace", ""); if (not trace_filename.empty()) { etiLog.register_backend(make_shared(trace_filename)); } mod_settings.showProcessTime = pt.GetInteger("log.show_process_time", mod_settings.showProcessTime); // modulator parameters: const string fft_engine_setting = pt.Get("modulator.fft_engine", "fftw"); mod_settings.fftEngine = parse_fft_engine(fft_engine_setting); const string gainMode_setting = pt.Get("modulator.gainmode", "var"); mod_settings.gainMode = parse_gainmode(gainMode_setting); mod_settings.gainmodeVariance = pt.GetReal("modulator.normalise_variance", mod_settings.gainmodeVariance); mod_settings.dabMode = pt.GetInteger("modulator.mode", mod_settings.dabMode); mod_settings.clockRate = pt.GetInteger("modulator.dac_clk_rate", (size_t)0); mod_settings.digitalgain = pt.GetReal("modulator.digital_gain", mod_settings.digitalgain); mod_settings.outputRate = pt.GetInteger("modulator.rate", mod_settings.outputRate); mod_settings.ofdmWindowOverlap = pt.GetInteger("modulator.ofdmwindowing", mod_settings.ofdmWindowOverlap); // FIR Filter parameters: if (pt.GetInteger("firfilter.enabled", 0) == 1) { mod_settings.filterTapsFilename = pt.Get("firfilter.filtertapsfile", "default"); } // Poly coefficients: if (pt.GetInteger("poly.enabled", 0) == 1) { mod_settings.polyCoefFilename = pt.Get("poly.polycoeffile", "dpd/poly.coef"); mod_settings.polyNumThreads = pt.GetInteger("poly.num_threads", 0); } // Crest factor reduction if (pt.GetInteger("cfr.enabled", 0) == 1) { mod_settings.enableCfr = true; mod_settings.cfrClip = pt.GetReal("cfr.clip", 0.0); mod_settings.cfrErrorClip = pt.GetReal("cfr.error_clip", 0.0); } // Output options std::string output_selected = pt.Get("output.output", ""); if(output_selected == "") { std::cerr << "Error:\n"; std::cerr << " Configuration does not specify output\n"; throw std::runtime_error("Configuration error"); } if (output_selected == "file") { mod_settings.outputName = pt.Get("fileoutput.filename", ""); if(mod_settings.outputName == "") { std::cerr << "Error:\n"; std::cerr << " Configuration does not specify file name for file output\n"; throw std::runtime_error("Configuration error"); } mod_settings.fileOutputShowMetadata = (pt.GetInteger("fileoutput.show_metadata", 0) > 0); mod_settings.useFileOutput = true; mod_settings.fileOutputFormat = pt.Get("fileoutput.format", mod_settings.fileOutputFormat); } #if defined(HAVE_OUTPUT_UHD) else if (output_selected == "uhd") { Output::SDRDeviceConfig sdr_device_config; string device = pt.Get("uhdoutput.device", ""); const auto usrpType = pt.Get("uhdoutput.type", ""); if (usrpType != "") { if (not device.empty()) { device += ","; } device += "type=" + usrpType; } sdr_device_config.device = device; sdr_device_config.subDevice = pt.Get("uhdoutput.subdevice", ""); sdr_device_config.masterClockRate = pt.GetInteger("uhdoutput.master_clock_rate", 0); if (sdr_device_config.device.find("master_clock_rate") != std::string::npos) { std::cerr << "Warning:" "setting master_clock_rate in [uhd] device is deprecated !\n"; } if (sdr_device_config.device.find("type=") != std::string::npos) { std::cerr << "Warning:" "setting type in [uhd] device is deprecated !\n"; } sdr_device_config.txgain = pt.GetReal("uhdoutput.txgain", 0.0); sdr_device_config.tx_antenna = pt.Get("uhdoutput.tx_antenna", ""); sdr_device_config.rx_antenna = pt.Get("uhdoutput.rx_antenna", "RX2"); sdr_device_config.rxgain = pt.GetReal("uhdoutput.rxgain", 0.0); sdr_device_config.frequency = pt.GetReal("uhdoutput.frequency", 0); sdr_device_config.bandwidth = pt.GetReal("uhdoutput.bandwidth", 0); std::string chan = pt.Get("uhdoutput.channel", ""); sdr_device_config.dabMode = mod_settings.dabMode; if (sdr_device_config.frequency == 0 && chan == "") { std::cerr << " UHD output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (sdr_device_config.frequency == 0) { sdr_device_config.frequency = parse_channel(chan); } else if (sdr_device_config.frequency != 0 && chan != "") { std::cerr << " UHD output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } sdr_device_config.lo_offset = pt.GetReal("uhdoutput.lo_offset", 0); sdr_device_config.refclk_src = pt.Get("uhdoutput.refclk_source", "internal"); sdr_device_config.pps_src = pt.Get("uhdoutput.pps_source", "none"); sdr_device_config.pps_polarity = pt.Get("uhdoutput.pps_polarity", "pos"); std::string behave = pt.Get("uhdoutput.behaviour_refclk_lock_lost", "ignore"); if (behave == "crash") { sdr_device_config.refclk_lock_loss_behaviour = Output::CRASH; } else if (behave == "ignore") { sdr_device_config.refclk_lock_loss_behaviour = Output::IGNORE; } else { std::cerr << "Error: UHD output: behaviour_refclk_lock_lost invalid." << std::endl; throw std::runtime_error("Configuration error"); } sdr_device_config.maxGPSHoldoverTime = pt.GetInteger("uhdoutput.max_gps_holdover_time", 0); sdr_device_config.dpdFeedbackServerPort = pt.GetInteger("uhdoutput.dpd_port", 0); mod_settings.sdr_device_config = sdr_device_config; mod_settings.useUHDOutput = true; } #endif // defined(HAVE_OUTPUT_UHD) #if defined(HAVE_SOAPYSDR) else if (output_selected == "soapysdr") { auto& outputsoapy_conf = mod_settings.sdr_device_config; outputsoapy_conf.device = pt.Get("soapyoutput.device", ""); outputsoapy_conf.masterClockRate = pt.GetInteger("soapyoutput.master_clock_rate", 0); outputsoapy_conf.txgain = pt.GetReal("soapyoutput.txgain", 0.0); outputsoapy_conf.tx_antenna = pt.Get("soapyoutput.tx_antenna", ""); outputsoapy_conf.lo_offset = pt.GetReal("soapyoutput.lo_offset", 0.0); outputsoapy_conf.frequency = pt.GetReal("soapyoutput.frequency", 0); outputsoapy_conf.bandwidth = pt.GetReal("soapyoutput.bandwidth", 0); std::string chan = pt.Get("soapyoutput.channel", ""); outputsoapy_conf.dabMode = mod_settings.dabMode; if (outputsoapy_conf.frequency == 0 && chan == "") { std::cerr << " soapy output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputsoapy_conf.frequency == 0) { outputsoapy_conf.frequency = parse_channel(chan); } else if (outputsoapy_conf.frequency != 0 && chan != "") { std::cerr << " soapy output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } outputsoapy_conf.dpdFeedbackServerPort = pt.GetInteger("soapyoutput.dpd_port", 0); mod_settings.useSoapyOutput = true; } #endif // defined(HAVE_SOAPYSDR) #if defined(HAVE_DEXTER) else if (output_selected == "dexter") { auto& outputdexter_conf = mod_settings.sdr_device_config; outputdexter_conf.txgain = pt.GetReal("dexteroutput.txgain", 0.0); outputdexter_conf.lo_offset = pt.GetReal("dexteroutput.lo_offset", 0.0); outputdexter_conf.frequency = pt.GetReal("dexteroutput.frequency", 0); std::string chan = pt.Get("dexteroutput.channel", ""); outputdexter_conf.dabMode = mod_settings.dabMode; outputdexter_conf.maxGPSHoldoverTime = pt.GetInteger("dexteroutput.max_gps_holdover_time", 0); if (outputdexter_conf.frequency == 0 && chan == "") { std::cerr << " dexter output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputdexter_conf.frequency == 0) { outputdexter_conf.frequency = parse_channel(chan); } else if (outputdexter_conf.frequency != 0 && chan != "") { std::cerr << " dexter output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } mod_settings.useDexterOutput = true; } #endif // defined(HAVE_DEXTER) #if defined(HAVE_LIMESDR) else if (output_selected == "limesdr") { auto& outputlime_conf = mod_settings.sdr_device_config; outputlime_conf.device = pt.Get("limeoutput.device", ""); outputlime_conf.masterClockRate = pt.GetInteger("limeoutput.master_clock_rate", 0); outputlime_conf.txgain = pt.GetReal("limeoutput.txgain", 0.0); outputlime_conf.tx_antenna = pt.Get("limeoutput.tx_antenna", ""); outputlime_conf.lo_offset = pt.GetReal("limeoutput.lo_offset", 0.0); outputlime_conf.frequency = pt.GetReal("limeoutput.frequency", 0); std::string chan = pt.Get("limeoutput.channel", ""); outputlime_conf.dabMode = mod_settings.dabMode; outputlime_conf.upsample = pt.GetInteger("limeoutput.upsample", 1); if (outputlime_conf.frequency == 0 && chan == "") { std::cerr << " Lime output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputlime_conf.frequency == 0) { outputlime_conf.frequency = parse_channel(chan); } else if (outputlime_conf.frequency != 0 && chan != "") { std::cerr << " Lime output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } outputlime_conf.dpdFeedbackServerPort = pt.GetInteger("limeoutput.dpd_port", 0); mod_settings.useLimeOutput = true; } #endif // defined(HAVE_LIMESDR) #if defined(HAVE_BLADERF) else if (output_selected == "bladerf") { auto& outputbladerf_conf = mod_settings.sdr_device_config; outputbladerf_conf.device = pt.Get("bladerfoutput.device", ""); outputbladerf_conf.refclk_src = pt.Get("bladerfoutput.refclk_source", ""); outputbladerf_conf.txgain = pt.GetReal("bladerfoutput.txgain", 0.0); outputbladerf_conf.frequency = pt.GetReal("bladerfoutput.frequency", 0); outputbladerf_conf.bandwidth = pt.GetReal("bladerfoutput.bandwidth", 0); std::string chan = pt.Get("bladerfoutput.channel", ""); outputbladerf_conf.dabMode = mod_settings.dabMode; if (outputbladerf_conf.frequency == 0 && chan == "") { std::cerr << " BladeRF output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputbladerf_conf.frequency == 0) { outputbladerf_conf.frequency = parse_channel(chan); } else if (outputbladerf_conf.frequency != 0 && chan != "") { std::cerr << " BladeRF output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } outputbladerf_conf.dpdFeedbackServerPort = pt.GetInteger("bladerfoutput.dpd_port", 0); mod_settings.useBladeRFOutput = true; } #endif // defined(HAVE_BLADERF) #if defined(HAVE_ZEROMQ) else if (output_selected == "zmq") { mod_settings.outputName = pt.Get("zmqoutput.listen", ""); mod_settings.zmqOutputSocketType = pt.Get("zmqoutput.socket_type", ""); mod_settings.useZeroMQOutput = true; } #endif else { std::cerr << "Error: Invalid output defined.\n"; throw std::runtime_error("Configuration error"); } #if defined(HAVE_OUTPUT_UHD) || defined(HAVE_DEXTER) mod_settings.sdr_device_config.enableSync = (pt.GetInteger("delaymanagement.synchronous", 0) == 1); mod_settings.sdr_device_config.muteNoTimestamps = (pt.GetInteger("delaymanagement.mutenotimestamps", 0) == 1); if (mod_settings.sdr_device_config.enableSync) { std::string delay_mgmt = pt.Get("delaymanagement.management", ""); std::string fixedoffset = pt.Get("delaymanagement.fixedoffset", ""); std::string offset_filename = pt.Get("delaymanagement.dynamicoffsetfile", ""); if (not(delay_mgmt.empty() and fixedoffset.empty() and offset_filename.empty())) { std::cerr << "Warning: you are using the old config syntax for the offset management.\n"; std::cerr << " Please see the example.ini configuration for the new settings.\n"; } try { mod_settings.tist_offset_s = pt.GetReal("delaymanagement.offset", 0.0); } catch (const std::exception &e) { std::cerr << "Error: delaymanagement: synchronous is enabled, but no offset defined!\n"; throw std::runtime_error("Configuration error"); } } #endif /* Read TII parameters from config file */ mod_settings.tiiConfig.enable = pt.GetInteger("tii.enable", 0); mod_settings.tiiConfig.comb = pt.GetInteger("tii.comb", 0); mod_settings.tiiConfig.pattern = pt.GetInteger("tii.pattern", 0); mod_settings.tiiConfig.old_variant = pt.GetInteger("tii.old_variant", 0); } void parse_args(int argc, char **argv, mod_settings_t& mod_settings) { bool use_configuration_cmdline = false; bool use_configuration_file = false; std::string configuration_file; // No argument given ? You can't be serious ! Show usage. if (argc == 1) { printUsage(argv[0]); throw std::invalid_argument("Invalid command line options"); } while (true) { int c = getopt(argc, argv, "a:C:c:f:F:g:G:hlm:o:O:r:T:u:V"); if (c == -1) { break; } if (c != 'C') { use_configuration_cmdline = true; } switch (c) { case 'C': use_configuration_file = true; configuration_file = optarg; break; case 'a': mod_settings.digitalgain = strtof(optarg, NULL); break; case 'c': mod_settings.clockRate = strtol(optarg, NULL, 0); break; case 'f': #if defined(HAVE_OUTPUT_UHD) if (mod_settings.useUHDOutput) { throw std::invalid_argument("Options -u and -f are mutually exclusive"); } #endif mod_settings.outputName = optarg; mod_settings.useFileOutput = true; break; case 'F': if (mod_settings.useFileOutput) { mod_settings.fileOutputFormat = optarg; } #if defined(HAVE_OUTPUT_UHD) else if (mod_settings.useUHDOutput) { mod_settings.sdr_device_config.frequency = strtof(optarg, NULL); } #endif else { throw std::invalid_argument("Cannot use -F before setting output!"); } break; case 'g': mod_settings.gainMode = parse_gainmode(optarg); break; case 'G': #if defined(HAVE_OUTPUT_UHD) mod_settings.sdr_device_config.txgain = strtod(optarg, NULL); #endif break; case 'l': mod_settings.loop = true; break; case 'o': mod_settings.tist_offset_s = strtod(optarg, NULL); #if defined(HAVE_OUTPUT_UHD) mod_settings.sdr_device_config.enableSync = true; #endif break; case 'm': mod_settings.dabMode = strtol(optarg, NULL, 0); break; case 'r': mod_settings.outputRate = strtol(optarg, NULL, 0); break; case 'T': mod_settings.filterTapsFilename = optarg; break; case 'u': #if defined(HAVE_OUTPUT_UHD) if (mod_settings.useFileOutput) { throw std::invalid_argument("Options -u and -f are mutually exclusive"); } mod_settings.sdr_device_config.device = optarg; mod_settings.sdr_device_config.refclk_src = "internal"; mod_settings.sdr_device_config.pps_src = "none"; mod_settings.sdr_device_config.pps_polarity = "pos"; mod_settings.useUHDOutput = true; #else throw std::invalid_argument("Cannot select UHD output, not compiled in!"); #endif break; case 'V': printVersion(); throw std::invalid_argument(""); break; case '?': case 'h': printUsage(argv[0]); throw std::invalid_argument(""); break; default: { string optstr(1, c); throw std::invalid_argument("Invalid command line option: -" + optstr); } } } // If only one argument is given, interpret as configuration file name if (argc == 2) { use_configuration_file = true; configuration_file = argv[1]; } if (use_configuration_file && use_configuration_cmdline) { fprintf(stderr, "Warning: configuration file and command " "line parameters are defined:\n\t" "Command line parameters override settings " "in the configuration file !\n"); } // Setting ETI input filename if (use_configuration_cmdline && mod_settings.inputName == "") { if (optind < argc) { mod_settings.inputName = argv[optind++]; if (mod_settings.inputName.substr(0, 4) == "zmq+" && mod_settings.inputName.find("://") != std::string::npos) { throw std::runtime_error("Support for ZeroMQ input transport has been removed."); } else if (mod_settings.inputName.substr(0, 6) == "tcp://") { mod_settings.inputTransport = "tcp"; } else if (mod_settings.inputName.substr(0, 6) == "udp://") { mod_settings.inputTransport = "edi"; } } else { mod_settings.inputName = "/dev/stdin"; } } // Checking unused arguments if (use_configuration_cmdline && optind != argc) { string invalid = "Invalid arguments:"; while (optind != argc) { invalid += argv[optind++]; } printUsage(argv[0]); etiLog.level(error) << "Received invalid command line arguments: " + invalid; throw std::invalid_argument("Invalid command line options"); } if (use_configuration_file) { parse_configfile(configuration_file, mod_settings); } } Opendigitalradio-ODR-DabMod-f7eedef/src/ConfigParser.h000066400000000000000000000051471475762153200227520ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "GainControl.h" #include "TII.h" #include "output/SDRDevice.h" enum class FFTEngine { FFTW, // floating point in software KISS, // fixed-point in software DEXTER // fixed-point in FPGA }; struct mod_settings_t { std::string startupCheck; std::string outputName; bool useZeroMQOutput = false; std::string zmqOutputSocketType = ""; bool useFileOutput = false; std::string fileOutputFormat = "complexf"; bool fileOutputShowMetadata = false; bool useUHDOutput = false; bool useSoapyOutput = false; bool useDexterOutput = false; bool useLimeOutput = false; bool useBladeRFOutput = false; FFTEngine fftEngine = FFTEngine::FFTW; size_t outputRate = 2048000; size_t clockRate = 0; unsigned dabMode = 1; float digitalgain = 1.0f; float normalise = 1.0f; GainMode gainMode = GainMode::GAIN_VAR; float gainmodeVariance = 4.0f; // To handle the timestamp offset of the modulator double tist_offset_s = 0.0; bool loop = false; std::string inputName = ""; std::string inputTransport = "file"; float edi_max_delay_ms = 0.0f; tii_config_t tiiConfig; std::string filterTapsFilename = ""; std::string polyCoefFilename = ""; unsigned polyNumThreads = 0; // Settings for crest factor reduction bool enableCfr = false; float cfrClip = 1.0f; float cfrErrorClip = 1.0f; // Settings for the OFDM windowing size_t ofdmWindowOverlap = 0; Output::SDRDeviceConfig sdr_device_config; bool showProcessTime = true; }; void parse_args(int argc, char **argv, mod_settings_t& mod_settings); Opendigitalradio-ODR-DabMod-f7eedef/src/ConvEncoder.cpp000066400000000000000000000127701475762153200231300ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "ConvEncoder.h" #include "PcDebug.h" #include #include #include #include const static uint8_t PARITY[] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; ConvEncoder::ConvEncoder(size_t framesize) : ModCodec(), d_framesize(framesize) { PDEBUG("ConvEncoder::ConvEncoder(%zu)\n", framesize); } int ConvEncoder::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("ConvEncoder::process" "(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); size_t in_block_size = d_framesize; size_t out_block_size = (d_framesize * 4) + 3; size_t in_offset = 0; size_t out_offset = 0; uint16_t memory = 0; uint8_t data; if (dataIn->getLength() != in_block_size) { PDEBUG("%zu != %zu != 0\n", dataIn->getLength(), in_block_size); throw std::runtime_error( "ConvEncoder::process input size not valid!\n"); } dataOut->setLength(out_block_size); const uint8_t* in = reinterpret_cast(dataIn->getData()); uint8_t* out = reinterpret_cast(dataOut->getData()); // While there is enought input and ouput items while (dataIn->getLength() - in_offset >= in_block_size && dataOut->getLength() - out_offset >= out_block_size) { for (size_t in_count = 0; in_count < in_block_size; ++in_count) { data = in[in_offset]; //PDEBUG("Input: 0x%x\n", data); // For next 4 output bytes for (unsigned out_count = 0; out_count < 4; ++out_count) { out[out_offset] = 0; // For each 4-bit output word for (unsigned j = 0; j < 2; ++j) { memory >>= 1; memory |= (data >> 7) << 6; data <<= 1; //PDEBUG("Memory: 0x%x\n", memory); uint8_t poly[4] = { (uint8_t)(memory & 0x5b), (uint8_t)(memory & 0x79), (uint8_t)(memory & 0x65), (uint8_t)(memory & 0x5b) }; //PDEBUG("Polys: 0x%x, 0x%x, 0x%x, 0x%x\n", poly[0], poly[1], poly[2], poly[3]); // For each poly for (unsigned k = 0; k < 4; ++k) { out[out_offset] <<= 1; out[out_offset] |= PARITY[poly[k]]; //PDEBUG("Out bit: %i\n", out[no] >> 7); } } //PDEBUG("Out: 0x%x\n", out[no]); ++out_offset; } ++in_offset; } for (unsigned pad_count = 0; pad_count < 3; ++pad_count) { out[out_offset] = 0; // For each 4-bit output word for (unsigned j = 0; j < 2; ++j) { memory >>= 1; //PDEBUG("Memory: 0x%x\n", memory); uint8_t poly[4] = { (uint8_t)(memory & 0x5b), (uint8_t)(memory & 0x79), (uint8_t)(memory & 0x65), (uint8_t)(memory & 0x5b) }; //PDEBUG("Polys: 0x%x, 0x%x, 0x%x, 0x%x\n", poly[0], poly[1], poly[2], poly[3]); // For each poly for (unsigned k = 0; k < 4; ++k) { out[out_offset] <<= 1; out[out_offset] |= PARITY[poly[k]]; //PDEBUG("Out bit: %i\n", out[no] >> 7); } } //PDEBUG("Out: 0x%x\n", out[no]); ++out_offset; } } PDEBUG(" Consume: %zu\n", in_offset); PDEBUG(" Return: %zu\n", out_offset); if (out_offset != dataOut->getLength()) { throw std::runtime_error("Assertion error: ConvEncoder output " + std::to_string(out_offset) + " == " + std::to_string(dataOut->getLength()) + " fail"); } return out_offset; } Opendigitalradio-ODR-DabMod-f7eedef/src/ConvEncoder.h000066400000000000000000000022171475762153200225700ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include class ConvEncoder : public ModCodec { public: ConvEncoder(size_t framesize); int process(Buffer* const dataIn, Buffer* dataOut); const char* name() { return "ConvEncoder"; } private: size_t d_framesize; }; Opendigitalradio-ODR-DabMod-f7eedef/src/DabMod.cpp000066400000000000000000000676731475762153200220650ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #if HAVE_NETINET_IN_H # include #endif #include "Events.h" #include "Utils.h" #include "Log.h" #include "DabModulator.h" #include "OutputFile.h" #include "FormatConverter.h" #include "FrameMultiplexer.h" #include "output/SDR.h" #include "output/UHD.h" #include "output/Soapy.h" #include "output/Dexter.h" #include "output/Lime.h" #include "output/BladeRF.h" #include "OutputZeroMQ.h" #include "InputReader.h" #include "PcDebug.h" #include "FIRFilter.h" #include "RemoteControl.h" #include "ConfigParser.h" /* UHD requires the input I and Q samples to be in the interval * [-1.0,1.0], otherwise they get truncated, which creates very * wide-spectrum spikes. Depending on the Transmission Mode, the * Gain Mode and the sample rate (and maybe other parameters), the * samples can have peaks up to about 48000. The value of 50000 * should guarantee that with a digital gain of 1.0, UHD never clips * our samples. * * This only applies when fixed_point == false. */ static const float normalise_factor = 50000.0f; // Empirical normalisation factors used to normalise the samples to amplitude 1. static const float normalise_factor_file_fix = 81000.0f; static const float normalise_factor_file_var = 46000.0f; static const float normalise_factor_file_max = 46000.0f; using namespace std; volatile sig_atomic_t running = 1; void signalHandler(int signalNb) { PDEBUG("signalHandler(%i)\n", signalNb); running = 0; } class ModulatorData : public RemoteControllable { public: // For ETI std::shared_ptr inputReader; std::shared_ptr etiReader; // For EDI std::shared_ptr ediInput; // Common to both EDI and EDI uint64_t framecount = 0; Flowgraph *flowgraph = nullptr; // RC-related ModulatorData() : RemoteControllable("mainloop") { RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); RC_ADD_PARAMETER(edi_source, "(Read-only) URL of the EDI/TCP source"); RC_ADD_PARAMETER(running_since, "(Read-only) UNIX Timestamp of most recent modulator restart"); RC_ADD_PARAMETER(ensemble_label, "(Read-only) Label of the ensemble"); RC_ADD_PARAMETER(ensemble_eid, "(Read-only) Ensemble ID"); RC_ADD_PARAMETER(ensemble_services, "(Read-only, only JSON) Ensemble service information"); RC_ADD_PARAMETER(num_services, "(Read-only) Number of services in the ensemble"); } virtual ~ModulatorData() {} virtual void set_parameter(const std::string& parameter, const std::string& value) { throw ParameterError("Parameter " + parameter + " is read-only"); } virtual const std::string get_parameter(const std::string& parameter) const { stringstream ss; if (parameter == "num_modulator_restarts") { ss << num_modulator_restarts; } else if (parameter == "running_since") { ss << running_since; } else if (parameter == "most_recent_edi_decoded") { ss << most_recent_edi_decoded; } else if (parameter == "ensemble_label") { if (ediInput) { const auto ens = ediInput->ediReader.getEnsembleInfo(); if (ens) { ss << FICDecoder::ConvertLabelToUTF8(ens->label, nullptr); } else { throw ParameterError("Not available yet"); } } else { throw ParameterError("Not available yet"); } } else if (parameter == "ensemble_eid") { if (ediInput) { const auto ens = ediInput->ediReader.getEnsembleInfo(); if (ens) { ss << ens->eid; } else { throw ParameterError("Not available yet"); } } else { throw ParameterError("Not available yet"); } } else if (parameter == "edi_source") { if (ediInput) { ss << ediInput->ediTransport.getTcpUri(); } else { throw ParameterError("Not available yet"); } } else if (parameter == "num_services") { if (ediInput) { ss << ediInput->ediReader.getSubchannels().size(); } else { throw ParameterError("Not available yet"); } } else if (parameter == "ensemble_services") { throw ParameterError("ensemble_services is only available through 'showjson'"); } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } virtual const json::map_t get_all_values() const { json::map_t map; map["num_modulator_restarts"].v = num_modulator_restarts; map["running_since"].v = running_since; map["most_recent_edi_decoded"].v = most_recent_edi_decoded; if (ediInput) { map["edi_source"].v = ediInput->ediTransport.getTcpUri(); map["num_services"].v = ediInput->ediReader.getSubchannels().size(); const auto ens = ediInput->ediReader.getEnsembleInfo(); if (ens) { map["ensemble_label"].v = FICDecoder::ConvertLabelToUTF8(ens->label, nullptr); map["ensemble_eid"].v = ens->eid; } else { map["ensemble_label"].v = nullopt; map["ensemble_eid"].v = nullopt; } std::vector services; for (const auto& s : ediInput->ediReader.getServiceInfo()) { auto service_map = make_shared(); (*service_map)["sad"].v = s.second.subchannel.start; (*service_map)["sid"].v = s.second.sid; (*service_map)["label"].v = FICDecoder::ConvertLabelToUTF8(s.second.label, nullptr); (*service_map)["bitrate"].v = s.second.subchannel.bitrate; (*service_map)["protection_level"].v = s.second.subchannel.pl; json::value_t v; v.v = service_map; services.push_back(v); } map["ensemble_services"].v = services; } return map; } size_t num_modulator_restarts = 0; time_t most_recent_edi_decoded = 0; time_t running_since = 0; }; enum class run_modulator_state_t { failure, // Corresponds to all failures normal_end, // Number of frames to modulate was reached again, // Restart the modulator part reconfigure // Some sort of change of configuration we cannot handle happened }; static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m); static shared_ptr prepare_output(mod_settings_t& s) { shared_ptr output; if (s.useFileOutput) { if (s.fftEngine != FFTEngine::FFTW) { // Intentionally ignore fileOutputFormat, it is always sc16 output = make_shared(s.outputName, s.fileOutputShowMetadata); } else if (s.fileOutputFormat == "complexf") { output = make_shared(s.outputName, s.fileOutputShowMetadata); } else if (s.fileOutputFormat == "complexf_normalised") { if (s.gainMode == GainMode::GAIN_FIX) s.normalise = 1.0f / normalise_factor_file_fix; else if (s.gainMode == GainMode::GAIN_MAX) s.normalise = 1.0f / normalise_factor_file_max; else if (s.gainMode == GainMode::GAIN_VAR) s.normalise = 1.0f / normalise_factor_file_var; output = make_shared(s.outputName, s.fileOutputShowMetadata); } else if (s.fileOutputFormat == "s16") { // We must normalise the samples to the interval [-32767.0; 32767.0] s.normalise = 32767.0f / normalise_factor; output = make_shared(s.outputName, s.fileOutputShowMetadata); } else if (s.fileOutputFormat == "s8" or s.fileOutputFormat == "u8") { // We must normalise the samples to the interval [-127.0; 127.0] // The formatconverter will add 127 for u8 so that it ends up in // [0; 255] s.normalise = 127.0f / normalise_factor; output = make_shared(s.outputName, s.fileOutputShowMetadata); } else { throw runtime_error("File output format " + s.fileOutputFormat + " not known"); } } #if defined(HAVE_OUTPUT_UHD) else if (s.useUHDOutput) { s.normalise = 1.0f / normalise_factor; s.sdr_device_config.sampleRate = s.outputRate; s.sdr_device_config.fixedPoint = (s.fftEngine != FFTEngine::FFTW); auto uhddevice = make_shared(s.sdr_device_config); output = make_shared(s.sdr_device_config, uhddevice); rcs.enrol((Output::SDR*)output.get()); } #endif #if defined(HAVE_SOAPYSDR) else if (s.useSoapyOutput) { /* We normalise the same way as for the UHD output */ s.normalise = 1.0f / normalise_factor; s.sdr_device_config.sampleRate = s.outputRate; if (s.fftEngine != FFTEngine::FFTW) throw runtime_error("soapy fixed_point unsupported"); auto soapydevice = make_shared(s.sdr_device_config); output = make_shared(s.sdr_device_config, soapydevice); rcs.enrol((Output::SDR*)output.get()); } #endif #if defined(HAVE_DEXTER) else if (s.useDexterOutput) { /* We normalise specifically range [-32768; 32767] */ s.normalise = 32767.0f / normalise_factor; s.sdr_device_config.sampleRate = s.outputRate; auto dexterdevice = make_shared(s.sdr_device_config); output = make_shared(s.sdr_device_config, dexterdevice); rcs.enrol((Output::SDR*)output.get()); } #endif #if defined(HAVE_LIMESDR) else if (s.useLimeOutput) { /* We normalise the same way as for the UHD output */ s.normalise = 1.0f / normalise_factor; if (s.fftEngine != FFTEngine::FFTW) throw runtime_error("limesdr fixed_point unsupported"); s.sdr_device_config.sampleRate = s.outputRate; auto limedevice = make_shared(s.sdr_device_config); output = make_shared(s.sdr_device_config, limedevice); rcs.enrol((Output::SDR*)output.get()); } #endif #if defined(HAVE_BLADERF) else if (s.useBladeRFOutput) { /* We normalise specifically for the BladeRF output : range [-2048; 2047] */ s.normalise = 2047.0f / normalise_factor; if (s.fftEngine != FFTEngine::FFTW) throw runtime_error("bladerf fixed_point unsupported"); s.sdr_device_config.sampleRate = s.outputRate; auto bladerfdevice = make_shared(s.sdr_device_config); output = make_shared(s.sdr_device_config, bladerfdevice); rcs.enrol((Output::SDR*)output.get()); } #endif #if defined(HAVE_ZEROMQ) else if (s.useZeroMQOutput) { /* We normalise the same way as for the UHD output */ s.normalise = 1.0f / normalise_factor; if (s.zmqOutputSocketType == "pub") { output = make_shared(s.outputName, ZMQ_PUB); } else if (s.zmqOutputSocketType == "rep") { output = make_shared(s.outputName, ZMQ_REP); } else { std::stringstream ss; ss << "ZeroMQ output socket type " << s.zmqOutputSocketType << " invalid"; throw std::invalid_argument(ss.str()); } } #endif return output; } int launch_modulator(int argc, char* argv[]) { int ret = 0; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = &signalHandler; if (sigaction(SIGINT, &sa, NULL) == -1) { const string errstr = strerror(errno); throw runtime_error("Could not set signal handler: " + errstr); } printStartupInfo(); mod_settings_t mod_settings; parse_args(argc, argv, mod_settings); #if defined(HAVE_ZEROMQ) etiLog.register_backend(make_shared()); #endif // defined(HAVE_ZEROMQ) etiLog.level(info) << "Configuration parsed. Starting up version " << #if defined(GITVERSION) GITVERSION; #else VERSION; #endif if (not (mod_settings.useFileOutput or mod_settings.useUHDOutput or mod_settings.useZeroMQOutput or mod_settings.useSoapyOutput or mod_settings.useDexterOutput or mod_settings.useLimeOutput or mod_settings.useBladeRFOutput)) { throw std::runtime_error("Configuration error: Output not specified"); } if (not mod_settings.startupCheck.empty()) { etiLog.level(info) << "Running startup check '" << mod_settings.startupCheck << "'"; int wstatus = system(mod_settings.startupCheck.c_str()); if (WIFEXITED(wstatus)) { if (WEXITSTATUS(wstatus) == 0) { etiLog.level(info) << "Startup check ok"; } else { etiLog.level(error) << "Startup check failed, returned " << WEXITSTATUS(wstatus); return 1; } } else { etiLog.level(error) << "Startup check failed, child didn't terminate normally"; return 1; } } printModSettings(mod_settings); ModulatorData m; rcs.enrol(&m); // Neither KISS FFT used for fixedpoint nor the FFT Accelerator used for DEXTER need planning. if (mod_settings.fftEngine == FFTEngine::FFTW) { // This is mostly useful on ARM systems where FFTW planning takes some time. If we do it here // it will be done before the modulator starts up etiLog.level(debug) << "Running FFTW planning..."; constexpr size_t fft_size = 2048; // Transmission Mode I. If different, it'll recalculate on OfdmGenerator // initialisation auto *fft_in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_size); auto *fft_out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_size); if (fft_in == nullptr or fft_out == nullptr) { throw std::runtime_error("FFTW malloc failed"); } fftwf_set_timelimit(2); fftwf_plan plan = fftwf_plan_dft_1d(fft_size, fft_in, fft_out, FFTW_FORWARD, FFTW_MEASURE); fftwf_destroy_plan(plan); plan = fftwf_plan_dft_1d(fft_size, fft_in, fft_out, FFTW_BACKWARD, FFTW_MEASURE); fftwf_destroy_plan(plan); fftwf_free(fft_in); fftwf_free(fft_out); etiLog.level(debug) << "FFTW planning done."; } std::string output_format; if (mod_settings.fftEngine == FFTEngine::KISS) { output_format = ""; //fixed point is native sc16, no converter needed } else if (mod_settings.fftEngine == FFTEngine::DEXTER) { output_format = "s16"; // FPGA FFT Engine outputs s32 } // else FFTW, i.e. floating point else if (mod_settings.useFileOutput and (mod_settings.fileOutputFormat == "s8" or mod_settings.fileOutputFormat == "u8" or mod_settings.fileOutputFormat == "s16")) { output_format = mod_settings.fileOutputFormat; } else if (mod_settings.useBladeRFOutput or mod_settings.useDexterOutput) { output_format = "s16"; } auto output = prepare_output(mod_settings); if (not output_format.empty()) { if (auto o = dynamic_pointer_cast(output)) { o->set_sample_size(FormatConverter::get_format_size(output_format)); } } // Set thread priority to realtime if (int r = set_realtime_prio(1)) { etiLog.level(error) << "Could not set priority for modulator:" << r; } shared_ptr inputReader; shared_ptr ediInput; if (mod_settings.inputTransport == "edi") { ediInput = make_shared(mod_settings.tist_offset_s, mod_settings.edi_max_delay_ms); ediInput->ediTransport.Open(mod_settings.inputName); if (not ediInput->ediTransport.isEnabled()) { throw runtime_error("inputTransport is edi, but ediTransport is not enabled"); } } else if (mod_settings.inputTransport == "file") { auto inputFileReader = make_shared(); // Opening ETI input file if (inputFileReader->Open(mod_settings.inputName, mod_settings.loop) == -1) { throw std::runtime_error("Unable to open input"); } inputReader = inputFileReader; } else if (mod_settings.inputTransport == "tcp") { auto inputTcpReader = make_shared(); inputTcpReader->Open(mod_settings.inputName); inputReader = inputTcpReader; } else { throw std::runtime_error("Unable to open input: " "invalid input transport " + mod_settings.inputTransport + " selected!"); } m.ediInput = ediInput; m.inputReader = inputReader; bool run_again = true; while (run_again) { m.running_since = get_clock_realtime_seconds(); Flowgraph flowgraph(mod_settings.showProcessTime); m.framecount = 0; m.flowgraph = &flowgraph; shared_ptr modulator; if (inputReader) { m.etiReader = make_shared(mod_settings.tist_offset_s); modulator = make_shared(*m.etiReader, mod_settings, output_format); } else if (ediInput) { modulator = make_shared(ediInput->ediReader, mod_settings, output_format); } rcs.enrol(modulator.get()); flowgraph.connect(modulator, output); if (inputReader) { etiLog.level(info) << inputReader->GetPrintableInfo(); } run_modulator_state_t st = run_modulator(mod_settings, m); etiLog.log(trace, "DABMOD,run_modulator() = %d", st); switch (st) { case run_modulator_state_t::failure: etiLog.level(error) << "Modulator failure."; run_again = false; ret = 1; break; case run_modulator_state_t::again: etiLog.level(warn) << "Restart modulator."; run_again = false; if (auto in = dynamic_pointer_cast(inputReader)) { if (in->Open(mod_settings.inputName, mod_settings.loop) == -1) { etiLog.level(error) << "Unable to open input file!"; ret = 1; } else { run_again = true; } } else if (dynamic_pointer_cast(inputReader)) { // Keep the same inputReader, as there is no input buffer overflow run_again = true; } else if (ediInput) { // In EDI, keep the same input run_again = true; } break; case run_modulator_state_t::reconfigure: etiLog.level(warn) << "Detected change in ensemble configuration."; /* We can keep the input in this case */ run_again = true; break; case run_modulator_state_t::normal_end: default: etiLog.level(info) << "modulator stopped."; ret = 0; run_again = false; break; } etiLog.level(info) << m.framecount << " DAB frames, " << ((float)m.framecount * 0.024f) << " seconds encoded"; m.num_modulator_restarts++; } etiLog.level(info) << "Terminating"; return ret; } static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m) { auto ret = run_modulator_state_t::failure; try { int last_eti_fct = -1; auto last_frame_received = chrono::steady_clock::now(); frame_timestamp ts; Buffer data; if (m.inputReader) { data.setLength(6144); } while (running) { unsigned fct = 0; unsigned fp = 0; /* Load ETI data from the source */ if (m.inputReader) { int framesize = m.inputReader->GetNextFrame(data.getData()); if (framesize == 0) { if (dynamic_pointer_cast(m.inputReader)) { etiLog.level(info) << "End of file reached."; running = 0; ret = run_modulator_state_t::normal_end; break; } else if (dynamic_pointer_cast(m.inputReader)) { /* An empty frame marks a timeout. We ignore it, but we are * now able to handle SIGINT properly. */ } else { throw logic_error("Unhandled framesize==0!"); } continue; } else if (framesize < 0) { etiLog.level(error) << "Input read error."; running = 0; ret = run_modulator_state_t::normal_end; break; } const int eti_bytes_read = m.etiReader->loadEtiData(data); if ((size_t)eti_bytes_read != data.getLength()) { etiLog.level(error) << "ETI frame incompletely read"; throw std::runtime_error("ETI read error"); } last_frame_received = chrono::steady_clock::now(); fct = m.etiReader->getFct(); fp = m.etiReader->getFp(); ts = m.etiReader->getTimestamp(); } else if (m.ediInput) { while (running and not m.ediInput->ediReader.isFrameReady()) { try { bool packet_received = m.ediInput->ediTransport.rxPacket(); if (packet_received) { last_frame_received = chrono::steady_clock::now(); } } catch (const std::runtime_error& e) { etiLog.level(warn) << "EDI input: " << e.what(); running = 0; break; } } if (!running) { break; } m.most_recent_edi_decoded = get_clock_realtime_seconds(); fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); ts = m.ediInput->ediReader.getTimestamp(); } // timestamp is good if we run unsynchronised, or if margin is sufficient bool ts_good = not mod_settings.sdr_device_config.enableSync or (ts.timestamp_valid and ts.offset_to_system_time() > 0.2); if (!ts_good) { etiLog.level(warn) << "Modulator skipping frame " << fct << " TS " << (ts.timestamp_valid ? "valid" : "invalid") << " offset " << (ts.timestamp_valid ? ts.offset_to_system_time() : 0); } else { bool modulate = true; if (last_eti_fct == -1) { if (fp != 0) { // Do not start the flowgraph before we get to FP 0 // to ensure all blocks are properly aligned. modulate = false; } else { last_eti_fct = fct; } } else { const unsigned expected_fct = (last_eti_fct + 1) % 250; if (fct == expected_fct) { last_eti_fct = fct; } else { etiLog.level(warn) << "ETI FCT discontinuity, expected " << expected_fct << " received " << fct; if (m.ediInput) { m.ediInput->ediReader.clearFrame(); } return run_modulator_state_t::again; } } if (modulate) { m.framecount++; m.flowgraph->run(); } } if (m.ediInput) { m.ediInput->ediReader.clearFrame(); } /* Check every once in a while if the remote control * is still working */ if ((m.framecount % 250) == 0) { rcs.check_faults(); } } } catch (const FrameMultiplexerError& e) { // The FrameMultiplexer saw an error or a change in the size of a // subchannel. This can be due to a multiplex reconfiguration. etiLog.level(warn) << e.what(); ret = run_modulator_state_t::reconfigure; } catch (const std::exception& e) { etiLog.level(error) << "Exception caught: " << e.what(); ret = run_modulator_state_t::failure; } return ret; } int main(int argc, char* argv[]) { // Set timezone to UTC setenv("TZ", "", 1); tzset(); // Version handling is done very early to ensure nothing else but the version gets printed out if (argc == 2 and strcmp(argv[1], "--version") == 0) { fprintf(stdout, "%s\n", #if defined(GITVERSION) GITVERSION #else PACKAGE_VERSION #endif ); return 0; } try { return launch_modulator(argc, argv); } catch (const std::invalid_argument& e) { std::string what(e.what()); if (not what.empty()) { std::cerr << "Modulator error: " << what << std::endl; } } catch (const std::runtime_error& e) { std::cerr << "Modulator runtime error: " << e.what() << std::endl; } return 1; } Opendigitalradio-ODR-DabMod-f7eedef/src/DabModulator.cpp000066400000000000000000000405711475762153200233000ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #include #include #include "DabModulator.h" #include "PcDebug.h" #include "BlockPartitioner.h" #include "CicEqualizer.h" #include "ConvEncoder.h" #include "DifferentialModulator.h" #include "FIRFilter.h" #include "FrameMultiplexer.h" #include "FrequencyInterleaver.h" #include "GainControl.h" #include "GuardIntervalInserter.h" #include "Log.h" #include "MemlessPoly.h" #include "NullSymbol.h" #include "OfdmGenerator.h" #include "PhaseReference.h" #include "PrbsGenerator.h" #include "PuncturingEncoder.h" #include "QpskSymbolMapper.h" #include "RemoteControl.h" #include "Resampler.h" #include "SignalMultiplexer.h" #include "TII.h" #include "TimeInterleaver.h" using namespace std; DabModulator::DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format) : ModInput(), RemoteControllable("modulator"), m_settings(settings), m_format(format), m_etiSource(etiSource), m_flowgraph() { PDEBUG("DabModulator::DabModulator() @ %p\n", this); RC_ADD_PARAMETER(rate, "(Read-only) IQ output samplerate"); RC_ADD_PARAMETER(num_clipped_samples, "(Read-only) Number of samples clipped in last frame during format conversion"); if (m_settings.dabMode == 0) { setMode(1); } else { setMode(m_settings.dabMode); } } void DabModulator::setMode(unsigned mode) { switch (mode) { case 1: m_nbSymbols = 76; m_nbCarriers = 1536; m_spacing = 2048; m_nullSize = 2656; m_symSize = 2552; m_ficSizeOut = 288; break; case 2: m_nbSymbols = 76; m_nbCarriers = 384; m_spacing = 512; m_nullSize = 664; m_symSize = 638; m_ficSizeOut = 288; break; case 3: m_nbSymbols = 153; m_nbCarriers = 192; m_spacing = 256; m_nullSize = 345; m_symSize = 319; m_ficSizeOut = 384; break; case 4: m_nbSymbols = 76; m_nbCarriers = 768; m_spacing = 1024; m_nullSize = 1328; m_symSize = 1276; m_ficSizeOut = 288; break; default: throw std::runtime_error("DabModulator::setMode invalid mode size"); } } int DabModulator::process(Buffer* dataOut) { using namespace std; PDEBUG("DabModulator::process(dataOut: %p)\n", dataOut); if (not m_flowgraph) { etiLog.level(debug) << "Setting up DabModulator..."; const unsigned mode = m_settings.dabMode; setMode(mode); m_flowgraph = make_shared(m_settings.showProcessTime); //////////////////////////////////////////////////////////////// // CIF data initialisation //////////////////////////////////////////////////////////////// auto cifPrbs = make_shared(864 * 8, 0x110); auto cifMux = make_shared(m_etiSource); auto cifPart = make_shared(mode); const bool fixedPoint = m_settings.fftEngine != FFTEngine::FFTW; auto cifMap = make_shared(m_nbCarriers, fixedPoint); auto cifRef = make_shared(mode, fixedPoint); auto cifFreq = make_shared(mode, fixedPoint); auto cifDiff = make_shared(m_nbCarriers, fixedPoint); auto cifNull = make_shared(m_nbCarriers, fixedPoint ? sizeof(complexfix) : sizeof(complexf)); auto cifSig = make_shared(); // TODO this needs a review bool useCicEq = false; unsigned cic_ratio = 1; if (m_settings.clockRate) { cic_ratio = m_settings.clockRate / m_settings.outputRate; cic_ratio /= 4; // FPGA DUC if (m_settings.clockRate == 400000000) { // USRP2 if (cic_ratio & 1) { // odd useCicEq = true; } // even, no filter } else { useCicEq = true; } } shared_ptr cifCicEq; if (useCicEq) { cifCicEq = make_shared( m_nbCarriers, (float)m_spacing * (float)m_settings.outputRate / 2048000.0f, cic_ratio); } shared_ptr tii; shared_ptr tiiRef; try { tii = make_shared( m_settings.dabMode, m_settings.tiiConfig, fixedPoint); rcs.enrol(tii.get()); tiiRef = make_shared(mode, fixedPoint); } catch (const TIIError& e) { etiLog.level(error) << "Could not initialise TII: " << e.what(); } shared_ptr cifOfdm; switch (m_settings.fftEngine) { case FFTEngine::FFTW: { auto ofdm = make_shared( (1 + m_nbSymbols), m_nbCarriers, m_spacing, m_settings.enableCfr, m_settings.cfrClip, m_settings.cfrErrorClip); rcs.enrol(ofdm.get()); cifOfdm = ofdm; } break; case FFTEngine::KISS: cifOfdm = make_shared( (1 + m_nbSymbols), m_nbCarriers, m_spacing); break; case FFTEngine::DEXTER: #if defined(HAVE_DEXTER) cifOfdm = make_shared( (1 + m_nbSymbols), m_nbCarriers, m_spacing); #else throw std::runtime_error("Cannot use DEXTER fft engine without --enable-dexter"); #endif break; } shared_ptr cifGain; if (not fixedPoint) { cifGain = make_shared( m_spacing, m_settings.gainMode, m_settings.digitalgain, m_settings.normalise, m_settings.gainmodeVariance); rcs.enrol(cifGain.get()); } auto cifGuard = make_shared( m_nbSymbols, m_spacing, m_nullSize, m_symSize, m_settings.ofdmWindowOverlap, m_settings.fftEngine); rcs.enrol(cifGuard.get()); shared_ptr cifFilter; if (not m_settings.filterTapsFilename.empty()) { if (fixedPoint) throw std::runtime_error("fixed point doesn't support fir filter"); cifFilter = make_shared(m_settings.filterTapsFilename); rcs.enrol(cifFilter.get()); } shared_ptr cifPoly; if (not m_settings.polyCoefFilename.empty()) { if (fixedPoint) throw std::runtime_error("fixed point doesn't support predistortion"); cifPoly = make_shared(m_settings.polyCoefFilename, m_settings.polyNumThreads); rcs.enrol(cifPoly.get()); } shared_ptr cifRes; if (m_settings.outputRate != 2048000) { if (fixedPoint) throw std::runtime_error("fixed point doesn't support resampler"); cifRes = make_shared( 2048000, m_settings.outputRate, m_spacing); } if (m_settings.fftEngine == FFTEngine::FFTW and not m_format.empty()) { m_formatConverter = make_shared(false, m_format); } else if (m_settings.fftEngine == FFTEngine::DEXTER) { m_formatConverter = make_shared(true, m_format); } // KISS is already in s16 m_output = make_shared(dataOut); m_flowgraph->connect(cifPrbs, cifMux); //////////////////////////////////////////////////////////////// // Processing FIC //////////////////////////////////////////////////////////////// shared_ptr fic(m_etiSource.getFic()); //////////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////////// size_t ficSizeIn = fic->getFramesize(); //////////////////////////////////////////////////////////////// // Modules configuration //////////////////////////////////////////////////////////////// // Configuring FIC channel PDEBUG("FIC:\n"); PDEBUG(" Framesize: %zu\n", fic->getFramesize()); // Configuring prbs generator auto ficPrbs = make_shared(ficSizeIn, 0x110); // Configuring convolutionnal encoder auto ficConv = make_shared(ficSizeIn); // Configuring puncturing encoder auto ficPunc = make_shared(); for (const auto &rule : fic->get_rules()) { PDEBUG(" Adding rule:\n"); PDEBUG(" Length: %zu\n", rule.length()); PDEBUG(" Pattern: 0x%x\n", rule.pattern()); ficPunc->append_rule(rule); } PDEBUG(" Adding tail\n"); ficPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); m_flowgraph->connect(fic, ficPrbs); m_flowgraph->connect(ficPrbs, ficConv); m_flowgraph->connect(ficConv, ficPunc); m_flowgraph->connect(ficPunc, cifPart); //////////////////////////////////////////////////////////////// // Configuring subchannels //////////////////////////////////////////////////////////////// for (const auto& subchannel : m_etiSource.getSubchannels()) { //////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////// size_t subchSizeIn = subchannel->framesize(); size_t subchSizeOut = subchannel->framesizeCu() * 8; //////////////////////////////////////////////////////////// // Modules configuration //////////////////////////////////////////////////////////// // Configuring subchannel PDEBUG("Subchannel:\n"); PDEBUG(" Start address: %zu\n", subchannel->startAddress()); PDEBUG(" Framesize: %zu\n", subchannel->framesize()); PDEBUG(" Bitrate: %zu\n", subchannel->bitrate()); PDEBUG(" Framesize CU: %zu\n", subchannel->framesizeCu()); PDEBUG(" Protection: %zu\n", subchannel->protection()); PDEBUG(" Form: %zu\n", subchannel->protectionForm()); PDEBUG(" Level: %zu\n", subchannel->protectionLevel()); PDEBUG(" Option: %zu\n", subchannel->protectionOption()); // Configuring prbs genrerator auto subchPrbs = make_shared(subchSizeIn, 0x110); // Configuring convolutionnal encoder auto subchConv = make_shared(subchSizeIn); // Configuring puncturing encoder auto subchPunc = make_shared(subchannel->framesizeCu()); for (const auto& rule : subchannel->get_rules()) { PDEBUG(" Adding rule:\n"); PDEBUG(" Length: %zu\n", rule.length()); PDEBUG(" Pattern: 0x%x\n", rule.pattern()); subchPunc->append_rule(rule); } PDEBUG(" Adding tail\n"); subchPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); // Configuring time interleaver auto subchInterleaver = make_shared(subchSizeOut); m_flowgraph->connect(subchannel, subchPrbs); m_flowgraph->connect(subchPrbs, subchConv); m_flowgraph->connect(subchConv, subchPunc); m_flowgraph->connect(subchPunc, subchInterleaver); m_flowgraph->connect(subchInterleaver, cifMux); } m_flowgraph->connect(cifMux, cifPart); m_flowgraph->connect(cifPart, cifMap); m_flowgraph->connect(cifMap, cifFreq); m_flowgraph->connect(cifRef, cifDiff); m_flowgraph->connect(cifFreq, cifDiff); m_flowgraph->connect(cifNull, cifSig); m_flowgraph->connect(cifDiff, cifSig); if (tii) { m_flowgraph->connect(tiiRef, tii); m_flowgraph->connect(tii, cifSig); } shared_ptr prev_plugin = static_pointer_cast(cifSig); const std::vector > plugins({ static_pointer_cast(cifCicEq), static_pointer_cast(cifOfdm), static_pointer_cast(cifGain), static_pointer_cast(cifGuard), // optional blocks static_pointer_cast(cifFilter), static_pointer_cast(cifRes), static_pointer_cast(cifPoly), static_pointer_cast(m_formatConverter), // mandatory block static_pointer_cast(m_output), }); for (auto& p : plugins) { if (p) { m_flowgraph->connect(prev_plugin, p); prev_plugin = p; } } etiLog.level(debug) << "DabModulator set up."; } //////////////////////////////////////////////////////////////////// // Processing data //////////////////////////////////////////////////////////////////// return m_flowgraph->run(); } meta_vec_t DabModulator::process_metadata(const meta_vec_t& metadataIn) { if (m_output) { return m_output->get_latest_metadata(); } return {}; } void DabModulator::set_parameter(const string& parameter, const string& value) { if (parameter == "rate") { throw ParameterError("Parameter 'rate' is read-only"); } else if (parameter == "num_clipped_samples") { throw ParameterError("Parameter 'num_clipped_samples' is read-only"); } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string DabModulator::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "rate") { ss << m_settings.outputRate; } else if (parameter == "num_clipped_samples") { if (m_formatConverter) { ss << m_formatConverter->get_num_clipped_samples(); } else { ss << "Parameter '" << parameter << "' is not available when no format conversion is done."; throw ParameterError(ss.str()); } } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t DabModulator::get_all_values() const { json::map_t map; map["rate"].v = m_settings.outputRate; map["num_clipped_samples"].v = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/DabModulator.h000066400000000000000000000047321475762153200227440ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "ModPlugin.h" #include "ConfigParser.h" #include "EtiReader.h" #include "Flowgraph.h" #include "FormatConverter.h" #include "OutputMemory.h" #include "RemoteControl.h" class DabModulator : public ModInput, public ModMetadata, public RemoteControllable { public: DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format); // Allowed formats: s8, u8 and s16. Empty string means no conversion virtual ~DabModulator() {} int process(Buffer* dataOut) override; const char* name() override { return "DabModulator"; } virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; /* Required to get the timestamp */ EtiSource* getEtiSource() { return &m_etiSource; } /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; protected: void setMode(unsigned mode); mod_settings_t& m_settings; std::string m_format; EtiSource& m_etiSource; std::shared_ptr m_flowgraph; size_t m_nbSymbols; size_t m_nbCarriers; size_t m_spacing; size_t m_nullSize; size_t m_symSize; size_t m_ficSizeOut; std::shared_ptr m_formatConverter; std::shared_ptr m_output; }; Opendigitalradio-ODR-DabMod-f7eedef/src/DifferentialModulator.cpp000066400000000000000000000066071475762153200252100ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "DifferentialModulator.h" #include "PcDebug.h" #include #include #include DifferentialModulator::DifferentialModulator(size_t carriers, bool fixedPoint) : ModMux(), m_carriers(carriers), m_fixedPoint(fixedPoint) { PDEBUG("DifferentialModulator::DifferentialModulator(%zu)\n", carriers); } DifferentialModulator::~DifferentialModulator() { PDEBUG("DifferentialModulator::~DifferentialModulator()\n"); } template void do_process(size_t carriers, const std::vector& dataIn, Buffer* dataOut) { size_t phaseSize = dataIn[0]->getLength() / sizeof(T); size_t dataSize = dataIn[1]->getLength() / sizeof(T); dataOut->setLength((phaseSize + dataSize) * sizeof(T)); const T* phase = reinterpret_cast(dataIn[0]->getData()); const T* in = reinterpret_cast(dataIn[1]->getData()); T* out = reinterpret_cast(dataOut->getData()); if (phaseSize != carriers) { throw std::runtime_error( "DifferentialModulator::process input phase size not valid!"); } if (dataSize % carriers != 0) { throw std::runtime_error( "DifferentialModulator::process input data size not valid!"); } memcpy(dataOut->getData(), phase, phaseSize * sizeof(T)); for (size_t i = 0; i < dataSize; i += carriers) { for (size_t j = 0; j < carriers; j += 4) { out[carriers + j] = out[j] * in[j]; out[carriers + j + 1] = out[j + 1] * in[j + 1]; out[carriers + j + 2] = out[j + 2] * in[j + 2]; out[carriers + j + 3] = out[j + 3] * in[j + 3]; } in += carriers; out += carriers; } } // dataIn[0] -> phase reference // dataIn[1] -> data symbols int DifferentialModulator::process(std::vector dataIn, Buffer* dataOut) { #ifdef TRACE fprintf(stderr, "DifferentialModulator::process (dataIn:"); for (size_t i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %p", dataIn[i]); } fprintf(stderr, ", sizeIn: "); for (size_t i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %zu", dataIn[i]->getLength()); } fprintf(stderr, ", dataOut: %p, sizeOut: %zu)\n", dataOut, dataOut->getLength()); #endif if (dataIn.size() != 2) { throw std::runtime_error( "DifferentialModulator::process nb of input streams not 2!"); } if (m_fixedPoint) { do_process(m_carriers, dataIn, dataOut); } else { do_process(m_carriers, dataIn, dataOut); } return dataOut->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/DifferentialModulator.h000066400000000000000000000026261475762153200246520ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include class DifferentialModulator : public ModMux { public: DifferentialModulator(size_t carriers, bool fixedPoint); virtual ~DifferentialModulator(); DifferentialModulator(const DifferentialModulator&); DifferentialModulator& operator=(const DifferentialModulator&); int process(std::vector dataIn, Buffer* dataOut); const char* name() { return "DifferentialModulator"; } protected: size_t m_carriers; size_t m_fixedPoint; }; Opendigitalradio-ODR-DabMod-f7eedef/src/Eti.cpp000066400000000000000000000031661475762153200214430ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifdef _WIN32 # pragma warning ( disable : 4103 ) # include "Eti.h" # pragma warning ( default : 4103 ) #else # include "Eti.h" #endif //definitions des structures des champs du ETI(NI, G703) uint16_t eti_FC::getFrameLength() { return (uint16_t)((FL_high << 8) | FL_low); } void eti_FC::setFrameLength(uint16_t length) { FL_high = (length >> 8) & 0x07; FL_low = length & 0xff; } void eti_STC::setSTL(uint16_t length) { STL_high = length >> 8; STL_low = length & 0xff; } uint16_t eti_STC::getSTL() { return (uint16_t)((STL_high << 8) + STL_low); } void eti_STC::setStartAddress(uint16_t address) { startAddress_high = address >> 8; startAddress_low = address & 0xff; } uint16_t eti_STC::getStartAddress() { return (uint16_t)((startAddress_high << 8) + startAddress_low); } Opendigitalradio-ODR-DabMod-f7eedef/src/Eti.h000066400000000000000000000054151475762153200211070ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Includes modifications for which no copyright is claimed 2012, Matthias P. Braendli, matthias.braendli@mpb.li */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifndef ETI_H #define ETI_H #ifdef HAVE_CONFIG_H # include #endif #ifdef _WIN32 # include // For types... typedef WORD uint16_t; typedef DWORD32 uint32_t; # define PACKED # pragma pack(push, 1) #else # include # define PACKED __attribute__ ((packed)) #endif #include //definitions des structures des champs du ETI(NI, G703) struct eti_SYNC { uint32_t ERR:8; uint32_t FSYNC:24; } PACKED; struct eti_FC { uint32_t FCT:8; uint32_t NST:7; uint32_t FICF:1; uint32_t FL_high:3; uint32_t MID:2; uint32_t FP:3; uint32_t FL_low:8; uint16_t getFrameLength(); void setFrameLength(uint16_t length); } PACKED; struct eti_STC { uint32_t startAddress_high:2; uint32_t SCID:6; uint32_t startAddress_low:8; uint32_t STL_high:2; uint32_t TPL:6; uint32_t STL_low:8; void setSTL(uint16_t length); uint16_t getSTL(); void setStartAddress(uint16_t address); uint16_t getStartAddress(); } PACKED; struct eti_EOH { uint16_t MNSC; uint16_t CRC; } PACKED; struct eti_EOF { uint16_t CRC; uint16_t RFU; } PACKED; struct eti_TIST { uint32_t TIST; } PACKED; struct eti_MNSC_TIME_0 { uint32_t type:4; uint32_t identifier:4; uint32_t rfa:8; } PACKED; struct eti_MNSC_TIME_1 { uint32_t second_unit:4; uint32_t second_tens:3; uint32_t accuracy:1; uint32_t minute_unit:4; uint32_t minute_tens:3; uint32_t sync_to_frame:1; } PACKED; struct eti_MNSC_TIME_2 { uint32_t hour_unit:4; uint32_t hour_tens:4; uint32_t day_unit:4; uint32_t day_tens:4; } PACKED; struct eti_MNSC_TIME_3 { uint32_t month_unit:4; uint32_t month_tens:4; uint32_t year_unit:4; uint32_t year_tens:4; } PACKED; struct eti_extension_TIME { uint32_t TIME_SECONDS; } PACKED; #endif // ETI_H Opendigitalradio-ODR-DabMod-f7eedef/src/EtiReader.cpp000066400000000000000000000511661475762153200225710ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "EtiReader.h" #include "Log.h" #include "PcDebug.h" #include "TimestampDecoder.h" #include "edi/common.hpp" #include #include #include #include #include #include using namespace std; EtiReader::EtiReader( double& tist_offset_s) : myTimestampDecoder(tist_offset_s), eti_fc_valid(false) { rcs.enrol(&myTimestampDecoder); } std::shared_ptr& EtiSource::getFic() { return myFicSource; } unsigned EtiReader::getMode() { if (not eti_fc_valid) { throw std::runtime_error("Trying to access Mode before it is ready!"); } return eti_fc.MID; } unsigned EtiReader::getFp() { if (not eti_fc_valid) { throw std::runtime_error("Trying to access FP before it is ready!"); } return eti_fc.FP; } unsigned EtiReader::getFct() { if (not eti_fc_valid) { throw std::runtime_error("Trying to access FCT before it is ready!"); } return eti_fc.FCT; } frame_timestamp EtiReader::getTimestamp() { return myTimestampDecoder.getTimestamp(); } const std::vector > EtiReader::getSubchannels() const { return mySources; } int EtiReader::loadEtiData(const Buffer& dataIn) { PDEBUG("EtiReader::loadEtiData(dataIn: %p)\n", &dataIn); PDEBUG(" state: %u\n", state); const unsigned char* in = reinterpret_cast(dataIn.getData()); size_t input_size = dataIn.getLength(); while (input_size > 0) { switch (state) { case EtiReaderState::NbFrame: if (input_size < 4) { return dataIn.getLength() - input_size; } nb_frames = *(uint32_t*)in; input_size -= 4; in += 4; state = EtiReaderState::FrameSize; PDEBUG("Nb frames: %i\n", nb_frames); break; case EtiReaderState::FrameSize: if (input_size < 2) { return dataIn.getLength() - input_size; } framesize = *(uint16_t*)in; input_size -= 2; in += 2; state = EtiReaderState::Sync; PDEBUG("Framesize: %i\n", framesize); break; case EtiReaderState::Sync: if (input_size < 4) { return dataIn.getLength() - input_size; } framesize = 6144; memcpy(&eti_sync, in, 4); input_size -= 4; framesize -= 4; in += 4; state = EtiReaderState::Fc; PDEBUG("Sync.err: 0x%.2x\n", eti_sync.ERR); PDEBUG("Sync.fsync: 0x%.6x\n", eti_sync.FSYNC); break; case EtiReaderState::Fc: if (input_size < 4) { return dataIn.getLength() - input_size; } memcpy(&eti_fc, in, 4); eti_fc_valid = true; input_size -= 4; framesize -= 4; in += 4; state = EtiReaderState::Nst; PDEBUG("Fc.fct: 0x%.2x\n", eti_fc.FCT); PDEBUG("Fc.ficf: %u\n", eti_fc.FICF); PDEBUG("Fc.nst: %u\n", eti_fc.NST); PDEBUG("Fc.fp: 0x%x\n", eti_fc.FP); PDEBUG("Fc.mid: %u\n", eti_fc.MID); PDEBUG("Fc.fl: %u\n", eti_fc.getFrameLength()); if (!eti_fc.FICF) { throw std::runtime_error("FIC must be present to modulate!"); } if (not myFicSource) { unsigned ficf = eti_fc.FICF; unsigned mid = eti_fc.MID; myFicSource = make_shared(ficf, mid); } break; case EtiReaderState::Nst: if (input_size < 4 * (size_t)eti_fc.NST) { return dataIn.getLength() - input_size; } if ((eti_stc.size() != eti_fc.NST) || (memcmp(&eti_stc[0], in, 4 * eti_fc.NST))) { PDEBUG("New stc!\n"); eti_stc.resize(eti_fc.NST); memcpy(&eti_stc[0], in, 4 * eti_fc.NST); mySources.clear(); for (unsigned i = 0; i < eti_fc.NST; ++i) { const auto tpl = eti_stc[i].TPL; mySources.push_back( make_shared( eti_stc[i].getStartAddress(), eti_stc[i].getSTL(), tpl)); PDEBUG("Sstc %u:\n", i); PDEBUG(" Stc%i.scid: %i\n", i, eti_stc[i].SCID); PDEBUG(" Stc%i.sad: %u\n", i, eti_stc[i].getStartAddress()); PDEBUG(" Stc%i.tpl: 0x%.2x\n", i, eti_stc[i].TPL); PDEBUG(" Stc%i.stl: %u\n", i, eti_stc[i].getSTL()); } } input_size -= 4 * eti_fc.NST; framesize -= 4 * eti_fc.NST; in += 4 * eti_fc.NST; state = EtiReaderState::Eoh; break; case EtiReaderState::Eoh: if (input_size < 4) { return dataIn.getLength() - input_size; } memcpy(&eti_eoh, in, 4); input_size -= 4; framesize -= 4; in += 4; state = EtiReaderState::Fic; PDEBUG("Eoh.mnsc: 0x%.4x\n", eti_eoh.MNSC); PDEBUG("Eoh.crc: 0x%.4x\n", eti_eoh.CRC); break; case EtiReaderState::Fic: if (eti_fc.MID == 3) { if (input_size < 128) { return dataIn.getLength() - input_size; } PDEBUG("Writing 128 bytes of FIC channel data\n"); Buffer fic(128, in); myFicSource->loadFicData(fic); input_size -= 128; framesize -= 128; in += 128; } else { if (input_size < 96) { return dataIn.getLength() - input_size; } PDEBUG("Writing 96 bytes of FIC channel data\n"); Buffer fic(96, in); myFicSource->loadFicData(fic); input_size -= 96; framesize -= 96; in += 96; } state = EtiReaderState::Subch; break; case EtiReaderState::Subch: for (size_t i = 0; i < eti_stc.size(); ++i) { unsigned size = mySources[i]->framesize(); PDEBUG("Writting %i bytes of subchannel data\n", size); Buffer subch(size, in); mySources[i]->loadSubchannelData(std::move(subch)); input_size -= size; framesize -= size; in += size; } state = EtiReaderState::Eof; break; case EtiReaderState::Eof: if (input_size < 4) { return dataIn.getLength() - input_size; } memcpy(&eti_eof, in, 4); input_size -= 4; framesize -= 4; in += 4; state = EtiReaderState::Tist; PDEBUG("Eof.crc: %#.4x\n", eti_eof.CRC); PDEBUG("Eof.rfu: %#.4x\n", eti_eof.RFU); break; case EtiReaderState::Tist: if (input_size < 4) { return dataIn.getLength() - input_size; } memcpy(&eti_tist, in, 4); input_size -= 4; framesize -= 4; in += 4; state = EtiReaderState::Pad; PDEBUG("Tist: 0x%.6x\n", eti_tist.TIST); break; case EtiReaderState::Pad: if (framesize > 0) { --input_size; --framesize; ++in; } else { state = EtiReaderState::Sync; } break; default: // throw std::runtime_error("Invalid state!"); PDEBUG("Invalid state (%i)!", state); input_size = 0; } } // Update timestamps myTimestampDecoder.updateTimestampEti(eti_fc.FP & 0x3, eti_eoh.MNSC, getPPSOffset(), eti_fc.FCT); myFicSource->loadTimestamp(myTimestampDecoder.getTimestamp()); return dataIn.getLength() - input_size; } uint32_t EtiReader::getPPSOffset() { const uint32_t timestamp = ntohl(eti_tist.TIST) & 0xFFFFFF; /* See ETS 300 799, Annex C.2.2 */ if (timestamp == 0xFFFFFF) { return 0.0; } return timestamp; } EdiReader::EdiReader(double& tist_offset_s) : m_timestamp_decoder(tist_offset_s), m_fic_decoder(/*verbose*/ false) { rcs.enrol(&m_timestamp_decoder); } unsigned EdiReader::getMode() { if (not m_fc_valid) { throw std::runtime_error("Trying to access Mode before it is ready!"); } return m_fc.mid; } unsigned EdiReader::getFp() { if (not m_fc_valid) { throw std::runtime_error("Trying to access FP before it is ready!"); } return m_fc.fp; } unsigned EdiReader::getFct() { if (not m_fc_valid) { throw std::runtime_error("Trying to access FCT before it is ready!"); } return m_fc.fct(); } frame_timestamp EdiReader::getTimestamp() { return m_timestamp_decoder.getTimestamp(); } const std::vector > EdiReader::getSubchannels() const { std::vector > sources; sources.resize(m_sources.size()); for (const auto& s : m_sources) { if (s.first < sources.size()) { sources.at(s.first) = s.second; } else { throw std::runtime_error("Missing subchannel data in EDI source"); } } return sources; } bool EdiReader::isFrameReady() { return m_frameReady; } void EdiReader::clearFrame() { m_frameReady = false; m_proto_valid = false; m_fc_valid = false; m_fic.clear(); } void EdiReader::update_protocol( const std::string& proto, uint16_t major, uint16_t minor) { m_proto_valid = (proto == "DETI" and major == 0 and minor == 0); if (not m_proto_valid) { throw std::invalid_argument("Wrong EDI protocol"); } } void EdiReader::update_err(uint8_t err) { if (not m_proto_valid) { throw std::logic_error("Cannot update ERR before protocol"); } m_err = err; } void EdiReader::update_fc_data(const EdiDecoder::eti_fc_data& fc_data) { if (not m_proto_valid) { throw std::logic_error("Cannot update FC before protocol"); } m_fc_valid = false; m_fc = fc_data; if (not m_fc.ficf) { throw std::invalid_argument("FIC must be present"); } if (m_fc.mid > 4) { throw std::invalid_argument("Invalid MID"); } if (m_fc.fp > 7) { throw std::invalid_argument("Invalid FP"); } m_fc_valid = true; } void EdiReader::update_fic(std::vector&& fic) { if (not m_proto_valid) { throw std::logic_error("Cannot update FIC before protocol"); } m_fic_decoder.Process(fic.data(), fic.size()); m_fic = std::move(fic); } void EdiReader::update_edi_time( uint32_t utco, uint32_t seconds) { if (not m_proto_valid) { throw std::logic_error("Cannot update time before protocol"); } m_utco = utco; m_seconds = seconds; // TODO check validity m_time_valid = true; } void EdiReader::update_mnsc(uint16_t mnsc) { if (not m_proto_valid) { throw std::logic_error("Cannot update MNSC before protocol"); } m_mnsc = mnsc; } void EdiReader::update_rfu(uint16_t rfu) { if (not m_proto_valid) { throw std::logic_error("Cannot update RFU before protocol"); } m_rfu = rfu; } void EdiReader::add_subchannel(EdiDecoder::eti_stc_data&& stc) { if (not m_proto_valid) { throw std::logic_error("Cannot add subchannel before protocol"); } if (m_sources.count(stc.stream_index) == 0) { m_sources[stc.stream_index] = make_shared(stc.sad, stc.stl(), stc.tpl); } auto& source = m_sources[stc.stream_index]; if (source->framesize() != stc.mst.size()) { throw std::invalid_argument( "EDI: MST data length inconsistent with FIC"); } source->loadSubchannelData(std::move(stc.mst)); if (m_sources.size() > 64) { throw std::invalid_argument("Too many subchannels"); } } void EdiReader::assemble(EdiDecoder::ReceivedTagPacket&& tagpacket) { if (not m_proto_valid) { throw std::logic_error("Cannot assemble EDI data before protocol"); } if (not m_fc_valid) { throw std::logic_error("Cannot assemble EDI data without FC"); } if (m_fic.empty()) { throw std::logic_error("Cannot assemble EDI data without FIC"); } // ETS 300 799 Clause 5.3.2, but we don't support not having // a FIC if ( (m_fc.mid == 3 and m_fic.size() != 32 * 4) or (m_fc.mid != 3 and m_fic.size() != 24 * 4) ) { stringstream ss; ss << "Invalid FIC length " << m_fic.size() << " for MID " << m_fc.mid; throw std::invalid_argument(ss.str()); } if (not myFicSource) { myFicSource = make_shared(m_fc.ficf, m_fc.mid); } myFicSource->loadFicData(m_fic); // Accept zero subchannels, because of an edge-case that can happen // during reconfiguration. See ETS 300 799 Clause 5.3.3 if (m_utco == 0 and m_seconds == 0) { // We don't support relative-only timestamps m_fc.tsta = 0xFFFFFF; // disable TSTA } /* According to Annex F * EDI = UTC + UTCO * We need UTC = EDI - UTCO * * The seconds value is given in number of seconds since * 1.1.2000 */ const std::time_t posix_timestamp_1_jan_2000 = 946684800; auto utc_ts = posix_timestamp_1_jan_2000 + m_seconds - m_utco; m_timestamp_decoder.updateTimestampEdi(utc_ts, m_fc.tsta, m_fc.fct(), m_fc.fp); myFicSource->loadTimestamp(m_timestamp_decoder.getTimestamp()); m_frameReady = true; } EdiTransport::EdiTransport(EdiDecoder::ETIDecoder& decoder) : m_enabled(false), m_port(0), m_bindto("0.0.0.0"), m_mcastaddr("0.0.0.0"), m_decoder(decoder) { } void EdiTransport::Open(const std::string& uri) { etiLog.level(info) << "Opening EDI :" << uri; const string proto = uri.substr(0, 6); if (proto == "udp://") { if (m_proto == Proto::TCP) { throw std::invalid_argument("Cannot specify both TCP and UDP urls"); } size_t found_port = uri.find_first_of(":", 6); if (found_port == string::npos) { throw std::invalid_argument("EDI UDP input port must be provided"); } m_port = std::stoi(uri.substr(found_port+1)); std::string host_full = uri.substr(6, found_port-6);// skip udp:// size_t found_mcast = host_full.find_first_of("@"); //have multicast address: if (found_mcast != string::npos) { if (found_mcast > 0) { m_bindto = host_full.substr(0, found_mcast); } m_mcastaddr = host_full.substr(found_mcast+1); } else if (found_port != 6) { m_bindto = host_full; } etiLog.level(info) << "EDI UDP input: host:" << m_bindto << ", source:" << m_mcastaddr << ", port:" << m_port; m_udp_rx.add_receive_port(m_port, m_bindto, m_mcastaddr); m_proto = Proto::UDP; m_enabled = true; } else if (proto == "tcp://") { if (m_proto != Proto::Unspecified) { throw std::invalid_argument("Cannot call Open several times with TCP"); } size_t found_port = uri.find_first_of(":", 6); if (found_port == string::npos) { throw std::invalid_argument("EDI TCP input port must be provided"); } m_port = std::stoi(uri.substr(found_port+1)); const std::string hostname = uri.substr(6, found_port-6); etiLog.level(info) << "EDI TCP connect to " << hostname << ":" << m_port; m_tcp_uri = uri; m_tcpclient.connect(hostname, m_port); m_proto = Proto::TCP; m_enabled = true; } else { throw std::invalid_argument("ETI protocol '" + proto + "' unknown"); } } bool EdiTransport::rxPacket() { switch (m_proto) { case Proto::Unspecified: { etiLog.level(warn) << "EDI receiving from uninitialised socket"; return false; } case Proto::UDP: { Socket::InetAddress received_from; try { auto received_packets = m_udp_rx.receive(100); for (auto rp : received_packets) { received_from = rp.received_from; EdiDecoder::Packet p; p.buf = std::move(rp.packetdata); p.received_on_port = rp.port_received_on; m_decoder.push_packet(p); } return true; } catch (const Socket::UDPReceiver::Timeout&) { return false; } catch (const Socket::UDPReceiver::Interrupted&) { return false; } catch (const invalid_argument& e) { try { fprintf(stderr, "Invalid argument receiving EDI from %s: %s\n", received_from.to_string().c_str(), e.what()); } catch (const invalid_argument& ee) { fprintf(stderr, "Invalid argument receiving EDI %s\n", e.what()); fprintf(stderr, "Invalid argument converting source address %s\n", ee.what()); } } catch (const runtime_error& e) { fprintf(stderr, "Runtime error UDP Receive: %s\n", e.what()); } return false; } case Proto::TCP: { // The buffer size must be smaller than the size of two AF Packets, because otherwise // the EDI decoder decodes two in a row and discards the first. This leads to ETI FCT // discontinuity. m_tcpbuffer.resize(512); const int timeout_ms = 1000; ssize_t ret = m_tcpclient.recv(m_tcpbuffer.data(), m_tcpbuffer.size(), 0, timeout_ms); if (ret <= 0) { return false; } else if (ret > (ssize_t)m_tcpbuffer.size()) { throw logic_error("EDI TCP: invalid recv() return value"); } else { m_tcpbuffer.resize(ret); m_decoder.push_bytes(m_tcpbuffer); return true; } } } throw logic_error("Incomplete rxPacket implementation!"); } EdiInput::EdiInput(double& tist_offset_s, float edi_max_delay_ms) : ediReader(tist_offset_s), decoder(ediReader), ediTransport(decoder) { if (edi_max_delay_ms > 0.0f) { // setMaxDelay wants number of AF packets, which correspond to 24ms ETI frames decoder.setMaxDelay(lroundf(edi_max_delay_ms / 24.0f)); } } Opendigitalradio-ODR-DabMod-f7eedef/src/EtiReader.h000066400000000000000000000157041475762153200222340ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "Eti.h" #include "Log.h" #include "FicSource.h" #include "FigParser.h" #include "Socket.h" #include "SubchannelSource.h" #include "TimestampDecoder.h" #include "lib/edi/ETIDecoder.hpp" #include #include #include #include /* The modulator uses this interface to get the necessary multiplex data, * either from an ETI or an EDI source. */ class EtiSource { public: /* Get the DAB Transmission Mode. Valid values: 1, 2, 3 or 4 */ virtual unsigned getMode() = 0; /* Get the current Frame Phase */ virtual unsigned getFp() = 0; /* Get the current Frame Count */ virtual unsigned getFct() = 0; /* Returns current Timestamp */ virtual frame_timestamp getTimestamp() = 0; /* Return the FIC source to be used for modulation */ virtual std::shared_ptr& getFic(void); /* Return all subchannel sources containing MST data */ virtual const std::vector > getSubchannels() const = 0; protected: std::shared_ptr myFicSource; }; enum class EtiReaderState { // For Framed input format NbFrame, // For Streamed input format FrameSize, // ETI Sync Sync, Fc, Nst, Eoh, Fic, Subch, Eof, Tist, Pad }; /* The EtiReader extracts the necessary data for modulation from an ETI(NI) byte stream. */ class EtiReader : public EtiSource { public: EtiReader(double& tist_offset_s); virtual unsigned getMode() override; virtual unsigned getFp() override; virtual unsigned getFct() override; virtual frame_timestamp getTimestamp() override; /* Read ETI data from dataIn. Returns the number of bytes * read from the buffer. */ int loadEtiData(const Buffer& dataIn); virtual const std::vector > getSubchannels() const override; private: /* Transform the ETI TIST to a PPS offset in units of 1/16384000 s */ uint32_t getPPSOffset(); EtiReaderState state = EtiReaderState::Sync; uint32_t nb_frames; uint16_t framesize; eti_SYNC eti_sync; eti_FC eti_fc; std::vector eti_stc; eti_EOH eti_eoh; eti_EOF eti_eof; eti_TIST eti_tist; TimestampDecoder myTimestampDecoder; bool eti_fc_valid; std::vector > mySources; }; /* The EdiReader extracts the necessary data using the EDI input library in * lib/edi */ class EdiReader : public EtiSource, public EdiDecoder::ETIDataCollector { public: EdiReader(double& tist_offset_s); virtual unsigned getMode() override; virtual unsigned getFp() override; virtual unsigned getFct() override; virtual frame_timestamp getTimestamp() override; virtual const std::vector > getSubchannels() const override; virtual bool isFrameReady(void); virtual void clearFrame(void); // Tell the ETIWriter what EDI protocol we receive in *ptr. // This is not part of the ETI data, but is used as check virtual void update_protocol( const std::string& proto, uint16_t major, uint16_t minor) override; // Update the data for the frame characterisation virtual void update_fc_data(const EdiDecoder::eti_fc_data& fc_data) override; virtual void update_fic(std::vector&& fic) override; virtual void update_err(uint8_t err) override; // In addition to TSTA in ETI, EDI also transports more time // stamp information. virtual void update_edi_time( uint32_t utco, uint32_t seconds) override; virtual void update_mnsc(uint16_t mnsc) override; virtual void update_rfu(uint16_t rfu) override; virtual void add_subchannel(EdiDecoder::eti_stc_data&& stc) override; // Gets called by the EDI library to tell us that all data for a frame was given to us virtual void assemble(EdiDecoder::ReceivedTagPacket&& tagpacket) override; std::optional getEnsembleInfo() const { return m_fic_decoder.observer.ensemble; } std::map getServiceInfo() const { return m_fic_decoder.observer.services; } private: bool m_proto_valid = false; bool m_frameReady = false; uint8_t m_err; bool m_fc_valid = false; EdiDecoder::eti_fc_data m_fc; std::vector m_fic; bool m_time_valid = false; uint32_t m_utco; uint32_t m_seconds; uint16_t m_mnsc = 0xffff; // 16 bits: RFU field in EOH uint16_t m_rfu = 0xffff; std::map > m_sources; TimestampDecoder m_timestamp_decoder; FICDecoder m_fic_decoder; }; /* The EDI input does not use the inputs defined in InputReader.h, as they were * designed for ETI. */ class EdiTransport { public: EdiTransport(EdiDecoder::ETIDecoder& decoder); /* Can be called once when using TCP, or several times when using UDP */ void Open(const std::string& uri); bool isEnabled(void) const { return m_enabled; } std::string getTcpUri(void) const { return m_tcp_uri; } /* Receive a packet and give it to the decoder. Returns * true if a packet was received, false in case of socket * read was interrupted by a signal. */ bool rxPacket(void); private: std::string m_tcp_uri; bool m_enabled; int m_port; std::string m_bindto; std::string m_mcastaddr; enum class Proto { Unspecified, UDP, TCP }; Proto m_proto = Proto::Unspecified; Socket::UDPReceiver m_udp_rx; std::vector m_tcpbuffer; Socket::TCPClient m_tcpclient; EdiDecoder::ETIDecoder& m_decoder; }; // EdiInput wraps an EdiReader, an EdiDecoder::ETIDecoder and an EdiTransport class EdiInput { public: EdiInput(double& tist_offset_s, float edi_max_delay_ms); EdiReader ediReader; EdiDecoder::ETIDecoder decoder; EdiTransport ediTransport; }; Opendigitalradio-ODR-DabMod-f7eedef/src/Events.cpp000066400000000000000000000052221475762153200221610ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #include #include #include "Events.h" #if defined(HAVE_ZEROMQ) EventSender events; EventSender::EventSender() : m_zmq_context(1), m_socket(m_zmq_context, zmq::socket_type::pub) { int linger = 2000; m_socket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); } EventSender::~EventSender() { } void EventSender::bind(const std::string& bind_endpoint) { try { m_socket.bind(bind_endpoint); m_socket_valid = true; } catch (const zmq::error_t& err) { fprintf(stderr, "Cannot bind event socket: %s", err.what()); } } void EventSender::send(const std::string& event_name, const json::map_t& detail) { if (not m_socket_valid) { return; } zmq::message_t zmsg1(event_name.data(), event_name.size()); const auto detail_json = json::map_to_json(detail); zmq::message_t zmsg2(detail_json.data(), detail_json.size()); try { m_socket.send(zmsg1, zmq::send_flags::sndmore); m_socket.send(zmsg2, zmq::send_flags::none); } catch (const zmq::error_t& err) { fprintf(stderr, "Cannot send event %s: %s", event_name.c_str(), err.what()); } } void LogToEventSender::log(log_level_t level, const std::string& message) { std::string event_name; if (level == log_level_t::warn) { event_name = "warn"; } else if (level == log_level_t::error) { event_name = "error"; } else if (level == log_level_t::alert) { event_name = "alert"; } else if (level == log_level_t::emerg) { event_name = "emerg"; } if (not event_name.empty()) { json::map_t detail; detail["message"].v = message; events.send(event_name, detail); } } std::string LogToEventSender::get_name() const { return "EventSender"; } #endif // defined(HAVE_ZEROMQ) Opendigitalradio-ODR-DabMod-f7eedef/src/Events.h000066400000000000000000000040051475762153200216240ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_ZEROMQ) # include "zmq.hpp" # include # include "Log.h" # include "Json.h" class EventSender { public: EventSender(); EventSender(const EventSender& other) = delete; const EventSender& operator=(const EventSender& other) = delete; EventSender(EventSender&& other) = delete; EventSender& operator=(EventSender&& other) = delete; ~EventSender(); void bind(const std::string& bind_endpoint); void send(const std::string& event_name, const json::map_t& detail); private: zmq::context_t m_zmq_context; zmq::socket_t m_socket; bool m_socket_valid = false; }; class LogToEventSender: public LogBackend { public: virtual ~LogToEventSender() {}; virtual void log(log_level_t level, const std::string& message); virtual std::string get_name() const; }; /* events is a singleton used in all parts of the program to output log messages. * It is constructed in Events.cpp */ extern EventSender events; #endif // defined(HAVE_ZEROMQ) Opendigitalradio-ODR-DabMod-f7eedef/src/FIRFilter.cpp000066400000000000000000000266741475762153200225210ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This block implements a FIR filter. The real filter taps are given as floats, and the block can take advantage of SSE. For better performance, filtering is done in another thread, leading to a pipeline delay of two calls to FIRFilter::process */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "FIRFilter.h" #include "PcDebug.h" #include "Utils.h" #include #include #include #include #include #include #ifdef __SSE__ # include #endif using namespace std; /* This is the FIR Filter calculated with the doc/fir-filter/generate-filter.py script * with settings * gain = 1 * sampling_freq = 2.048e6 * cutoff = 810e3 * transition_width = 250e3 * * It is a good default filter for the common scenarios. */ static const std::array default_filter_taps({ -0.00110450468492, 0.00120703084394, -0.000840645749122, -0.000187368263141, 0.00184351124335, -0.00355578539893, 0.00419321097434, -0.00254214904271, -0.00183473504148, 0.00781436730176, -0.0125957569107, 0.0126200336963, -0.00537294941023, -0.00866683479398, 0.0249746385962, -0.0356550291181, 0.0319730602205, -0.00795613788068, -0.0363943465054, 0.0938014090061, -0.151176810265, 0.193567320704, 0.791776955128, 0.193567320704, -0.151176810265, 0.0938014090061, -0.0363943465054, -0.00795613788068, 0.0319730602205, -0.0356550291181, 0.0249746385962, -0.00866683479398, -0.00537294941023, 0.0126200336963, -0.0125957569107, 0.00781436730176, -0.00183473504148, -0.00254214904271, 0.00419321097434, -0.00355578539893, 0.00184351124335, -0.000187368263141, -0.000840645749122, 0.00120703084394, -0.00110450468492}); FIRFilter::FIRFilter(std::string& taps_file) : PipelinedModCodec(), RemoteControllable("firfilter"), m_taps_file(taps_file) { PDEBUG("FIRFilter::FIRFilter(%s) @ %p\n", taps_file.c_str(), this); RC_ADD_PARAMETER(ntaps, "(Read-only) number of filter taps."); RC_ADD_PARAMETER(tapsfile, "Filename containing filter taps. When written to, the new file gets automatically loaded."); load_filter_taps(m_taps_file); start_pipeline_thread(); } FIRFilter::~FIRFilter() { stop_pipeline_thread(); } void FIRFilter::load_filter_taps(const std::string &tapsFile) { std::vector filter_taps; if (tapsFile == "default") { std::copy(default_filter_taps.begin(), default_filter_taps.end(), std::back_inserter(filter_taps)); } else { std::ifstream taps_fstream(tapsFile.c_str()); if (!taps_fstream) { throw std::runtime_error("FIRFilter: Could not open taps file " + tapsFile); } int n_taps; taps_fstream >> n_taps; if (n_taps <= 0) { throw std::runtime_error("FIRFilter: taps file has invalid format."); } if (n_taps > 100) { etiLog.level(warn) << "FIRFilter: warning: taps file has more than 100 taps"; } etiLog.level(debug) << "FIRFilter: Reading " << n_taps << " taps..."; filter_taps.resize(n_taps); int n; for (n = 0; n < n_taps; n++) { taps_fstream >> filter_taps[n]; PDEBUG("FIRFilter: tap: %f\n", (double)filter_taps[n] ); if (taps_fstream.eof()) { throw std::runtime_error( "FIRFilter: file " + tapsFile + " should contain " + to_string(n_taps) + " taps, but EOF reached after " + to_string(n) + " taps!"); } } } { std::lock_guard lock(m_taps_mutex); m_taps = filter_taps; } } int FIRFilter::internal_process(Buffer* const dataIn, Buffer* dataOut) { size_t i; #if __SSE__ // The SSE accelerated version cannot work on the complex values, // it is necessary to do the convolution on the real and imaginary // parts separately. Thankfully, the taps are real, simplifying the // procedure. const float* in = reinterpret_cast(dataIn->getData()); float* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(float); if ((uintptr_t)(&out[0]) % 16 != 0) { throw std::runtime_error("FIRFilterWorker: out not aligned"); } __m128 SSEout; __m128 SSEtaps; __m128 SSEin; { std::lock_guard lock(m_taps_mutex); for (i = 0; i < sizeIn - 2*m_taps.size(); i += 4) { SSEout = _mm_setr_ps(0,0,0,0); for (size_t j = 0; j < m_taps.size(); j++) { if ((uintptr_t)(&in[i+2*j]) % 16 == 0) { SSEin = _mm_load_ps(&in[i+2*j]); //faster when aligned } else { SSEin = _mm_loadu_ps(&in[i+2*j]); } SSEtaps = _mm_load1_ps(&m_taps[j]); SSEout = _mm_add_ps(SSEout, _mm_mul_ps(SSEin, SSEtaps)); } _mm_store_ps(&out[i], SSEout); } for (; i < sizeIn; i++) { out[i] = 0.0; for (int j = 0; i+2*j < sizeIn; j++) { out[i] += in[i+2*j] * m_taps[j]; } } } #else // No SSE ? Loop unrolling should make this faster. As for the SSE, // the real and imaginary parts are calculated separately. const float* in = reinterpret_cast(dataIn->getData()); float* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(float); { std::lock_guard lock(m_taps_mutex); // Convolve by aligning both frame and taps at zero. for (i = 0; i < sizeIn - 2*m_taps.size(); i += 4) { out[i] = 0.0; out[i+1] = 0.0; out[i+2] = 0.0; out[i+3] = 0.0; for (size_t j = 0; j < m_taps.size(); j++) { out[i] += in[i + 2*j] * m_taps[j]; out[i+1] += in[i+1 + 2*j] * m_taps[j]; out[i+2] += in[i+2 + 2*j] * m_taps[j]; out[i+3] += in[i+3 + 2*j] * m_taps[j]; } } // At the end of the frame, we cut the convolution off. // The beginning of the next frame starts with a NULL symbol // anyway. for (; i < sizeIn; i++) { out[i] = 0.0; for (int j = 0; i+2*j < sizeIn; j++) { out[i] += in[i+2*j] * m_taps[j]; } } } #endif // The following implementations are for debugging only. #if 0 // Same thing as above, without loop unrolling. For debugging. const float* in = reinterpret_cast(dataIn->getData()); float* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(float); std::lock_guard lock(m_taps_mutex); for (i = 0; i < sizeIn - 2*m_taps.size(); i += 1) { out[i] = 0.0; for (size_t j = 0; j < m_taps.size(); j++) { out[i] += in[i+2*j] * m_taps[j]; } } for (; i < sizeIn; i++) { out[i] = 0.0; for (int j = 0; i+2*j < sizeIn; j++) { out[i] += in[i+2*j] * m_taps[j]; } } #elif 0 // An unrolled loop, but this time, the input data is cast to complex float. // Makes indices more natural. For debugging. const complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); std::lock_guard lock(m_taps_mutex); for (i = 0; i < sizeIn - m_taps.size(); i += 4) { out[i] = 0.0; out[i+1] = 0.0; out[i+2] = 0.0; out[i+3] = 0.0; for (size_t j = 0; j < m_taps.size(); j++) { out[i] += in[i+j ] * m_taps[j]; out[i+1] += in[i+1+j] * m_taps[j]; out[i+2] += in[i+2+j] * m_taps[j]; out[i+3] += in[i+3+j] * m_taps[j]; } } for (; i < sizeIn; i++) { out[i] = 0.0; for (int j = 0; j+i < sizeIn; j++) { out[i] += in[i+j] * m_taps[j]; } } #elif 0 // Simple implementation. Slow. For debugging. const complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); std::lock_guard lock(m_taps_mutex); for (i = 0; i < sizeIn - m_taps.size(); i += 1) { out[i] = 0.0; for (size_t j = 0; j < m_taps.size(); j++) { out[i] += in[i+j ] * m_taps[j]; } } for (; i < sizeIn; i++) { out[i] = 0.0; for (int j = 0; j+i < sizeIn; j++) { out[i] += in[i+j] * m_taps[j]; } } #endif return dataOut->getLength(); } void FIRFilter::set_parameter(const string& parameter, const string& value) { if (parameter == "ntaps") { throw ParameterError("Parameter 'ntaps' is read-only"); } else if (parameter == "tapsfile") { try { load_filter_taps(value); m_taps_file = value; } catch (const std::runtime_error &e) { throw ParameterError(e.what()); } } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string FIRFilter::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "ntaps") { ss << m_taps.size(); } else if (parameter == "tapsfile") { ss << m_taps_file; } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t FIRFilter::get_all_values() const { json::map_t map; map["ntaps"].v = m_taps.size(); map["tapsfile"].v = m_taps_file; return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/FIRFilter.h000066400000000000000000000037301475762153200221520ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "RemoteControl.h" #include "ModPlugin.h" #include #include #include #include #define FIRFILTER_PIPELINE_DELAY 1 class FIRFilter : public PipelinedModCodec, public RemoteControllable { public: FIRFilter(std::string& taps_file); FIRFilter(const FIRFilter& other) = delete; FIRFilter& operator=(const FIRFilter& other) = delete; virtual ~FIRFilter(); const char* name() override { return "FIRFilter"; } /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; protected: virtual int internal_process(Buffer* const dataIn, Buffer* dataOut) override; void load_filter_taps(const std::string &tapsFile); std::string& m_taps_file; mutable std::mutex m_taps_mutex; std::vector m_taps; }; Opendigitalradio-ODR-DabMod-f7eedef/src/FicSource.cpp000066400000000000000000000055351475762153200226060ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "FicSource.h" #include "PcDebug.h" #include "Log.h" #include #include #include #include #include FicSource::FicSource(unsigned ficf, unsigned mid) : ModInput() { // PDEBUG("FicSource::FicSource(...)\n"); // PDEBUG(" Start address: %i\n", d_start_address); // PDEBUG(" Framesize: %i\n", m_framesize); // PDEBUG(" Protection: %i\n", d_protection); if (ficf == 0) { m_buffer.setLength(0); return; } if (mid == 3) { m_framesize = 32 * 4; m_puncturing_rules.emplace_back(29 * 16, 0xeeeeeeee); m_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec); } else { m_framesize = 24 * 4; m_puncturing_rules.emplace_back(21 * 16, 0xeeeeeeee); m_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec); } m_buffer.setLength(m_framesize); } size_t FicSource::getFramesize() const { return m_framesize; } const std::vector& FicSource::get_rules() const { return m_puncturing_rules; } void FicSource::loadFicData(const Buffer& fic) { m_buffer = fic; } int FicSource::process(Buffer* outputData) { PDEBUG("FicSource::process (outputData: %p, outputSize: %zu)\n", outputData, outputData->getLength()); if (m_buffer.getLength() != m_framesize) { throw std::runtime_error( "ERROR: FicSource::process.outputSize != m_framesize: " + std::to_string(m_buffer.getLength()) + " != " + std::to_string(m_framesize)); } *outputData = m_buffer; return outputData->getLength(); } void FicSource::loadTimestamp(const frame_timestamp& ts) { m_ts_valid = true; m_ts = ts; } meta_vec_t FicSource::process_metadata(const meta_vec_t& metadataIn) { meta_vec_t md_vec; if (m_ts_valid) { flowgraph_metadata meta; meta.ts = m_ts; md_vec.push_back(meta); } return md_vec; } Opendigitalradio-ODR-DabMod-f7eedef/src/FicSource.h000066400000000000000000000033741475762153200222520ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "PuncturingRule.h" #include "Eti.h" #include "ModPlugin.h" #include "TimestampDecoder.h" #include #include class FicSource : public ModInput, public ModMetadata { public: FicSource(unsigned ficf, unsigned mid); size_t getFramesize() const; const std::vector& get_rules() const; void loadFicData(const Buffer& fic); int process(Buffer* outputData) override; const char* name() override { return "FicSource"; } void loadTimestamp(const frame_timestamp& ts); virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; private: size_t m_framesize = 0; Buffer m_buffer; frame_timestamp m_ts; bool m_ts_valid = false; std::vector m_puncturing_rules; }; Opendigitalradio-ODR-DabMod-f7eedef/src/FigParser.cpp000066400000000000000000001057301475762153200226040ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Most parts of this file are taken from dablin, Copyright (C) 2015-2022 Stefan Pöschel Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "FigParser.h" #include "PcDebug.h" #include "Log.h" #include "crc.h" #include "CharsetTools.h" #include #include #include #include #include template static uint16_t read_16b(T buf) { uint16_t value = 0; value = (uint16_t)(buf[0]) << 8; value |= (uint16_t)(buf[1]); return value; } static bool checkCRC(const uint8_t *buf, size_t size) { const uint16_t crc_from_packet = read_16b(buf + size - 2); uint16_t crc_calc = 0xffff; crc_calc = crc16(crc_calc, buf, size - 2); crc_calc ^= 0xffff; return crc_from_packet == crc_calc; } void FICDecoderObserver::FICChangeEnsemble(const FIC_ENSEMBLE& e) { if (ensemble.has_value() and e.eid == ensemble->eid and e.ecc == ensemble->ecc) { return; } services.clear(); ensemble = e; } void FICDecoderObserver::FICChangeService(const LISTED_SERVICE& ls) { services[ls.sid] = ls; } void FICDecoderObserver::FICChangeUTCDateTime(const FIC_DAB_DT& dt) { utc_dt = dt; } // --- FICDecoder ----------------------------------------------------------------- FICDecoder::FICDecoder(bool verbose) : verbose(verbose), utc_dt_long(false) { } void FICDecoder::Reset() { ensemble = FIC_ENSEMBLE(); services.clear(); subchannels.clear(); utc_dt = FIC_DAB_DT(); } void FICDecoder::Process(const uint8_t *data, size_t len) { // check for integer FIB count if(len % 32) { etiLog.log(warn, "FICDecoder: Ignoring non-integer FIB count FIC data with %zu bytes\n", len); return; } for(size_t i = 0; i < len; i += 32) ProcessFIB(data + i); } void FICDecoder::ProcessFIB(const uint8_t *data) { if (not checkCRC(data, 32)) { observer.FICDiscardedFIB(); return; } // iterate over all FIGs for(size_t offset = 0; offset < 30 && data[offset] != 0xFF;) { int type = data[offset] >> 5; size_t len = data[offset] & 0x1F; offset++; switch(type) { case 0: ProcessFIG0(data + offset, len); break; case 1: ProcessFIG1(data + offset, len); break; // default: // etiLog.log(warn, "FICDecoder: received unsupported FIG %d with %zu bytes\n", type, len); } offset += len; } } void FICDecoder::ProcessFIG0(const uint8_t *data, size_t len) { if(len < 1) { etiLog.log(warn, "FICDecoder: received empty FIG 0\n"); return; } // read/skip FIG 0 header FIG0_HEADER header(data[0]); data++; len--; // ignore next config/other ensembles/data services if(header.cn || header.oe || header.pd) return; // handle extension switch(header.extension) { case 0: ProcessFIG0_0(data, len); break; case 1: ProcessFIG0_1(data, len); break; case 2: ProcessFIG0_2(data, len); break; case 5: ProcessFIG0_5(data, len); break; case 8: ProcessFIG0_8(data, len); break; case 9: ProcessFIG0_9(data, len); break; case 10: ProcessFIG0_10(data, len); break; case 13: ProcessFIG0_13(data, len); break; case 17: ProcessFIG0_17(data, len); break; case 18: ProcessFIG0_18(data, len); break; case 19: ProcessFIG0_19(data, len); break; // default: // etiLog.log(warn, "FICDecoder: received unsupported FIG 0/%d with %zu field bytes\n", header.extension, len); } } void FICDecoder::ProcessFIG0_0(const uint8_t *data, size_t len) { // FIG 0/0 - Ensemble information // EId and alarm flag only if(len < 4) return; FIC_ENSEMBLE new_ensemble = ensemble; new_ensemble.eid = data[0] << 8 | data[1]; new_ensemble.al_flag = data[2] & 0x20; if(ensemble != new_ensemble) { ensemble = new_ensemble; if (verbose) etiLog.log(debug, "FICDecoder: EId 0x%04X: alarm flag: %s\n", ensemble.eid, ensemble.al_flag ? "true" : "false"); UpdateEnsemble(); } } void FICDecoder::ProcessFIG0_1(const uint8_t *data, size_t len) { // FIG 0/1 - Basic sub-channel organization // iterate through all sub-channels for(size_t offset = 0; offset < len;) { int subchid = data[offset] >> 2; size_t start_address = (data[offset] & 0x03) << 8 | data[offset + 1]; offset += 2; FIC_SUBCHANNEL sc; sc.start = start_address; bool short_long_form = data[offset] & 0x80; if(short_long_form) { // long form int option = (data[offset] & 0x70) >> 4; int pl = (data[offset] & 0x0C) >> 2; size_t subch_size = (data[offset] & 0x03) << 8 | data[offset + 1]; switch(option) { case 0b000: sc.size = subch_size; sc.pl = "EEP " + std::to_string(pl + 1) + "-A"; sc.bitrate = subch_size / eep_a_size_factors[pl] * 8; break; case 0b001: sc.size = subch_size; sc.pl = "EEP " + std::to_string(pl + 1) + "-B"; sc.bitrate = subch_size / eep_b_size_factors[pl] * 32; break; } offset += 2; } else { // short form bool table_switch = data[offset] & 0x40; if(!table_switch) { int table_index = data[offset] & 0x3F; sc.size = uep_sizes[table_index]; sc.pl = "UEP " + std::to_string(uep_pls[table_index]); sc.bitrate = uep_bitrates[table_index]; } offset++; } if(!sc.IsNone()) { FIC_SUBCHANNEL& current_sc = GetSubchannel(subchid); sc.language = current_sc.language; // ignored for comparison if(current_sc != sc) { current_sc = sc; if (verbose) etiLog.log(debug, "FICDecoder: SubChId %2d: start %3zu CUs, size %3zu CUs, PL %-7s = %3d kBit/s\n", subchid, sc.start, sc.size, sc.pl.c_str(), sc.bitrate); UpdateSubchannel(subchid); } } } } void FICDecoder::ProcessFIG0_2(const uint8_t *data, size_t len) { // FIG 0/2 - Basic service and service component definition // programme services only // iterate through all services for(size_t offset = 0; offset < len;) { uint16_t sid = data[offset] << 8 | data[offset + 1]; offset += 2; size_t num_service_comps = data[offset++] & 0x0F; // iterate through all service components for(size_t comp = 0; comp < num_service_comps; comp++) { int tmid = data[offset] >> 6; switch(tmid) { case 0b00: // MSC stream audio int ascty = data[offset] & 0x3F; int subchid = data[offset + 1] >> 2; bool ps = data[offset + 1] & 0x02; bool ca = data[offset + 1] & 0x01; if(!ca) { switch(ascty) { case 0: // DAB case 63: // DAB+ bool dab_plus = ascty == 63; AUDIO_SERVICE audio_service(subchid, dab_plus); FIC_SERVICE& service = GetService(sid); AUDIO_SERVICE& current_audio_service = service.audio_comps[subchid]; if(current_audio_service != audio_service || ps != (service.pri_comp_subchid == subchid)) { current_audio_service = audio_service; if(ps) service.pri_comp_subchid = subchid; if (verbose) etiLog.log(debug, "FICDecoder: SId 0x%04X: audio service (SubChId %2d, %-4s, %s)\n", sid, subchid, dab_plus ? "DAB+" : "DAB", ps ? "primary" : "secondary"); UpdateService(service); } break; } } } offset += 2; } } } void FICDecoder::ProcessFIG0_5(const uint8_t *data, size_t len) { // FIG 0/5 - Service component language // programme services only // iterate through all components for(size_t offset = 0; offset < len;) { bool ls_flag = data[offset] & 0x80; if(ls_flag) { // long form - skipped, as not relevant offset += 3; } else { // short form bool msc_fic_flag = data[offset] & 0x40; // handle only MSC components if(!msc_fic_flag) { int subchid = data[offset] & 0x3F; int language = data[offset + 1]; FIC_SUBCHANNEL& current_sc = GetSubchannel(subchid); if(current_sc.language != language) { current_sc.language = language; if (verbose) etiLog.log(debug, "FICDecoder: SubChId %2d: language '%s'\n", subchid, ConvertLanguageToString(language).c_str()); UpdateSubchannel(subchid); } } offset += 2; } } } void FICDecoder::ProcessFIG0_8(const uint8_t *data, size_t len) { // FIG 0/8 - Service component global definition // programme services only // iterate through all service components for(size_t offset = 0; offset < len;) { uint16_t sid = data[offset] << 8 | data[offset + 1]; offset += 2; bool ext_flag = data[offset] & 0x80; int scids = data[offset] & 0x0F; offset++; bool ls_flag = data[offset] & 0x80; if(ls_flag) { // long form - skipped, as not relevant offset += 2; } else { // short form bool msc_fic_flag = data[offset] & 0x40; // handle only MSC components if(!msc_fic_flag) { int subchid = data[offset] & 0x3F; FIC_SERVICE& service = GetService(sid); bool new_comp = service.comp_defs.find(scids) == service.comp_defs.end(); int& current_subchid = service.comp_defs[scids]; if(new_comp || current_subchid != subchid) { current_subchid = subchid; if (verbose) etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: MSC service component (SubChId %2d)\n", sid, scids, subchid); UpdateService(service); } } offset++; } // skip Rfa field, if needed if(ext_flag) offset++; } } void FICDecoder::ProcessFIG0_9(const uint8_t *data, size_t len) { // FIG 0/9 - Time and country identifier - Country, LTO and International table // ensemble ECC/LTO and international table ID only if(len < 3) return; FIC_ENSEMBLE new_ensemble = ensemble; new_ensemble.lto = (data[0] & 0x20 ? -1 : 1) * (data[0] & 0x1F); new_ensemble.ecc = data[1]; new_ensemble.inter_table_id = data[2]; if(ensemble != new_ensemble) { ensemble = new_ensemble; if (verbose) etiLog.log(debug, "FICDecoder: ECC: 0x%02X, LTO: %s, international table ID: 0x%02X (%s)\n", ensemble.ecc, ConvertLTOToString(ensemble.lto).c_str(), ensemble.inter_table_id, ConvertInterTableIDToString(ensemble.inter_table_id).c_str()); UpdateEnsemble(); // update services that changes may affect for(const fic_services_t::value_type& service : services) { const FIC_SERVICE& s = service.second; if(s.pty_static != FIC_SERVICE::pty_none || s.pty_dynamic != FIC_SERVICE::pty_none) UpdateService(s); } } } void FICDecoder::ProcessFIG0_10(const uint8_t *data, size_t len) { // FIG 0/10 - Date and time (d&t) if(len < 4) return; FIC_DAB_DT new_utc_dt; // ignore short form, once long form available bool utc_flag = data[2] & 0x08; if(!utc_flag && utc_dt_long) return; // retrieve date int mjd = (data[0] & 0x7F) << 10 | data[1] << 2 | data[2] >> 6; int y0 = floor((mjd - 15078.2) / 365.25); int m0 = floor((mjd - 14956.1 - floor(y0 * 365.25)) / 30.6001); int d = mjd - 14956 - floor(y0 * 365.25) - floor(m0 * 30.6001); int k = (m0 == 14 || m0 == 15) ? 1 : 0; int y = y0 + k; int m = m0 - 1 - k * 12; new_utc_dt.dt.tm_year = y; // from 1900 new_utc_dt.dt.tm_mon = m - 1; // 0-based new_utc_dt.dt.tm_mday = d; // retrieve time new_utc_dt.dt.tm_hour = (data[2] & 0x07) << 2 | data[3] >> 6; new_utc_dt.dt.tm_min = data[3] & 0x3F; new_utc_dt.dt.tm_isdst = -1; // ignore DST if(utc_flag) { // long form if(len < 6) return; new_utc_dt.dt.tm_sec = data[4] >> 2; new_utc_dt.ms = (data[4] & 0x03) << 8 | data[5]; utc_dt_long = true; } else { // short form new_utc_dt.dt.tm_sec = 0; new_utc_dt.ms = FIC_DAB_DT::ms_none; } if(utc_dt != new_utc_dt) { // print only once (or once again on precision change) if(utc_dt.IsNone() || utc_dt.IsMsNone() != new_utc_dt.IsMsNone()) if (verbose) etiLog.log(debug, "FICDecoder: UTC date/time: %s\n", ConvertDateTimeToString(new_utc_dt, 0, true).c_str()); utc_dt = new_utc_dt; observer.FICChangeUTCDateTime(utc_dt); } } void FICDecoder::ProcessFIG0_13(const uint8_t *data, size_t len) { // FIG 0/13 - User application information // programme services only // iterate through all service components for(size_t offset = 0; offset < len;) { uint16_t sid = data[offset] << 8 | data[offset + 1]; offset += 2; int scids = data[offset] >> 4; size_t num_scids_uas = data[offset] & 0x0F; offset++; // iterate through all user applications for(size_t scids_ua = 0; scids_ua < num_scids_uas; scids_ua++) { int ua_type = data[offset] << 3 | data[offset + 1] >> 5; size_t ua_data_length = data[offset + 1] & 0x1F; offset += 2; // handle only Slideshow if(ua_type == 0x002) { FIC_SERVICE& service = GetService(sid); if(service.comp_sls_uas.find(scids) == service.comp_sls_uas.end()) { ua_data_t& sls_ua_data = service.comp_sls_uas[scids]; sls_ua_data.resize(ua_data_length); if(ua_data_length) memcpy(&sls_ua_data[0], data + offset, ua_data_length); if (verbose) etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: Slideshow (%zu bytes UA data)\n", sid, scids, ua_data_length); UpdateService(service); } } offset += ua_data_length; } } } void FICDecoder::ProcessFIG0_17(const uint8_t *data, size_t len) { // FIG 0/17 - Programme Type // programme type only // iterate through all services for(size_t offset = 0; offset < len;) { uint16_t sid = data[offset] << 8 | data[offset + 1]; bool sd = data[offset + 2] & 0x80; bool l_flag = data[offset + 2] & 0x20; bool cc_flag = data[offset + 2] & 0x10; offset += 3; // skip language, if present if(l_flag) offset++; // programme type (international code) int pty = data[offset] & 0x1F; offset++; // skip CC part, if present if(cc_flag) offset++; FIC_SERVICE& service = GetService(sid); int& current_pty = sd ? service.pty_dynamic : service.pty_static; if(current_pty != pty) { // suppress message, if dynamic FIC messages disabled and dynamic PTY not initally be set bool show_msg = !(sd && current_pty != FIC_SERVICE::pty_none); current_pty = pty; if(verbose && show_msg) { // assuming international table ID 0x01 here! etiLog.log(debug, "FICDecoder: SId 0x%04X: programme type (%s): '%s'\n", sid, sd ? "dynamic" : "static", ConvertPTYToString(pty, 0x01).c_str()); } UpdateService(service); } } } void FICDecoder::ProcessFIG0_18(const uint8_t *data, size_t len) { // FIG 0/18 - Announcement support // iterate through all services for(size_t offset = 0; offset < len;) { uint16_t sid = data[offset] << 8 | data[offset + 1]; uint16_t asu_flags = data[offset + 2] << 8 | data[offset + 3]; size_t number_of_clusters = data[offset + 4] & 0x1F; offset += 5; cids_t cids; for(size_t i = 0; i < number_of_clusters; i++) cids.emplace(data[offset++]); FIC_SERVICE& service = GetService(sid); uint16_t& current_asu_flags = service.asu_flags; cids_t& current_cids = service.cids; if(current_asu_flags != asu_flags || current_cids != cids) { current_asu_flags = asu_flags; current_cids = cids; std::string cids_str; char cid_string[5]; for(const cids_t::value_type& cid : cids) { if(!cids_str.empty()) cids_str += "/"; snprintf(cid_string, sizeof(cid_string), "0x%02X", cid); cids_str += std::string(cid_string); } if (verbose) etiLog.log(debug, "FICDecoder: SId 0x%04X: ASu flags 0x%04X, cluster(s) %s\n", sid, asu_flags, cids_str.c_str()); UpdateService(service); } } } void FICDecoder::ProcessFIG0_19(const uint8_t *data, size_t len) { // FIG 0/19 - Announcement switching // iterate through all announcement clusters for(size_t offset = 0; offset < len;) { uint8_t cid = data[offset]; uint16_t asw_flags = data[offset + 1] << 8 | data[offset + 2]; bool region_flag = data[offset + 3] & 0x40; int subchid = data[offset + 3] & 0x3F; offset += region_flag ? 5 : 4; FIC_ASW_CLUSTER ac; ac.asw_flags = asw_flags; ac.subchid = subchid; FIC_ASW_CLUSTER& current_ac = ensemble.asw_clusters[cid]; if(current_ac != ac) { current_ac = ac; if (verbose) { etiLog.log(debug, "FICDecoder: ASw cluster 0x%02X: flags 0x%04X, SubChId %2d\n", cid, asw_flags, subchid); } UpdateEnsemble(); // update services that changes may affect for(const fic_services_t::value_type& service : services) { const FIC_SERVICE& s = service.second; if(s.cids.find(cid) != s.cids.cend()) UpdateService(s); } } } } void FICDecoder::ProcessFIG1(const uint8_t *data, size_t len) { if(len < 1) { etiLog.log(warn, "FICDecoder: received empty FIG 1\n"); return; } // read/skip FIG 1 header FIG1_HEADER header(data[0]); data++; len--; // ignore other ensembles if(header.oe) return; // check for (un)supported extension + set ID field len size_t len_id = -1; switch(header.extension) { case 0: // ensemble case 1: // programme service len_id = 2; break; case 4: // service component // programme services only (P/D = 0) if(data[0] & 0x80) return; len_id = 3; break; default: // etiLog.log(debug, "FICDecoder: received unsupported FIG 1/%d with %zu field bytes\n", header.extension, len); return; } // check length size_t len_calced = len_id + 16 + 2; if(len != len_calced) { etiLog.log(warn, "FICDecoder: received FIG 1/%d having %zu field bytes (expected: %zu)\n", header.extension, len, len_calced); return; } // parse actual label data FIC_LABEL label; label.charset = header.charset; memcpy(label.label, data + len_id, 16); label.short_label_mask = data[len_id + 16] << 8 | data[len_id + 17]; // handle extension switch(header.extension) { case 0: { // ensemble uint16_t eid = data[0] << 8 | data[1]; ProcessFIG1_0(eid, label); break; } case 1: { // programme service uint16_t sid = data[0] << 8 | data[1]; ProcessFIG1_1(sid, label); break; } case 4: { // service component int scids = data[0] & 0x0F; uint16_t sid = data[1] << 8 | data[2]; ProcessFIG1_4(sid, scids, label); break; } } } void FICDecoder::ProcessFIG1_0(uint16_t eid, const FIC_LABEL& label) { if(ensemble.label != label) { ensemble.label = label; std::string label_str = ConvertLabelToUTF8(label, nullptr); std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask); if (verbose) etiLog.log(debug, "FICDecoder: EId 0x%04X: ensemble label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n", eid, label_str.c_str(), short_label_str.c_str()); UpdateEnsemble(); } } void FICDecoder::ProcessFIG1_1(uint16_t sid, const FIC_LABEL& label) { FIC_SERVICE& service = GetService(sid); if(service.label != label) { service.label = label; if (verbose) { std::string label_str = ConvertLabelToUTF8(label, nullptr); std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask); etiLog.log(debug, "FICDecoder: SId 0x%04X: programme service label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n", sid, label_str.c_str(), short_label_str.c_str()); } UpdateService(service); } } void FICDecoder::ProcessFIG1_4(uint16_t sid, int scids, const FIC_LABEL& label) { // programme services only FIC_SERVICE& service = GetService(sid); FIC_LABEL& comp_label = service.comp_labels[scids]; if(comp_label != label) { comp_label = label; if (verbose) { std::string label_str = ConvertLabelToUTF8(label, nullptr); std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask); etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: service component label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n", sid, scids, label_str.c_str(), short_label_str.c_str()); } UpdateService(service); } } FIC_SUBCHANNEL& FICDecoder::GetSubchannel(int subchid) { // created automatically, if not yet existing return subchannels[subchid]; } void FICDecoder::UpdateSubchannel(int subchid) { // update services that consist of this sub-channel for(const fic_services_t::value_type& service : services) { const FIC_SERVICE& s = service.second; if(s.audio_comps.find(subchid) != s.audio_comps.end()) UpdateService(s); } } FIC_SERVICE& FICDecoder::GetService(uint16_t sid) { FIC_SERVICE& result = services[sid]; // created, if not yet existing // if new service, set SID if(result.IsNone()) result.sid = sid; return result; } void FICDecoder::UpdateService(const FIC_SERVICE& service) { // abort update, if primary component or label not yet present if(service.HasNoPriCompSubchid() || service.label.IsNone()) return; // secondary components (if both component and definition are present) bool multi_comps = false; for(const comp_defs_t::value_type& comp_def : service.comp_defs) { if(comp_def.second == service.pri_comp_subchid || service.audio_comps.find(comp_def.second) == service.audio_comps.end()) continue; UpdateListedService(service, comp_def.first, true); multi_comps = true; } // primary component UpdateListedService(service, LISTED_SERVICE::scids_none, multi_comps); } void FICDecoder::UpdateListedService(const FIC_SERVICE& service, int scids, bool multi_comps) { // assemble listed service LISTED_SERVICE ls; ls.sid = service.sid; ls.scids = scids; ls.label = service.label; ls.pty_static = service.pty_static; ls.pty_dynamic = service.pty_dynamic; ls.asu_flags = service.asu_flags; ls.cids = service.cids; ls.pri_comp_subchid = service.pri_comp_subchid; ls.multi_comps = multi_comps; if(scids == LISTED_SERVICE::scids_none) { // primary component ls.audio_service = service.audio_comps.at(service.pri_comp_subchid); } else { // secondary component ls.audio_service = service.audio_comps.at(service.comp_defs.at(scids)); // use component label, if available comp_labels_t::const_iterator cl_it = service.comp_labels.find(scids); if(cl_it != service.comp_labels.end()) ls.label = cl_it->second; } // use sub-channel information, if available fic_subchannels_t::const_iterator sc_it = subchannels.find(ls.audio_service.subchid); if(sc_it != subchannels.end()) ls.subchannel = sc_it->second; /* check (for) Slideshow; currently only supported in X-PAD * - derive the required SCIdS (if not yet known) * - derive app type from UA data (if present) */ int sls_scids = scids; if(sls_scids == LISTED_SERVICE::scids_none) { for(const comp_defs_t::value_type& comp_def : service.comp_defs) { if(comp_def.second == ls.audio_service.subchid) { sls_scids = comp_def.first; break; } } } if(sls_scids != LISTED_SERVICE::scids_none && service.comp_sls_uas.find(sls_scids) != service.comp_sls_uas.end()) ls.sls_app_type = GetSLSAppType(service.comp_sls_uas.at(sls_scids)); // forward to observer observer.FICChangeService(ls); } int FICDecoder::GetSLSAppType(const ua_data_t& ua_data) { // default values, if no UA data present bool ca_flag = false; int xpad_app_type = 12; bool dg_flag = false; int dscty = 60; // MOT // if UA data present, parse X-PAD data if(ua_data.size() >= 2) { ca_flag = ua_data[0] & 0x80; xpad_app_type = ua_data[0] & 0x1F; dg_flag = ua_data[1] & 0x80; dscty = ua_data[1] & 0x3F; } // if no CA is used, but DGs and MOT, enable Slideshow if(!ca_flag && !dg_flag && dscty == 60) return xpad_app_type; else return LISTED_SERVICE::sls_app_type_none; } void FICDecoder::UpdateEnsemble() { // abort update, if EId or label not yet present if(ensemble.IsNone() || ensemble.label.IsNone()) return; // forward to observer observer.FICChangeEnsemble(ensemble); } std::string FICDecoder::ConvertLabelToUTF8(const FIC_LABEL& label, std::string* charset_name) { std::string result = CharsetTools::ConvertTextToUTF8(label.label, sizeof(label.label), label.charset, charset_name); // discard trailing spaces size_t last_pos = result.find_last_not_of(' '); if(last_pos != std::string::npos) result.resize(last_pos + 1); return result; } const size_t FICDecoder::uep_sizes[] = { 16, 21, 24, 29, 35, 24, 29, 35, 42, 52, 29, 35, 42, 52, 32, 42, 48, 58, 70, 40, 52, 58, 70, 84, 48, 58, 70, 84, 104, 58, 70, 84, 104, 64, 84, 96, 116, 140, 80, 104, 116, 140, 168, 96, 116, 140, 168, 208, 116, 140, 168, 208, 232, 128, 168, 192, 232, 280, 160, 208, 280, 192, 280, 416 }; const int FICDecoder::uep_pls[] = { 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 2, 5, 3, 1 }; const int FICDecoder::uep_bitrates[] = { 32, 32, 32, 32, 32, 48, 48, 48, 48, 48, 56, 56, 56, 56, 64, 64, 64, 64, 64, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 112, 112, 112, 112, 128, 128, 128, 128, 128, 160, 160, 160, 160, 160, 192, 192, 192, 192, 192, 224, 224, 224, 224, 224, 256, 256, 256, 256, 256, 320, 320, 320, 384, 384, 384 }; const int FICDecoder::eep_a_size_factors[] = {12, 8, 6, 4}; const int FICDecoder::eep_b_size_factors[] = {27, 21, 18, 15}; const char* FICDecoder::languages_0x00_to_0x2B[] = { "unknown/not applicable", "Albanian", "Breton", "Catalan", "Croatian", "Welsh", "Czech", "Danish", "German", "English", "Spanish", "Esperanto", "Estonian", "Basque", "Faroese", "French", "Frisian", "Irish", "Gaelic", "Galician", "Icelandic", "Italian", "Sami", "Latin", "Latvian", "Luxembourgian", "Lithuanian", "Hungarian", "Maltese", "Dutch", "Norwegian", "Occitan", "Polish", "Portuguese", "Romanian", "Romansh", "Serbian", "Slovak", "Slovene", "Finnish", "Swedish", "Turkish", "Flemish", "Walloon" }; const char* FICDecoder::languages_0x7F_downto_0x45[] = { "Amharic", "Arabic", "Armenian", "Assamese", "Azerbaijani", "Bambora", "Belorussian", "Bengali", "Bulgarian", "Burmese", "Chinese", "Chuvash", "Dari", "Fulani", "Georgian", "Greek", "Gujurati", "Gurani", "Hausa", "Hebrew", "Hindi", "Indonesian", "Japanese", "Kannada", "Kazakh", "Khmer", "Korean", "Laotian", "Macedonian", "Malagasay", "Malaysian", "Moldavian", "Marathi", "Ndebele", "Nepali", "Oriya", "Papiamento", "Persian", "Punjabi", "Pushtu", "Quechua", "Russian", "Rusyn", "Serbo-Croat", "Shona", "Sinhalese", "Somali", "Sranan Tongo", "Swahili", "Tadzhik", "Tamil", "Tatar", "Telugu", "Thai", "Ukranian", "Urdu", "Uzbek", "Vietnamese", "Zulu" }; const char* FICDecoder::ptys_rds_0x00_to_0x1D[] = { "No programme type", "News", "Current Affairs", "Information", "Sport", "Education", "Drama", "Culture", "Science", "Varied", "Pop Music", "Rock Music", "Easy Listening Music", "Light Classical", "Serious Classical", "Other Music", "Weather/meteorology", "Finance/Business", "Children's programmes", "Social Affairs", "Religion", "Phone In", "Travel", "Leisure", "Jazz Music", "Country Music", "National Music", "Oldies Music", "Folk Music", "Documentary" }; const char* FICDecoder::ptys_rbds_0x00_to_0x1D[] = { "No program type", "News", "Information", "Sports", "Talk", "Rock", "Classic Rock", "Adult Hits", "Soft Rock", "Top 40", "Country", "Oldies", "Soft", "Nostalgia", "Jazz", "Classical", "Rhythm and Blues", "Soft Rhythm and Blues", "Foreign Language", "Religious Music", "Religious Talk", "Personality", "Public", "College", "(rfu)", "(rfu)", "(rfu)", "(rfu)", "(rfu)", "Weather" }; const char* FICDecoder::asu_types_0_to_10[] = { "Alarm", "Road Traffic flash", "Transport flash", "Warning/Service", "News flash", "Area weather flash", "Event announcement", "Special event", "Programme Information", "Sport report", "Financial report" }; std::string FICDecoder::ConvertLanguageToString(const int value) { if(value >= 0x00 && value <= 0x2B) return languages_0x00_to_0x2B[value]; if(value == 0x40) return "background sound/clean feed"; if(value >= 0x45 && value <= 0x7F) return languages_0x7F_downto_0x45[0x7F - value]; return "unknown (" + std::to_string(value) + ")"; } std::string FICDecoder::ConvertLTOToString(const int value) { // just to silence recent GCC's truncation warnings int lto_value = value % 0x3F; char lto_string[7]; snprintf(lto_string, sizeof(lto_string), "%+03d:%02d", lto_value / 2, (lto_value % 2) ? 30 : 0); return lto_string; } std::string FICDecoder::ConvertInterTableIDToString(const int value) { switch(value) { case 0x01: return "RDS PTY"; case 0x02: return "RBDS PTY"; default: return "unknown"; } } std::string FICDecoder::ConvertDateTimeToString(FIC_DAB_DT utc_dt, const int lto, bool output_ms) { const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; // if desired, apply LTO if(lto) utc_dt.dt.tm_min += lto * 30; // normalize time (apply LTO, set day of week) if(mktime(&utc_dt.dt) == (time_t) -1) throw std::runtime_error("FICDecoder: error while normalizing date/time"); std::string result; char s[11]; strftime(s, sizeof(s), "%F", &utc_dt.dt); result += std::string(s) + ", " + weekdays[utc_dt.dt.tm_wday] + " - "; if(!utc_dt.IsMsNone()) { // long form strftime(s, sizeof(s), "%T", &utc_dt.dt); result += s; if(output_ms) { snprintf(s, sizeof(s), ".%03d", utc_dt.ms); result += s; } } else { // short form strftime(s, sizeof(s), "%R", &utc_dt.dt); result += s; } return result; } std::string FICDecoder::ConvertPTYToString(const int value, const int inter_table_id) { switch(inter_table_id) { case 0x01: return value <= 0x1D ? ptys_rds_0x00_to_0x1D[value] : "(not used)"; case 0x02: return value <= 0x1D ? ptys_rbds_0x00_to_0x1D[value] : "(not used)"; default: return "(unknown)"; } } std::string FICDecoder::ConvertASuTypeToString(const int value) { if(value >= 0 && value <= 10) return asu_types_0_to_10[value]; return "unknown (" + std::to_string(value) + ")"; } std::string FICDecoder::DeriveShortLabelUTF8(const std::string& long_label, uint16_t short_label_mask) { std::string short_label; for(size_t i = 0; i < long_label.length(); i++) // consider discarded trailing spaces if(short_label_mask & (0x8000 >> i)) short_label += StringTools::UTF8Substr(long_label, i, 1); return short_label; } Opendigitalradio-ODR-DabMod-f7eedef/src/FigParser.h000066400000000000000000000300041475762153200222400ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Most parts of this file are taken from dablin, Copyright (C) 2015-2022 Stefan Pöschel Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #include #include #include #include #include #include #include #include #include #include #include struct FIG0_HEADER { bool cn; bool oe; bool pd; int extension; FIG0_HEADER(uint8_t data) : cn(data & 0x80), oe(data & 0x40), pd(data & 0x20), extension(data & 0x1F) {} }; struct FIG1_HEADER { int charset; bool oe; int extension; FIG1_HEADER(uint8_t data) : charset(data >> 4), oe(data & 0x08), extension(data & 0x07) {} }; struct FIC_LABEL { int charset; uint8_t label[16]; uint16_t short_label_mask; static const int charset_none = -1; bool IsNone() const {return charset == charset_none;} FIC_LABEL() : charset(charset_none), short_label_mask(0x0000) { memset(label, 0x00, sizeof(label)); } bool operator==(const FIC_LABEL & fic_label) const { return charset == fic_label.charset && !memcmp(label, fic_label.label, sizeof(label)) && short_label_mask == fic_label.short_label_mask; } bool operator!=(const FIC_LABEL & fic_label) const { return !(*this == fic_label); } }; struct FIC_SUBCHANNEL { size_t start; size_t size; std::string pl; int bitrate; int language; static const int language_none = -1; bool IsNone() const {return pl.empty() && language == language_none;} FIC_SUBCHANNEL() : start(0), size(0), bitrate(-1), language(language_none) {} bool operator==(const FIC_SUBCHANNEL & fic_subchannel) const { return start == fic_subchannel.start && size == fic_subchannel.size && pl == fic_subchannel.pl && bitrate == fic_subchannel.bitrate && language == fic_subchannel.language; } bool operator!=(const FIC_SUBCHANNEL & fic_subchannel) const { return !(*this == fic_subchannel); } }; struct FIC_ASW_CLUSTER { uint16_t asw_flags; int subchid; static const int asw_flags_none = 0x0000; static const int subchid_none = -1; bool IsNone() const {return subchid == subchid_none;} FIC_ASW_CLUSTER() : asw_flags(asw_flags_none), subchid(subchid_none) {} bool operator==(const FIC_ASW_CLUSTER & fic_asw_cluster) const { return asw_flags == fic_asw_cluster.asw_flags && subchid == fic_asw_cluster.subchid; } bool operator!=(const FIC_ASW_CLUSTER & fic_asw_cluster) const { return !(*this == fic_asw_cluster); } }; typedef std::map asw_clusters_t; struct FIC_DAB_DT { struct tm dt; int ms; static const int none = -1; bool IsNone() const {return dt.tm_year == none;} static const int ms_none = -1; bool IsMsNone() const {return ms == ms_none;} FIC_DAB_DT() : ms(ms_none) { dt.tm_year = none; } bool operator==(const FIC_DAB_DT & fic_dab_dt) const { return ms == fic_dab_dt.ms && dt.tm_sec == fic_dab_dt.dt.tm_sec && dt.tm_min == fic_dab_dt.dt.tm_min && dt.tm_hour == fic_dab_dt.dt.tm_hour && dt.tm_mday == fic_dab_dt.dt.tm_mday && dt.tm_mon == fic_dab_dt.dt.tm_mon && dt.tm_year == fic_dab_dt.dt.tm_year; } bool operator!=(const FIC_DAB_DT & fic_dab_dt) const { return !(*this == fic_dab_dt); } }; struct FIC_ENSEMBLE { int eid; bool al_flag; FIC_LABEL label; int ecc; int lto; int inter_table_id; asw_clusters_t asw_clusters; static const int eid_none = -1; bool IsNone() const {return eid == eid_none;} static const int ecc_none = -1; static const int lto_none = -100; static const int inter_table_id_none = -1; FIC_ENSEMBLE() : eid(eid_none), al_flag(false), ecc(ecc_none), lto(lto_none), inter_table_id(inter_table_id_none) {} bool operator==(const FIC_ENSEMBLE & ensemble) const { return eid == ensemble.eid && al_flag == ensemble.al_flag && label == ensemble.label && ecc == ensemble.ecc && lto == ensemble.lto && inter_table_id == ensemble.inter_table_id && asw_clusters == ensemble.asw_clusters; } bool operator!=(const FIC_ENSEMBLE & ensemble) const { return !(*this == ensemble); } }; struct AUDIO_SERVICE { int subchid; bool dab_plus; static const int subchid_none = -1; bool IsNone() const {return subchid == subchid_none;} AUDIO_SERVICE() : AUDIO_SERVICE(subchid_none, false) {} AUDIO_SERVICE(int subchid, bool dab_plus) : subchid(subchid), dab_plus(dab_plus) {} bool operator==(const AUDIO_SERVICE & audio_service) const { return subchid == audio_service.subchid && dab_plus == audio_service.dab_plus; } bool operator!=(const AUDIO_SERVICE & audio_service) const { return !(*this == audio_service); } }; typedef std::map audio_comps_t; typedef std::map comp_defs_t; typedef std::map comp_labels_t; typedef std::vector ua_data_t; typedef std::map comp_sls_uas_t; typedef std::set cids_t; struct FIC_SERVICE { int sid; int pri_comp_subchid; FIC_LABEL label; int pty_static; int pty_dynamic; uint16_t asu_flags; cids_t cids; // components audio_comps_t audio_comps; // from FIG 0/2 : SubChId -> AUDIO_SERVICE comp_defs_t comp_defs; // from FIG 0/8 : SCIdS -> SubChId comp_labels_t comp_labels; // from FIG 1/4 : SCIdS -> FIC_LABEL comp_sls_uas_t comp_sls_uas; // from FIG 0/13: SCIdS -> UA data static const int sid_none = -1; bool IsNone() const {return sid == sid_none;} static const int pri_comp_subchid_none = -1; bool HasNoPriCompSubchid() const {return pri_comp_subchid == pri_comp_subchid_none;} static const int pty_none = -1; static const int asu_flags_none = 0x0000; FIC_SERVICE() : sid(sid_none), pri_comp_subchid(pri_comp_subchid_none), pty_static(pty_none), pty_dynamic(pty_none), asu_flags(asu_flags_none) {} }; struct LISTED_SERVICE { int sid; int scids; FIC_SUBCHANNEL subchannel; AUDIO_SERVICE audio_service; FIC_LABEL label; int pty_static; int pty_dynamic; int sls_app_type; uint16_t asu_flags; cids_t cids; int pri_comp_subchid; // only used for sorting bool multi_comps; static const int sid_none = -1; bool IsNone() const {return sid == sid_none;} static const int scids_none = -1; bool IsPrimary() const {return scids == scids_none;} static const int pty_none = -1; static const int asu_flags_none = 0x0000; static const int sls_app_type_none = -1; bool HasSLS() const {return sls_app_type != sls_app_type_none;} LISTED_SERVICE() : sid(sid_none), scids(scids_none), pty_static(pty_none), pty_dynamic(pty_none), sls_app_type(sls_app_type_none), asu_flags(asu_flags_none), pri_comp_subchid(AUDIO_SERVICE::subchid_none), multi_comps(false) {} bool operator<(const LISTED_SERVICE & service) const { if(pri_comp_subchid != service.pri_comp_subchid) return pri_comp_subchid < service.pri_comp_subchid; if(sid != service.sid) return sid < service.sid; return scids < service.scids; } }; typedef std::map fic_services_t; typedef std::map fic_subchannels_t; // --- FICDecoderObserver ----------------------------------------------------------------- class FICDecoderObserver { public: virtual ~FICDecoderObserver() {} std::optional ensemble; std::optional utc_dt; std::map services; virtual void FICChangeEnsemble(const FIC_ENSEMBLE& ensemble); virtual void FICChangeService(const LISTED_SERVICE& service); virtual void FICChangeUTCDateTime(const FIC_DAB_DT& utc_dt); virtual void FICDiscardedFIB() {} }; // --- FICDecoder ----------------------------------------------------------------- class FICDecoder { private: bool verbose; void ProcessFIB(const uint8_t *data); void ProcessFIG0(const uint8_t *data, size_t len); void ProcessFIG0_0(const uint8_t *data, size_t len); void ProcessFIG0_1(const uint8_t *data, size_t len); void ProcessFIG0_2(const uint8_t *data, size_t len); void ProcessFIG0_5(const uint8_t *data, size_t len); void ProcessFIG0_8(const uint8_t *data, size_t len); void ProcessFIG0_9(const uint8_t *data, size_t len); void ProcessFIG0_10(const uint8_t *data, size_t len); void ProcessFIG0_13(const uint8_t *data, size_t len); void ProcessFIG0_17(const uint8_t *data, size_t len); void ProcessFIG0_18(const uint8_t *data, size_t len); void ProcessFIG0_19(const uint8_t *data, size_t len); void ProcessFIG1(const uint8_t *data, size_t len); void ProcessFIG1_0(uint16_t eid, const FIC_LABEL& label); void ProcessFIG1_1(uint16_t sid, const FIC_LABEL& label); void ProcessFIG1_4(uint16_t sid, int scids, const FIC_LABEL& label); FIC_SUBCHANNEL& GetSubchannel(int subchid); void UpdateSubchannel(int subchid); FIC_SERVICE& GetService(uint16_t sid); void UpdateService(const FIC_SERVICE& service); void UpdateListedService(const FIC_SERVICE& service, int scids, bool multi_comps); int GetSLSAppType(const ua_data_t& ua_data); FIC_ENSEMBLE ensemble; void UpdateEnsemble(); fic_services_t services; fic_subchannels_t subchannels; // from FIG 0/1: SubChId -> FIC_SUBCHANNEL FIC_DAB_DT utc_dt; bool utc_dt_long; static const size_t uep_sizes[]; static const int uep_pls[]; static const int uep_bitrates[]; static const int eep_a_size_factors[]; static const int eep_b_size_factors[]; static const char* languages_0x00_to_0x2B[]; static const char* languages_0x7F_downto_0x45[]; static const char* ptys_rds_0x00_to_0x1D[]; static const char* ptys_rbds_0x00_to_0x1D[]; static const char* asu_types_0_to_10[]; public: FICDecoder(bool verbose); void Process(const uint8_t *data, size_t len); void Reset(); FICDecoderObserver observer; static std::string ConvertLabelToUTF8(const FIC_LABEL& label, std::string* charset_name); static std::string ConvertLanguageToString(const int value); static std::string ConvertLTOToString(const int value); static std::string ConvertInterTableIDToString(const int value); static std::string ConvertDateTimeToString(FIC_DAB_DT utc_dt, const int lto, bool output_ms); static std::string ConvertPTYToString(const int value, const int inter_table_id); static std::string ConvertASuTypeToString(const int value); static std::string DeriveShortLabelUTF8(const std::string& long_label, uint16_t short_label_mask); }; Opendigitalradio-ODR-DabMod-f7eedef/src/Flowgraph.cpp000066400000000000000000000220101475762153200226400ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "Flowgraph.h" #include "PcDebug.h" #include "Log.h" #include #include #include #include #include #include using namespace std; using NodeIterator = std::vector >::iterator; using EdgeIterator = std::vector >::iterator; Node::Node(shared_ptr plugin) : myPlugin(plugin) { PDEBUG("Node::Node(plugin(%s): %p) @ %p\n", plugin->name(), plugin.get(), this); } Node::~Node() { PDEBUG("Node::~Node() @ %p\n", this); assert(myInputBuffers.size() == 0); assert(myOutputBuffers.size() == 0); } void Node::addOutputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md) { myOutputBuffers.push_back(buffer); myOutputMetadata.push_back(md); #if TRACE std::string fname = string(myPlugin->name()) + "-" + to_string(myDebugFiles.size()) + "-" + to_string((size_t)(void*)myPlugin.get()) + ".dat"; FILE* fd = fopen(fname.c_str(), "wb"); assert(fd != nullptr); myDebugFiles.push_back(fd); #endif } void Node::removeOutputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md) { auto it = std::find( myOutputBuffers.begin(), myOutputBuffers.end(), buffer); if (it != myOutputBuffers.end()) { #if TRACE size_t pos = std::distance(myOutputBuffers.begin(), it); auto fd_it = std::next(myDebugFiles.begin(), pos); fclose(*fd_it); myDebugFiles.erase(fd_it); #endif myOutputBuffers.erase(it); } auto mdit = std::find( myOutputMetadata.begin(), myOutputMetadata.end(), md); if (mdit != myOutputMetadata.end()) { myOutputMetadata.erase(mdit); } } void Node::addInputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md) { myInputBuffers.push_back(buffer); myInputMetadata.push_back(md); } void Node::removeInputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md) { auto it = std::find( myInputBuffers.begin(), myInputBuffers.end(), buffer); if (it != myInputBuffers.end()) { myInputBuffers.erase(it); } auto mdit = std::find( myInputMetadata.begin(), myInputMetadata.end(), md); if (mdit != myInputMetadata.end()) { myInputMetadata.erase(mdit); } } int Node::process() { PDEBUG("Node::process()\n"); PDEBUG(" Plugin name: %s (%p)\n", myPlugin->name(), myPlugin.get()); // the plugin process() wants vector // arguments. std::vector inBuffers; for (auto& buffer : myInputBuffers) { assert(buffer.get() != nullptr); inBuffers.push_back(buffer.get()); } std::vector outBuffers; for (auto& buffer : myOutputBuffers) { assert(buffer.get() != nullptr); outBuffers.push_back(buffer.get()); } int ret = myPlugin->process(inBuffers, outBuffers); // Collect all incoming metadata into a single vector meta_vec_t all_input_mds; for (auto& md_vec_sp : myInputMetadata) { if (md_vec_sp) { move(md_vec_sp->begin(), md_vec_sp->end(), back_inserter(all_input_mds)); md_vec_sp->clear(); } } auto mod_meta = dynamic_pointer_cast(myPlugin); if (mod_meta) { auto outputMetadata = mod_meta->process_metadata(all_input_mds); // Distribute the result metadata to all outputs for (auto& out_md : myOutputMetadata) { out_md->clear(); std::move(outputMetadata.begin(), outputMetadata.end(), std::back_inserter(*out_md)); } } else { // Propagate the unmodified input metadata to all outputs for (auto& out_md : myOutputMetadata) { out_md->clear(); std::move(all_input_mds.begin(), all_input_mds.end(), std::back_inserter(*out_md)); } } #if TRACE assert(myDebugFiles.size() == myOutputBuffers.size()); auto buf = myOutputBuffers.begin(); auto fd_it = myDebugFiles.begin(); for (size_t i = 0; i < myDebugFiles.size(); i++) { if (*buf) { Buffer& b = *buf->get(); FILE* fd = *fd_it; fwrite(b.getData(), b.getLength(), 1, fd); } ++buf; ++fd_it; } #endif return ret; } time_t Node::processTime() const { return myProcessTime; } void Node::addProcessTime(time_t time) { myProcessTime += time; } Edge::Edge(shared_ptr& srcNode, shared_ptr& dstNode) : mySrcNode(srcNode), myDstNode(dstNode) { PDEBUG("Edge::Edge(srcNode(%s): %p, dstNode(%s): %p) @ %p\n", srcNode->plugin()->name(), srcNode.get(), dstNode->plugin()->name(), dstNode.get(), this); myBuffer = make_shared(); myMetadata = make_shared >(); srcNode->addOutputBuffer(myBuffer, myMetadata); dstNode->addInputBuffer(myBuffer, myMetadata); } Edge::~Edge() { PDEBUG("Edge::~Edge() @ %p\n", this); if (myBuffer) { mySrcNode->removeOutputBuffer(myBuffer, myMetadata); myDstNode->removeInputBuffer(myBuffer, myMetadata); } } Flowgraph::Flowgraph(bool showProcessTime) : myShowProcessTime(showProcessTime) { PDEBUG("Flowgraph::Flowgraph() @ %p\n", this); } Flowgraph::~Flowgraph() { PDEBUG("Flowgraph::~Flowgraph() @ %p\n", this); if (myShowProcessTime and myProcessTime) { stringstream ss; ss << "Process time:\n"; char node_time_sz[1024] = {}; for (const auto &node : nodes) { snprintf(node_time_sz, 1023, " %30s: %10lld us (%2.2f %%)\n", node->plugin()->name(), (long long)node->processTime(), node->processTime() * 100.0 / myProcessTime); ss << node_time_sz; } snprintf(node_time_sz, 1023, " %30s: %10lld us (100.00 %%)\n", "total", (long long)myProcessTime); ss << node_time_sz; etiLog.level(debug) << ss.str(); } } void Flowgraph::connect(shared_ptr input, shared_ptr output) { PDEBUG("Flowgraph::connect(input(%s): %p, output(%s): %p)\n", input->name(), input.get(), output->name(), output.get()); NodeIterator inputNode; NodeIterator outputNode; for (inputNode = nodes.begin(); inputNode != nodes.end(); ++inputNode) { if ((*inputNode)->plugin() == input) { break; } } if (inputNode == nodes.end()) { inputNode = nodes.insert(nodes.end(), make_shared(input)); } for (outputNode = nodes.begin(); outputNode != nodes.end(); ++outputNode) { if ((*outputNode)->plugin() == output) { break; } } if (outputNode == nodes.end()) { outputNode = nodes.insert(nodes.end(), make_shared(output)); for (inputNode = nodes.begin(); inputNode != nodes.end(); ++inputNode) { if ((*inputNode)->plugin() == input) { break; } } } else if (inputNode > outputNode) { shared_ptr node = *outputNode; nodes.erase(outputNode); outputNode = nodes.insert(nodes.end(), node); for (inputNode = nodes.begin(); inputNode != nodes.end(); ++inputNode) { if ((*inputNode)->plugin() == input) { break; } } } assert((*inputNode)->plugin() == input); assert((*outputNode)->plugin() == output); edges.push_back(make_shared(*inputNode, *outputNode)); } bool Flowgraph::run() { PDEBUG("Flowgraph::run()\n"); timeval start, stop; time_t diff; gettimeofday(&start, NULL); for (const auto &node : nodes) { int ret = node->process(); PDEBUG(" ret: %i\n", ret); gettimeofday(&stop, NULL); diff = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; myProcessTime += diff; node->addProcessTime(diff); start = stop; if (!ret) { return false; } } return true; } Opendigitalradio-ODR-DabMod-f7eedef/src/Flowgraph.h000066400000000000000000000056321475762153200223200ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include #include #include #include using Metadata_vec_sptr = std::shared_ptr >; class Node { public: Node(std::shared_ptr plugin); ~Node(); Node(const Node&) = delete; Node& operator=(const Node&) = delete; std::shared_ptr plugin() { return myPlugin; } int process(); time_t processTime() const; void addProcessTime(time_t time); void addOutputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md); void removeOutputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md); void addInputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md); void removeInputBuffer(Buffer::sptr& buffer, Metadata_vec_sptr& md); protected: std::list myInputBuffers; std::list myOutputBuffers; std::list myInputMetadata; std::list myOutputMetadata; #if TRACE std::list myDebugFiles; #endif std::shared_ptr myPlugin; time_t myProcessTime = 0; }; class Edge { public: Edge(std::shared_ptr& src, std::shared_ptr& dst); ~Edge(); Edge(const Edge&) = delete; Edge& operator=(const Edge&) = delete; protected: std::shared_ptr mySrcNode; std::shared_ptr myDstNode; std::shared_ptr myBuffer; std::shared_ptr > myMetadata; }; class Flowgraph { public: Flowgraph(bool showProcessTime); virtual ~Flowgraph(); Flowgraph(const Flowgraph&) = delete; Flowgraph& operator=(const Flowgraph&) = delete; void connect(std::shared_ptr input, std::shared_ptr output); bool run(); protected: std::vector > nodes; std::vector > edges; time_t myProcessTime = 0; bool myShowProcessTime; }; Opendigitalradio-ODR-DabMod-f7eedef/src/FormatConverter.cpp000066400000000000000000000140721475762153200240400ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This flowgraph block converts complexf to signed integer. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "FormatConverter.h" #include "PcDebug.h" #include "Log.h" #include #include #include #include #if defined(__ARM_NEON) #include #endif FormatConverter::FormatConverter(bool input_is_complexfix_wide, const std::string& format_out) : ModCodec(), m_input_complexfix_wide(input_is_complexfix_wide), m_format_out(format_out) { } FormatConverter::~FormatConverter() { if ( #if defined(__ARM_NEON) not m_input_complexfix_wide #else true #endif ) { etiLog.level(debug) << "FormatConverter: " << m_num_clipped_samples.load() << " clipped"; } } /* Expect the input samples to be in the correct range for the required format */ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("FormatConverter::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); size_t num_clipped_samples = 0; if (m_input_complexfix_wide) { size_t sizeIn = dataIn->getLength() / sizeof(int32_t); if (m_format_out == "s16") { dataOut->setLength(sizeIn * sizeof(int16_t)); const int32_t *in = reinterpret_cast(dataIn->getData()); int16_t* out = reinterpret_cast(dataOut->getData()); constexpr int shift = 6; #if defined(__ARM_NEON) if (sizeIn % 4 != 0) { throw std::logic_error("Unexpected length not multiple of 4"); } for (size_t i = 0; i < sizeIn; i += 4) { int32x4_t input_vec = vld1q_s32(&in[i]); // Apply shift right, saturate on conversion to int16_t int16x4_t output_vec = vqshrn_n_s32(input_vec, shift); vst1_s16(&out[i], output_vec); } #else for (size_t i = 0; i < sizeIn; i++) { const int32_t val = in[i] >> shift; if (val < INT16_MIN) { out[i] = INT16_MIN; num_clipped_samples++; } else if (val > INT16_MAX) { out[i] = INT16_MAX; num_clipped_samples++; } else { out[i] = val; } } #endif } else { throw std::runtime_error("FormatConverter: Invalid fix format " + m_format_out); } } else { size_t sizeIn = dataIn->getLength() / sizeof(float); const float* in = reinterpret_cast(dataIn->getData()); if (m_format_out == "s16") { dataOut->setLength(sizeIn * sizeof(int16_t)); int16_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { if (in[i] < INT16_MIN) { out[i] = INT16_MIN; num_clipped_samples++; } else if (in[i] > INT16_MAX) { out[i] = INT16_MAX; num_clipped_samples++; } else { out[i] = in[i]; } } } else if (m_format_out == "u8") { dataOut->setLength(sizeIn * sizeof(int8_t)); uint8_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { const auto samp = in[i] + 128.0f; if (samp < 0) { out[i] = 0; num_clipped_samples++; } else if (samp > UINT8_MAX) { out[i] = UINT8_MAX; num_clipped_samples++; } else { out[i] = samp; } } } else if (m_format_out == "s8") { dataOut->setLength(sizeIn * sizeof(int8_t)); int8_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { if (in[i] < INT8_MIN) { out[i] = INT8_MIN; num_clipped_samples++; } else if (in[i] > INT8_MAX) { out[i] = INT8_MAX; num_clipped_samples++; } else { out[i] = in[i]; } } } else { throw std::runtime_error("FormatConverter: Invalid format " + m_format_out); } } m_num_clipped_samples.store(num_clipped_samples); return dataOut->getLength(); } const char* FormatConverter::name() { return "FormatConverter"; } size_t FormatConverter::get_num_clipped_samples() const { return m_num_clipped_samples.load(); } size_t FormatConverter::get_format_size(const std::string& format) { // Returns 2*sizeof(SAMPLE_TYPE) because we have I + Q if (format == "s16") { return 4; } else if (format == "u8") { return 2; } else if (format == "s8") { return 2; } else { throw std::runtime_error("FormatConverter: Invalid format " + format); } } Opendigitalradio-ODR-DabMod-f7eedef/src/FormatConverter.h000066400000000000000000000034761475762153200235130ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org This flowgraph block converts complexf to signed integer. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include class FormatConverter : public ModCodec { public: static size_t get_format_size(const std::string& format); // floating-point input allows output formats: s8, u8 and s16 // complexfix_wide input allows output formats: s16 // complexfix input is already in s16, and needs no converter FormatConverter(bool input_is_complexfix_wide, const std::string& format_out); virtual ~FormatConverter(); int process(Buffer* const dataIn, Buffer* dataOut); const char* name(); size_t get_num_clipped_samples() const; private: bool m_input_complexfix_wide; std::string m_format_out; std::atomic m_num_clipped_samples = 0; }; Opendigitalradio-ODR-DabMod-f7eedef/src/FrameMultiplexer.cpp000066400000000000000000000057171475762153200242130ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "FrameMultiplexer.h" #include #include #include #include FrameMultiplexer::FrameMultiplexer( const EtiSource& etiSource) : ModMux(), m_etiSource(etiSource) { } // dataIn[0] -> PRBS // dataIn[1+] -> subchannels int FrameMultiplexer::process(std::vector dataIn, Buffer* dataOut) { assert(dataIn.size() >= 1); assert(dataIn[0]->getLength() == 864 * 8); dataOut->setLength(dataIn[0]->getLength()); #ifdef TRACE fprintf(stderr, "FrameMultiplexer::process(dataIn:"); for (size_t i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %p", dataIn[i]); } fprintf(stderr, ", sizeIn:"); for (size_t i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %zu", dataIn[i]->getLength()); } fprintf(stderr, ", dataOut: %p, sizeOut: %zu)\n", dataOut, dataOut->getLength()); #endif uint8_t* out = reinterpret_cast(dataOut->getData()); std::vector::const_iterator in = dataIn.begin(); // Write PRBS memcpy(out, (*in)->getData(), (*in)->getLength()); ++in; // Write subchannel const auto subchannels = m_etiSource.getSubchannels(); if (subchannels.size() != dataIn.size() - 1) { throw FrameMultiplexerError( "FrameMultiplexer detected subchannel size change from " + std::to_string(dataIn.size() - 1) + " to " + std::to_string(subchannels.size())); } auto subchannel = subchannels.begin(); while (in != dataIn.end()) { if ((*subchannel)->framesizeCu() * 8 != (*in)->getLength()) { throw FrameMultiplexerError( "FrameMultiplexer detected invalid subchannel size! " + std::to_string((*subchannel)->framesizeCu() * 8) + " != " + std::to_string((*in)->getLength())); } size_t offset = (*subchannel)->startAddress() * 8; memcpy(&out[offset], (*in)->getData(), (*in)->getLength()); ++in; ++subchannel; } return dataOut->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/FrameMultiplexer.h000066400000000000000000000031571475762153200236540ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include "SubchannelSource.h" #include "EtiReader.h" #include #include class FrameMultiplexerError : public std::runtime_error { public: FrameMultiplexerError(const char* msg) : std::runtime_error(msg) {} FrameMultiplexerError(const std::string& msg) : std::runtime_error(msg) {} }; class FrameMultiplexer : public ModMux { public: FrameMultiplexer(const EtiSource& etiSource); int process(std::vector dataIn, Buffer* dataOut); const char* name() { return "FrameMultiplexer"; } protected: const EtiSource& m_etiSource; }; Opendigitalradio-ODR-DabMod-f7eedef/src/FrequencyInterleaver.cpp000066400000000000000000000103071475762153200250570ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "FrequencyInterleaver.h" #include "PcDebug.h" #include #include #include #include FrequencyInterleaver::FrequencyInterleaver(size_t mode, bool fixedPoint) : ModCodec(), m_fixedPoint(fixedPoint) { PDEBUG("FrequencyInterleaver::FrequencyInterleaver(%zu) @ %p\n", mode, this); size_t num; size_t alpha = 13; size_t beta; switch (mode) { case 1: m_carriers = 1536; num = 2048; beta = 511; break; case 2: m_carriers = 384; num = 512; beta = 127; break; case 3: m_carriers = 192; num = 256; beta = 63; break; case 0: case 4: m_carriers = 768; num = 1024; beta = 255; break; default: PDEBUG("Carriers: %zu\n", (m_carriers >> 1) << 1); throw std::runtime_error("FrequencyInterleaver: invalid dab mode"); } const int ret = posix_memalign((void**)(&m_indices), 16, m_carriers * sizeof(size_t)); if (ret != 0) { throw std::runtime_error("memory allocation failed: " + std::to_string(ret)); } size_t *index = m_indices; size_t perm = 0; PDEBUG("i: %4u, R: %4u\n", 0, 0); for (size_t j = 1; j < num; ++j) { perm = (alpha * perm + beta) & (num - 1); if (perm >= ((num - m_carriers) / 2) && perm <= (num - (num - m_carriers) / 2) && perm != (num / 2)) { PDEBUG("i: %4zu, R: %4zu, d: %4zu, n: %4zu, k: %5zi, index: %zu\n", j, perm, perm, index - m_indices, perm - num / 2, perm > num / 2 ? perm - (1 + (num / 2)) : perm + (m_carriers - (num / 2))); *(index++) = perm > num / 2 ? perm - (1 + (num / 2)) : perm + (m_carriers - (num / 2)); } else { PDEBUG("i: %4zu, R: %4zu\n", j, perm); } } } FrequencyInterleaver::~FrequencyInterleaver() { PDEBUG("FrequencyInterleaver::~FrequencyInterleaver() @ %p\n", this); free(m_indices); } template void do_process(Buffer* const dataIn, Buffer* dataOut, size_t carriers, const size_t * const indices) { const T* in = reinterpret_cast(dataIn->getData()); T* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(T); if (sizeIn % carriers != 0) { throw std::runtime_error( "FrequencyInterleaver::process input size not valid!"); } for (size_t i = 0; i < sizeIn;) { // memset(out, 0, m_carriers * sizeof(T)); for (size_t j = 0; j < carriers; i += 4, j += 4) { out[indices[j]] = in[i]; out[indices[j + 1]] = in[i + 1]; out[indices[j + 2]] = in[i + 2]; out[indices[j + 3]] = in[i + 3]; } out += carriers; } } int FrequencyInterleaver::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("FrequencyInterleaver::process" "(dataIn: %p, sizeIn: %zu, dataOut: %p, sizeOut: %zu)\n", dataIn, dataIn->getLength(), dataOut, dataOut->getLength()); dataOut->setLength(dataIn->getLength()); if (m_fixedPoint) { do_process(dataIn, dataOut, m_carriers, m_indices); } else { do_process(dataIn, dataOut, m_carriers, m_indices); } return 1; } Opendigitalradio-ODR-DabMod-f7eedef/src/FrequencyInterleaver.h000066400000000000000000000026511475762153200245270ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include class FrequencyInterleaver : public ModCodec { public: FrequencyInterleaver(size_t mode, bool fixedPoint); virtual ~FrequencyInterleaver(); FrequencyInterleaver(const FrequencyInterleaver&) = delete; FrequencyInterleaver& operator=(const FrequencyInterleaver&) = delete; int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "FrequencyInterleaver"; } protected: bool m_fixedPoint; size_t m_carriers; size_t *m_indices; }; Opendigitalradio-ODR-DabMod-f7eedef/src/GainControl.cpp000066400000000000000000000447311475762153200231440ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "GainControl.h" #include "PcDebug.h" #include #include #include #include #ifdef __SSE__ # include union u128_union_t { __m128 m; float f[4]; }; #endif using namespace std; static float var_variance; GainControl::GainControl(size_t framesize, GainMode& gainMode, float& digGain, float normalise, float& varVariance) : PipelinedModCodec(), RemoteControllable("gain"), #ifdef __SSE__ m_frameSize(framesize * sizeof(complexf) / sizeof(__m128)), #else // !__SSE__ m_frameSize(framesize), #endif m_digGain(digGain), m_normalise(normalise), m_var_variance_rc(varVariance), m_gainmode(gainMode), m_mutex() { PDEBUG("GainControl::GainControl(%zu, %zu) @ %p\n", framesize, (size_t)m_gainmode, this); /* register the parameters that can be remote controlled */ RC_ADD_PARAMETER(digital, "Digital Gain"); RC_ADD_PARAMETER(mode, "Gainmode (fix|max|var)"); RC_ADD_PARAMETER(var, "Variance setting for gainmode var (default: 4)"); start_pipeline_thread(); } GainControl::~GainControl() { stop_pipeline_thread(); } int GainControl::internal_process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("GainControl::process" "(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); dataOut->setLength(dataIn->getLength()); #ifdef __SSE__ __m128 (*computeGain)(const __m128* in, size_t sizeIn); #else float (*computeGain)(const complexf* in, size_t sizeIn); #endif { std::lock_guard lock(m_mutex); var_variance = m_var_variance_rc; switch (m_gainmode) { case GainMode::GAIN_FIX: PDEBUG("Gain mode: fix\n"); computeGain = computeGainFix; break; case GainMode::GAIN_MAX: PDEBUG("Gain mode: max\n"); computeGain = computeGainMax; break; case GainMode::GAIN_VAR: PDEBUG("Gain mode: var\n"); computeGain = computeGainVar; break; default: throw std::logic_error("Internal error: invalid gainmode"); } } const float constantGain = m_normalise * m_digGain; #ifdef __SSE__ const __m128* in = reinterpret_cast(dataIn->getData()); __m128* out = reinterpret_cast<__m128*>(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(__m128); size_t sizeOut = dataOut->getLength() / sizeof(__m128); u128_union_t gain128; if ((sizeIn % m_frameSize) != 0) { PDEBUG("%zu != %zu\n", sizeIn, m_frameSize); throw std::runtime_error("GainControl::process input size not valid!"); } const auto constantGain4 = _mm_set1_ps(constantGain); for (size_t i = 0; i < sizeIn; i += m_frameSize) { // Do not apply gain computation to the NULL symbol, which either // is blank or contains TII. Apply the gain calculation from the next // symbol on the NULL symbol to get consistent TII power. if (i > 0) { gain128.m = computeGain(in, m_frameSize); } else { gain128.m = computeGain(in + m_frameSize, m_frameSize); } gain128.m = _mm_mul_ps(gain128.m, constantGain4); PDEBUG("********** Gain: %10f **********\n", gain128.f[0]); for (size_t sample = 0; sample < m_frameSize; ++sample) { out[sample] = _mm_mul_ps(in[sample], gain128.m); } in += m_frameSize; out += m_frameSize; } #else // !__SSE__ const complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); size_t sizeOut = dataOut->getLength() / sizeof(complexf); float gain; if ((sizeIn % m_frameSize) != 0) { PDEBUG("%zu != %zu\n", sizeIn, m_frameSize); throw std::runtime_error( "GainControl::process input size not valid!"); } for (size_t i = 0; i < sizeIn; i += m_frameSize) { // Do not apply gain computation to the NULL symbol, which either // is blank or contains TII. Apply the gain calculation from the next // symbol on the NULL symbol to get consistent TII power. gain = constantGain * (i > 0 ? computeGain(in, m_frameSize) : computeGain(in + m_frameSize, m_frameSize)); PDEBUG("********** Gain: %10f **********\n", gain); //////////////////////////////////////////////////////////////////////// // Applying gain to output data //////////////////////////////////////////////////////////////////////// for (size_t sample = 0; sample < m_frameSize; ++sample) { out[sample] = in[sample] * gain; } in += m_frameSize; out += m_frameSize; } #endif // __SSE__ return sizeOut; } #ifdef __SSE__ __m128 GainControl::computeGainFix(const __m128* in, size_t sizeIn) { return _mm_set1_ps(512.0f); } __m128 GainControl::computeGainMax(const __m128* in, size_t sizeIn) { u128_union_t gain128; u128_union_t min128; u128_union_t max128; u128_union_t tmp128; static const __m128 factor128 = _mm_set1_ps(0x7fff); //////////////////////////////////////////////////////////////////////// // Computing max, min and average //////////////////////////////////////////////////////////////////////// min128.m = _mm_set1_ps(__FLT_MAX__); max128.m = _mm_set1_ps(__FLT_MIN__); for (size_t sample = 0; sample < sizeIn; ++sample) { min128.m = _mm_min_ps(in[sample], min128.m); max128.m = _mm_max_ps(in[sample], max128.m); } // Merging min tmp128.m = _mm_shuffle_ps(min128.m, min128.m, _MM_SHUFFLE(0, 1, 2, 3)); min128.m = _mm_min_ps(min128.m, tmp128.m); tmp128.m = _mm_shuffle_ps(min128.m, min128.m, _MM_SHUFFLE(1, 0, 3, 2)); min128.m = _mm_min_ps(min128.m, tmp128.m); PDEBUG("********** Min: %10f **********\n", min128.f[0]); // Merging max tmp128.m = _mm_shuffle_ps(max128.m, max128.m, _MM_SHUFFLE(0, 1, 2, 3)); max128.m = _mm_max_ps(max128.m, tmp128.m); tmp128.m = _mm_shuffle_ps(max128.m, max128.m, _MM_SHUFFLE(1, 0, 3, 2)); max128.m = _mm_max_ps(max128.m, tmp128.m); PDEBUG("********** Max: %10f **********\n", max128.f[0]); //////////////////////////////////////////////////////////////////////////// // Computing gain //////////////////////////////////////////////////////////////////////////// // max = max(-min, max) max128.m = _mm_max_ps(_mm_mul_ps(min128.m, _mm_set1_ps(-1.0f)), max128.m); // Detect NULL if ((int)max128.f[0] != 0) { gain128.m = _mm_div_ps(factor128, max128.m); } else { gain128.m = _mm_set1_ps(1.0f); } return gain128.m; } __m128 GainControl::computeGainVar(const __m128* in, size_t sizeIn) { u128_union_t gain128; u128_union_t mean128; u128_union_t var128; u128_union_t tmp128; static const __m128 factor128 = _mm_set1_ps(0x7fff); mean128.m = _mm_setzero_ps(); for (size_t sample = 0; sample < sizeIn; ++sample) { __m128 delta128 = _mm_sub_ps(in[sample], mean128.m); __m128 i128 = _mm_set1_ps(sample + 1); __m128 q128 = _mm_div_ps(delta128, i128); mean128.m = _mm_add_ps(mean128.m, q128); /* tmp128.m = in[sample]; printf("S %zu, %.2f+%.2fj\t", sample, tmp128.f[0], tmp128.f[1]); printf(": %.2f+%.2fj\n", mean128.f[0], mean128.f[1]); printf("S %zu, %.2f+%.2fj\t", sample, tmp128.f[2], tmp128.f[3]); printf(": %.2f+%.2fj\n", mean128.f[2], mean128.f[3]); */ } // Merging average tmp128.m = _mm_shuffle_ps(mean128.m, mean128.m, _MM_SHUFFLE(1, 0, 3, 2)); mean128.m = _mm_add_ps(mean128.m, tmp128.m); mean128.m = _mm_mul_ps(mean128.m, _mm_set1_ps(0.5f)); PDEBUG("********** Mean: %10f + %10fj %10f + %10fj **********\n", mean128.f[0], mean128.f[1], mean128.f[2], mean128.f[3]); //////////////////////////////////////////////////////////////////////// // Computing standard deviation //////////////////////////////////////////////////////////////////////// var128.m = _mm_setzero_ps(); for (size_t sample = 0; sample < sizeIn; ++sample) { __m128 diff128 = _mm_sub_ps(in[sample], mean128.m); __m128 delta128 = _mm_sub_ps(_mm_mul_ps(diff128, diff128), var128.m); __m128 i128 = _mm_set1_ps(sample + 1); __m128 q128 = _mm_div_ps(delta128, i128); var128.m = _mm_add_ps(var128.m, q128); /* u128_union_t udiff128; udiff128.m = diff128; u128_union_t udelta128; udelta128.m = delta128; for (int off=0; off<4; off+=2) { printf("S %zu, %.2f+%.2fj\t", sample, udiff128.f[off], udiff128.f[1+off]); printf(": %.2f+%.2fj\t", udelta128.f[off], udelta128.f[1+off]); printf(": %.2f+%.2fj\n", var128.f[off], var128.f[1+off]); } */ } PDEBUG("********** Vars: %10f + %10fj, %10f + %10fj **********\n", var128.f[0], var128.f[1], var128.f[2], var128.f[3]); // Merging standard deviations tmp128.m = _mm_shuffle_ps(var128.m, var128.m, _MM_SHUFFLE(1, 0, 3, 2)); var128.m = _mm_add_ps(var128.m, tmp128.m); var128.m = _mm_mul_ps(var128.m, _mm_set1_ps(0.5f)); var128.m = _mm_sqrt_ps(var128.m); PDEBUG("********** Var: %10f + %10fj, %10f + %10fj **********\n", var128.f[0], var128.f[1], var128.f[2], var128.f[3]); var128.m = _mm_mul_ps(var128.m, _mm_set1_ps(var_variance)); PDEBUG("********** 4*Var: %10f + %10fj, %10f + %10fj **********\n", var128.f[0], var128.f[1], var128.f[2], var128.f[3]); //////////////////////////////////////////////////////////////////////////// // Computing gain //////////////////////////////////////////////////////////////////////////// // gain = factor128 / max(real, imag) // Detect NULL if ((int)var128.f[0] != 0) { gain128.m = _mm_div_ps(factor128, _mm_max_ps(var128.m, _mm_shuffle_ps(var128.m, var128.m, _MM_SHUFFLE(2, 3, 0, 1)))); } else { gain128.m = _mm_set1_ps(1.0f); } return gain128.m; } #else // !__SSE__ float GainControl::computeGainFix(const complexf* in, size_t sizeIn) { return 512.0f; } float GainControl::computeGainMax(const complexf* in, size_t sizeIn) { float gain; float min; float max; static const float factor = 0x7fff; //////////////////////////////////////////////////////////////////////// // Computing max, min and average //////////////////////////////////////////////////////////////////////// min = __FLT_MAX__; max = __FLT_MIN__; for (size_t sample = 0; sample < sizeIn; ++sample) { if (in[sample].real() < min) { min = in[sample].real(); } if (in[sample].real() > max) { max = in[sample].real(); } if (in[sample].imag() < min) { min = in[sample].imag(); } if (in[sample].imag() > max) { max = in[sample].imag(); } } PDEBUG("********** Min: %10f **********\n", min); PDEBUG("********** Max: %10f **********\n", max); //////////////////////////////////////////////////////////////////////////// // Computing gain //////////////////////////////////////////////////////////////////////////// // gain = factor128 / max(-min, max) min = -min; if (min > max) { max = min; } // Detect NULL if ((int)max != 0) { gain = factor / max; } else { gain = 1.0f; } return gain; } float GainControl::computeGainVar(const complexf* in, size_t sizeIn) { complexf mean; /* The variance calculation is a bit strange, because we * emulate the exact same functionality as the SSE code, * which is the most used one. * * TODO: verify that this actually corresponds to the * gain mode suggested in EN 300 798 Clause 5.3 Numerical Range. */ complexf var1; complexf var2; static const float factor = 0x7fff; mean = complexf(0.0f, 0.0f); for (size_t sample = 0; sample < sizeIn; ++sample) { const complexf delta = in[sample] - mean; const float i = sample + 1; const complexf q = delta / i; mean += q; /* printf("F %zu, %.2f+%.2fj\t", sample, in[sample].real(), in[sample].imag()); printf(": %.2f+%.2fj\n", mean.real(), mean.imag()); */ } PDEBUG("********** Mean: %10f + %10fj **********\n", mean.real(), mean.imag()); //////////////////////////////////////////////////////////////////////// // Computing standard deviation //////////////////////////////////////////////////////////////////////// var1 = complexf(0.0f, 0.0f); var2 = complexf(0.0f, 0.0f); for (size_t sample = 0; sample < sizeIn; ++sample) { const complexf diff = in[sample] - mean; complexf delta; complexf q; float i = (sample/2) + 1; if (sample % 2 == 0) { delta = complexf(diff.real() * diff.real(), diff.imag() * diff.imag()) - var1; q = delta / i; var1 += q; } else { delta = complexf(diff.real() * diff.real(), diff.imag() * diff.imag()) - var2; q = delta / i; var2 += q; } /* printf("F %zu, %.2f+%.2fj\t", sample, diff.real(), diff.imag()); printf(": %.2f+%.2fj\t", delta.real(), delta.imag()); printf(": %.2f+%.2fj\t", var1.real(), var1.imag()); printf(": %.2f+%.2fj\n", var2.real(), var2.imag()); */ } PDEBUG("********** Vars: %10f + %10fj, %10f + %10fj **********\n", var1.real(), var1.imag(), var2.real(), var2.imag()); // Merge standard deviations in the same way the SSE version does it complexf tmpvar = (var1 + var2) * 0.5f; complexf var(sqrt(tmpvar.real()), sqrt(tmpvar.imag())); PDEBUG("********** Var: %10f + %10fj **********\n", var.real(), var.imag()); var = var * var_variance; PDEBUG("********** 4*Var: %10f + %10fj **********\n", var.real(), var.imag()); //////////////////////////////////////////////////////////////////////////// // Computing gain //////////////////////////////////////////////////////////////////////////// float gain = var.real(); // gain = factor128 / max(real, imag) if (var.imag() > gain) { gain = var.imag(); } // Ignore zero variance samples and apply no gain if ((int)gain == 0) { gain = 1.0f; } else { gain = factor / gain; } return gain; } #endif // !__SSE__ void GainControl::set_parameter(const string& parameter, const string& value) { stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "digital") { float new_factor; ss >> new_factor; m_digGain = new_factor; } else if (parameter == "mode") { string new_mode; ss >> new_mode; std::transform(new_mode.begin(), new_mode.end(), new_mode.begin(), [](const char c) { return std::tolower(c); } ); GainMode m; if (new_mode == "fix") { m = GainMode::GAIN_FIX; } else if (new_mode == "max") { m = GainMode::GAIN_MAX; } else if (new_mode == "var") { m = GainMode::GAIN_VAR; } else { throw ParameterError("Gainmode " + new_mode + " unknown"); } { std::lock_guard lock(m_mutex); m_gainmode = m; } } else if (parameter == "var") { float newvar = 0; ss >> newvar; { std::lock_guard lock(m_mutex); m_var_variance_rc = newvar; } } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const string GainControl::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "digital") { ss << std::fixed << m_digGain; } else if (parameter == "mode") { switch (m_gainmode) { case GainMode::GAIN_FIX: ss << "fix"; break; case GainMode::GAIN_MAX: ss << "max"; break; case GainMode::GAIN_VAR: ss << "var"; break; } } else if (parameter == "var") { ss << std::fixed << m_var_variance_rc; } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t GainControl::get_all_values() const { json::map_t map; map["digital"].v = m_digGain; switch (m_gainmode) { case GainMode::GAIN_FIX: map["mode"].v = "fix"; break; case GainMode::GAIN_MAX: map["mode"].v = "max"; break; case GainMode::GAIN_VAR: map["mode"].v = "var"; break; } map["var"].v = m_var_variance_rc; return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/GainControl.h000066400000000000000000000054601475762153200226050ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include "RemoteControl.h" #include #include #include #ifdef __SSE__ # include #endif enum class GainMode { GAIN_FIX = 0, GAIN_MAX = 1, GAIN_VAR = 2 }; class GainControl : public PipelinedModCodec, public RemoteControllable { public: GainControl(size_t framesize, GainMode& gainMode, float& digGain, float normalise, float& varVariance); virtual ~GainControl(); GainControl(const GainControl&) = delete; GainControl& operator=(const GainControl&) = delete; const char* name() override { return "GainControl"; } /* Functions for the remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; protected: virtual int internal_process( Buffer* const dataIn, Buffer* dataOut) override; size_t m_frameSize; float& m_digGain; float m_normalise; // The following variables are accessed from the RC thread float& m_var_variance_rc; GainMode& m_gainmode; mutable std::mutex m_mutex; #ifdef __SSE__ __m128 static computeGainFix(const __m128* in, size_t sizeIn); __m128 static computeGainMax(const __m128* in, size_t sizeIn); __m128 static computeGainVar(const __m128* in, size_t sizeIn); #else float static computeGainFix(const complexf* in, size_t sizeIn); float static computeGainMax(const complexf* in, size_t sizeIn); float static computeGainVar(const complexf* in, size_t sizeIn); #endif }; Opendigitalradio-ODR-DabMod-f7eedef/src/GuardIntervalInserter.cpp000066400000000000000000000346151475762153200252100ustar00rootroot00000000000000/* Copyright (C) 2005, 2206, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "GuardIntervalInserter.h" #include "PcDebug.h" #include #include #include #include GuardIntervalInserter::Params::Params( size_t nbSymbols, size_t spacing, size_t nullSize, size_t symSize, size_t& windowOverlap) : nbSymbols(nbSymbols), spacing(spacing), nullSize(nullSize), symSize(symSize), windowOverlap(windowOverlap) {} GuardIntervalInserter::GuardIntervalInserter( size_t nbSymbols, size_t spacing, size_t nullSize, size_t symSize, size_t& windowOverlap, FFTEngine fftEngine) : ModCodec(), RemoteControllable("guardinterval"), m_fftEngine(fftEngine), m_params(nbSymbols, spacing, nullSize, symSize, windowOverlap) { if (nullSize == 0) { throw std::logic_error("NULL symbol must be present"); } RC_ADD_PARAMETER(windowlen, "Window length for OFDM windowng [0 to disable]"); /* We use a raised-cosine window for the OFDM windowing. * Each symbol is extended on both sides by windowOverlap samples. * * * Sym n |####################| * Sym n+1 |####################| * * We now extend the symbols by windowOverlap (one dash) * * Sym n extended -|####################|- * Sym n+1 extended -|####################|- * * The windows are raised-cosine: * ____________________ * Sym n window / \ * ... ____/ \___________ ... * * Sym n+1 window ____________________ * / \ * ... ________________/ \__ ... * * The window length is 2*windowOverlap. */ update_window(windowOverlap); PDEBUG("GuardIntervalInserter::GuardIntervalInserter" "(%zu, %zu, %zu, %zu, %zu) @ %p\n", nbSymbols, spacing, nullSize, symSize, windowOverlap, this); } void GuardIntervalInserter::update_window(size_t new_window_overlap) { std::lock_guard lock(m_params.windowMutex); m_params.windowOverlap = new_window_overlap; // m_params.window only contains the rising window edge. m_params.windowFloat.resize(2*m_params.windowOverlap); m_params.windowFix.resize(2*m_params.windowOverlap); m_params.windowFixWide.resize(2*m_params.windowOverlap); for (size_t i = 0; i < 2*m_params.windowOverlap; i++) { const float value = (float)(0.5 * (1.0 - cos(M_PI * i / (2*m_params.windowOverlap - 1)))); m_params.windowFloat[i] = value; m_params.windowFix[i] = complexfix::value_type((double)value); m_params.windowFixWide[i] = complexfix_wide::value_type((double)value); } } template int do_process(const GuardIntervalInserter::Params& p, Buffer* const dataIn, Buffer* dataOut) { PDEBUG("GuardIntervalInserter do_process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); // Every symbol overlaps over a length of windowOverlap with // the previous symbol, and with the next symbol. First symbol // receives no prefix window, because we don't remember the // last symbol from the previous TF (yet). Last symbol also // receives no suffix window, for the same reason. // Overall output buffer length must stay independent of the windowing. dataOut->setLength((p.nullSize + (p.nbSymbols * p.symSize)) * sizeof(T)); const T* in = reinterpret_cast(dataIn->getData()); T* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(T); const size_t num_symbols = p.nbSymbols + 1; if (sizeIn != num_symbols * p.spacing) { PDEBUG("Nb symbols: %zu\n", p.nbSymbols); PDEBUG("Spacing: %zu\n", p.spacing); PDEBUG("Null size: %zu\n", p.nullSize); PDEBUG("Sym size: %zu\n", p.symSize); PDEBUG("\n%zu != %zu\n", sizeIn, (p.nbSymbols + 1) * p.spacing); throw std::runtime_error( "GuardIntervalInserter::process input size not valid!"); } // TODO remember the end of the last TF so that we can do some // windowing too. std::lock_guard lock(p.windowMutex); if (p.windowOverlap) { { // Handle Null symbol separately because it is longer const size_t prefixlength = p.nullSize - p.spacing; // end = spacing memcpy(out, &in[p.spacing - prefixlength], prefixlength * sizeof(T)); memcpy(&out[prefixlength], in, (p.spacing - p.windowOverlap) * sizeof(T)); // The remaining part of the symbol must have half of the window applied, // sloping down from 1 to 0.5 for (size_t i = 0; i < p.windowOverlap; i++) { const size_t out_ix = prefixlength + p.spacing - p.windowOverlap + i; const size_t in_ix = p.spacing - p.windowOverlap + i; if constexpr (std::is_same_v) { out[out_ix] = in[in_ix] * p.windowFloat[2*p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[out_ix] = in[in_ix] * p.windowFix[2*p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[out_ix] = in[in_ix] * p.windowFixWide[2*p.windowOverlap - (i+1)]; } } // Suffix is taken from the beginning of the symbol, and sees the other // half of the window applied. for (size_t i = 0; i < p.windowOverlap; i++) { const size_t out_ix = prefixlength + p.spacing + i; if constexpr (std::is_same_v) { out[out_ix] = in[i] * p.windowFloat[p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[out_ix] = in[i] * p.windowFix[p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[out_ix] = in[i] * p.windowFixWide[p.windowOverlap - (i+1)]; } } in += p.spacing; out += p.nullSize; // out is now pointing to the proper end of symbol. There are // windowOverlap samples ahead that were already written. } // Data symbols for (size_t sym_ix = 0; sym_ix < p.nbSymbols; sym_ix++) { /* _ix variables are indices into in[], _ox variables are * indices for out[] */ const ssize_t start_rise_ox = -p.windowOverlap; const size_t start_rise_ix = 2 * p.spacing - p.symSize - p.windowOverlap; /* const size_t start_real_symbol_ox = 0; const size_t start_real_symbol_ix = 2 * p.spacing - p.symSize; */ const ssize_t end_rise_ox = p.windowOverlap; const size_t end_rise_ix = 2 * p.spacing - p.symSize + p.windowOverlap; const ssize_t end_cyclic_prefix_ox = p.symSize - p.spacing; /* end_cyclic_prefix_ix = end of symbol const size_t begin_fall_ox = p.symSize - p.windowOverlap; const size_t begin_fall_ix = p.spacing - p.windowOverlap; const size_t end_real_symbol_ox = p.symSize; end_real_symbol_ix = end of symbol const size_t end_fall_ox = p.symSize + p.windowOverlap; const size_t end_fall_ix = p.spacing + p.windowOverlap; */ ssize_t ox = start_rise_ox; size_t ix = start_rise_ix; for (size_t i = 0; ix < end_rise_ix; i++) { if constexpr (std::is_same_v) { out[ox] += in[ix] * p.windowFloat.at(i); } if constexpr (std::is_same_v) { out[ox] += in[ix] * p.windowFix.at(i); } if constexpr (std::is_same_v) { out[ox] += in[ix] * p.windowFixWide.at(i); } ix++; ox++; } assert(ox == end_rise_ox); const size_t remaining_prefix_length = end_cyclic_prefix_ox - end_rise_ox; memcpy( &out[ox], &in[ix], remaining_prefix_length * sizeof(T)); ox += remaining_prefix_length; assert(ox == end_cyclic_prefix_ox); ix = 0; const bool last_symbol = (sym_ix + 1 >= p.nbSymbols); if (last_symbol) { // No windowing at all at end memcpy(&out[ox], &in[ix], p.spacing * sizeof(T)); ox += p.spacing; } else { // Copy the middle part of the symbol, p.windowOverlap samples // short of the end. memcpy( &out[ox], &in[ix], (p.spacing - p.windowOverlap) * sizeof(T)); ox += p.spacing - p.windowOverlap; ix += p.spacing - p.windowOverlap; assert(ox == (ssize_t)(p.symSize - p.windowOverlap)); // Apply window from 1 to 0.5 for the end of the symbol for (size_t i = 0; ox < (ssize_t)p.symSize; i++) { if constexpr (std::is_same_v) { out[ox] = in[ix] * p.windowFloat[2*p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[ox] = in[ix] * p.windowFix[2*p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[ox] = in[ix] * p.windowFixWide[2*p.windowOverlap - (i+1)]; } ox++; ix++; } assert(ix == p.spacing); ix = 0; // Cyclic suffix, with window from 0.5 to 0 for (size_t i = 0; ox < (ssize_t)(p.symSize + p.windowOverlap); i++) { if constexpr (std::is_same_v) { out[ox] = in[ix] * p.windowFloat[p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[ox] = in[ix] * p.windowFix[p.windowOverlap - (i+1)]; } if constexpr (std::is_same_v) { out[ox] = in[ix] * p.windowFixWide[p.windowOverlap - (i+1)]; } ox++; ix++; } assert(ix == p.windowOverlap); } out += p.symSize; in += p.spacing; // out is now pointing to the proper end of symbol. There are // windowOverlap samples ahead that were already written. } } else { // Handle Null symbol separately because it is longer // end - (nullSize - spacing) = 2 * spacing - nullSize memcpy(out, &in[2 * p.spacing - p.nullSize], (p.nullSize - p.spacing) * sizeof(T)); memcpy(&out[p.nullSize - p.spacing], in, p.spacing * sizeof(T)); in += p.spacing; out += p.nullSize; // Data symbols for (size_t i = 0; i < p.nbSymbols; ++i) { // end - (symSize - spacing) = 2 * spacing - symSize memcpy(out, &in[2 * p.spacing - p.symSize], (p.symSize - p.spacing) * sizeof(T)); memcpy(&out[p.symSize - p.spacing], in, p.spacing * sizeof(T)); in += p.spacing; out += p.symSize; } } const auto sizeOut = dataOut->getLength(); return sizeOut; } int GuardIntervalInserter::process(Buffer* const dataIn, Buffer* dataOut) { switch (m_fftEngine) { case FFTEngine::FFTW: return do_process(m_params, dataIn, dataOut); case FFTEngine::KISS: return do_process(m_params, dataIn, dataOut); case FFTEngine::DEXTER: return do_process(m_params, dataIn, dataOut); } throw std::logic_error("Unhandled fftEngine variant"); } void GuardIntervalInserter::set_parameter( const std::string& parameter, const std::string& value) { using namespace std; stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "windowlen") { size_t new_window_overlap = 0; ss >> new_window_overlap; update_window(new_window_overlap); } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const std::string GuardIntervalInserter::get_parameter(const std::string& parameter) const { using namespace std; stringstream ss; if (parameter == "windowlen") { ss << m_params.windowOverlap; } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t GuardIntervalInserter::get_all_values() const { json::map_t map; map["windowlen"].v = m_params.windowOverlap; return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/GuardIntervalInserter.h000066400000000000000000000055571475762153200246600ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ConfigParser.h" #include "ModPlugin.h" #include "RemoteControl.h" #include /* The GuardIntervalInserter prepends the cyclic prefix to all * symbols in the transmission frame. * * If windowOverlap is non-zero, it will also add a cyclic suffix of * that length, enlarge the cyclic prefix too, and make symbols * overlap using a raised cosine window. * */ class GuardIntervalInserter : public ModCodec, public RemoteControllable { public: GuardIntervalInserter( size_t nbSymbols, size_t spacing, size_t nullSize, size_t symSize, size_t& windowOverlap, FFTEngine fftEngine); virtual ~GuardIntervalInserter() {} int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "GuardIntervalInserter"; } /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; struct Params { Params( size_t nbSymbols, size_t spacing, size_t nullSize, size_t symSize, size_t& windowOverlap); size_t nbSymbols; size_t spacing; size_t nullSize; size_t symSize; size_t& windowOverlap; mutable std::mutex windowMutex; std::vector windowFloat; std::vector windowFix; std::vector windowFixWide; }; protected: void update_window(size_t new_window_overlap); FFTEngine m_fftEngine; Params m_params; }; Opendigitalradio-ODR-DabMod-f7eedef/src/InputFileReader.cpp000066400000000000000000000214501475762153200237400ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyrigth (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li Input module for reading the ETI data from file or pipe. Supported file formats: RAW, FRAMED, STREAMED Supports re-sync to RAW ETI file */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include "InputReader.h" #include "PcDebug.h" int InputFileReader::Open(std::string filename, bool loop) { filename_ = filename; loop_ = loop; FILE* fd = fopen(filename_.c_str(), "r"); if (fd == nullptr) { etiLog.level(error) << "Unable to open input file!"; perror(filename_.c_str()); return -1; } inputfile_.reset(fd); return IdentifyType(); } int InputFileReader::Rewind() { rewind(inputfile_.get()); // Also clears the EOF flag return IdentifyType(); } int InputFileReader::IdentifyType() { EtiStreamType streamType = EtiStreamType::None; struct stat inputFileStat; fstat(fileno(inputfile_.get()), &inputFileStat); inputfilelength_ = inputFileStat.st_size; uint32_t sync; uint32_t nbFrames; uint16_t frameSize; char discard_buffer[6144]; if (fread(&sync, sizeof(sync), 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read sync in input file!"; perror(filename_.c_str()); return -1; } if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) { streamType = EtiStreamType::Raw; if (inputfilelength_ > 0) { nbframes_ = inputfilelength_ / 6144; } else { nbframes_ = ~0; } if (fseek(inputfile_.get(), -sizeof(sync), SEEK_CUR) != 0) { // if the seek fails, consume the rest of the frame if (fread(discard_buffer, 6144 - sizeof(sync), 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } } this->streamtype_ = streamType; return 0; } nbFrames = sync; if (fread(&frameSize, sizeof(frameSize), 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read frame size in input file!"; perror(filename_.c_str()); return -1; } sync >>= 16; sync &= 0xffff; sync |= ((uint32_t)frameSize) << 16; if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) { streamType = EtiStreamType::Streamed; frameSize = nbFrames & 0xffff; if (inputfilelength_ > 0) { nbframes_ = inputfilelength_ / (frameSize + 2); } else { nbframes_ = ~0; } if (fseek(inputfile_.get(), -6, SEEK_CUR) != 0) { // if the seek fails, consume the rest of the frame if (fread(discard_buffer, frameSize - 4, 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } } this->streamtype_ = streamType; return 0; } if (fread(&sync, sizeof(sync), 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read nb frame in input file!"; perror(filename_.c_str()); return -1; } if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) { streamType = EtiStreamType::Framed; if (fseek(inputfile_.get(), -6, SEEK_CUR) != 0) { // if the seek fails, consume the rest of the frame if (fread(discard_buffer, frameSize - 4, 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } } this->streamtype_ = streamType; nbframes_ = ~0; return 0; } // Search for the sync marker byte by byte for (size_t i = 10; i < 6144 + 10; ++i) { sync >>= 8; sync &= 0xffffff; if (fread((uint8_t*)&sync + 3, 1, 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } if ((sync == 0x49c5f8ff) || (sync == 0xb63a07ff)) { streamType = EtiStreamType::Raw; if (inputfilelength_ > 0) { nbframes_ = (inputfilelength_ - i) / 6144; } else { nbframes_ = ~0; } if (fseek(inputfile_.get(), -sizeof(sync), SEEK_CUR) != 0) { if (fread(discard_buffer, 6144 - sizeof(sync), 1, inputfile_.get()) != 1) { etiLog.level(error) << "Unable to read from input file!"; perror(filename_.c_str()); return -1; } } this->streamtype_ = streamType; return 0; } } etiLog.level(error) << "Bad input file format!"; return -1; } std::string InputFileReader::GetPrintableInfo() const { std::string info = "Input file format: "; switch (streamtype_) { case EtiStreamType::Raw: info += "raw"; break; case EtiStreamType::Streamed: info += "streamed"; break; case EtiStreamType::Framed: info += "framed"; break; default: info += "unknown!"; break; } info += ", length: " + std::to_string(inputfilelength_); if (~nbframes_ != 0) { info += ", nb frames: " + std::to_string(nbframes_); } else { info += ", nb frames: endless"; } return info; } int InputFileReader::GetNextFrame(void* buffer) { uint16_t frameSize; if (streamtype_ == EtiStreamType::Raw) { frameSize = 6144; } else { if (fread(&frameSize, sizeof(frameSize), 1, inputfile_.get()) != 1) { etiLog.level(error) << "Reached end of file."; if (loop_) { if (Rewind() == 0) { if (fread(&frameSize, sizeof(frameSize), 1, inputfile_.get()) != 1) { PDEBUG("Error after rewinding file!\n"); etiLog.level(error) << "Error after rewinding file!"; return -1; } } else { PDEBUG("Impossible to rewind file!\n"); etiLog.level(error) << "Impossible to rewind file!"; return -1; } } else { return 0; } } } if (frameSize > 6144) { // there might be a better limit etiLog.level(error) << "Wrong frame size " << frameSize << " in ETI file!"; return -1; } PDEBUG("Frame size: %u\n", frameSize); size_t read_bytes = fread(buffer, 1, frameSize, inputfile_.get()); if ( loop_ && streamtype_ == EtiStreamType::Raw && //implies frameSize == 6144 read_bytes == 0 && feof(inputfile_.get())) { // in case of an EOF from a RAW that we loop, rewind // otherwise, we won't tolerate it if (Rewind() == 0) { read_bytes = fread(buffer, 1, frameSize, inputfile_.get()); } else { PDEBUG("Impossible to rewind file!\n"); etiLog.level(error) << "Impossible to rewind file!"; return -1; } } if (read_bytes != frameSize) { // A short read of a frame (i.e. reading an incomplete frame) // is not tolerated. Input files must not contain incomplete frames if (read_bytes != 0) { etiLog.level(error) << "Unable to read a complete frame of " << frameSize << " data bytes from input file!"; return -1; } else { return 0; } } memset(&((uint8_t*)buffer)[frameSize], 0x55, 6144 - frameSize); return 6144; } Opendigitalradio-ODR-DabMod-f7eedef/src/InputMemory.cpp000066400000000000000000000026061475762153200232100ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "InputMemory.h" #include "PcDebug.h" #include #include InputMemory::InputMemory(Buffer* dataIn) : ModInput() { PDEBUG("InputMemory::InputMemory(%p) @ %p\n", dataIn, this); setInput(dataIn); } InputMemory::~InputMemory() { PDEBUG("InputMemory::~InputMemory() @ %p\n", this); } void InputMemory::setInput(Buffer* dataIn) { myDataIn = dataIn; } int InputMemory::process(Buffer* dataOut) { PDEBUG("InputMemory::process (dataOut: %p)\n", dataOut); *dataOut = *myDataIn; return dataOut->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/InputMemory.h000066400000000000000000000022361475762153200226540ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" class InputMemory : public ModInput { public: InputMemory(Buffer* dataIn); virtual ~InputMemory(); virtual int process(Buffer* dataOut); const char* name() { return "InputMemory"; } void setInput(Buffer* dataIn); protected: Buffer *myDataIn; }; Opendigitalradio-ODR-DabMod-f7eedef/src/InputReader.h000066400000000000000000000110771475762153200226110ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include "Log.h" #include "Socket.h" #define INVALID_SOCKET -1 class InputReader { public: // Put next frame into buffer. This function will never write more than // 6144 bytes into buffer. // returns number of bytes written to buffer, 0 on eof, -1 on error virtual int GetNextFrame(void* buffer) = 0; // Get some information virtual std::string GetPrintableInfo() const = 0; }; class InputFileReader : public InputReader { public: InputFileReader() = default; InputFileReader(const InputFileReader& other) = delete; InputFileReader& operator=(const InputFileReader& other) = delete; // open file and determine stream type // When loop=1, GetNextFrame will never return 0 int Open(std::string filename, bool loop); // Print information about the file opened virtual std::string GetPrintableInfo() const override; virtual int GetNextFrame(void* buffer) override; private: int IdentifyType(); // Rewind the file, and replay anew // returns 0 on success, -1 on failure int Rewind(); bool loop_; // if shall we loop the file over and over std::string filename_; /* Known types of input streams. Description taken from the CRC * mmbTools forum. All values are are little-endian. */ enum class EtiStreamType { /* Not yet identified */ None, /* Raw format is a bit-by-bit (but byte aligned on sync) recording * of a G.703 data stream. The padding is always present. * The raw format can also be referred to as ETI(NI, G.703) or ETI(NI). * Format: for each frame: uint8_t data[6144] */ Raw, /* Streamed format is used for streamed applications. As the total * number of frames is unknown before end of transmission, the * corresponding field is removed. The padding can be removed from * data. * Format: for each frame: uint16_t frameSize uint8_t data[frameSize] */ Streamed, /* Framed format is used for file recording. It is the default format. * The padding can be removed from data. * Format: uint32_t nbFrames for each frame: uint16_t frameSize uint8_t data[frameSize] */ Framed, }; EtiStreamType streamtype_ = EtiStreamType::None; struct FILEDeleter{ void operator()(FILE* fd){ if(fd) fclose(fd);}}; std::unique_ptr inputfile_; size_t inputfilelength_ = 0; uint64_t nbframes_ = 0; // 64-bit because 32-bit overflow is // after 2**32 * 24ms ~= 3.3 years }; class InputTcpReader : public InputReader { public: // Endpoint is either host:port or tcp://host:port void Open(const std::string& endpoint); // Put next frame into buffer. This function will never write more than // 6144 bytes into buffer. // returns number of bytes written to buffer, 0 on eof, -1 on error virtual int GetNextFrame(void* buffer) override; virtual std::string GetPrintableInfo() const override; private: Socket::TCPClient m_tcpclient; std::string m_uri; }; Opendigitalradio-ODR-DabMod-f7eedef/src/InputTcpReader.cpp000066400000000000000000000047331475762153200236140ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "InputReader.h" #include "PcDebug.h" #include "Utils.h" #include #include void InputTcpReader::Open(const std::string& endpoint) { std::string hostname; if (endpoint.compare(0, 6, "tcp://") == 0) { hostname = endpoint.substr(6, std::string::npos); } else { hostname = endpoint; } size_t colon_pos = hostname.find(":"); if (colon_pos == std::string::npos) { std::stringstream ss; ss << "Could not parse TCP endpoint " << endpoint; throw std::runtime_error(ss.str()); } long port = strtol(hostname.c_str() + colon_pos + 1, NULL, 10); if (errno == ERANGE) { std::stringstream ss; ss << "Could not parse port in TCP endpoint " << endpoint; throw std::runtime_error(ss.str()); } hostname = hostname.substr(0, colon_pos); m_tcpclient.connect(hostname, port); m_uri = endpoint; } int InputTcpReader::GetNextFrame(void* buffer) { uint8_t* buf = (uint8_t*)buffer; const size_t framesize = 6144; const int timeout_ms = 8000; ssize_t ret = m_tcpclient.recv(buf, framesize, MSG_WAITALL, timeout_ms); if (ret == 0) { etiLog.level(debug) << "TCP input auto reconnect"; std::this_thread::sleep_for(std::chrono::seconds(1)); } else if (ret == -2) { etiLog.level(debug) << "TCP input timeout"; } return ret; } std::string InputTcpReader::GetPrintableInfo() const { return "Input TCP: Receiving from " + m_uri; } Opendigitalradio-ODR-DabMod-f7eedef/src/MemlessPoly.cpp000066400000000000000000000351441475762153200231740ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li Andreas Steger, andreas.steger@digris.ch http://opendigitalradio.org This block implements both a memoryless polynom for digital predistortion, and a lookup table predistorter. For better performance, multiplying is done in another thread, leading to a pipeline delay of two calls to MemlessPoly::process */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma GCC optimize ("O3") #include "MemlessPoly.h" #include "PcDebug.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include using namespace std; // Number of AM/AM coefs, identical to number of AM/PM coefs #define NUM_COEFS 5 MemlessPoly::MemlessPoly(std::string& coefs_file, unsigned int num_threads) : PipelinedModCodec(), RemoteControllable("memlesspoly"), m_coefs_am(), m_coefs_pm(), m_coefs_file(coefs_file), m_coefs_mutex() { PDEBUG("MemlessPoly::MemlessPoly(%s) @ %p\n", coefs_file.c_str(), this); RC_ADD_PARAMETER(ncoefs, "(Read-only) number of coefficients."); RC_ADD_PARAMETER(coefs, "Predistortion coefficients, same format as file."); RC_ADD_PARAMETER(coeffile, "Filename containing coefficients. " "When set, the file gets loaded."); if (num_threads == 0) { const unsigned int hw_concurrency = std::thread::hardware_concurrency(); etiLog.level(info) << "Digital Predistorter will use " << hw_concurrency << " threads (auto detected)"; for (size_t i = 0; i < hw_concurrency; i++) { m_workers.emplace_back(); } for (auto& worker : m_workers) { worker.thread = std::thread( &MemlessPoly::worker_thread, &worker); } } else { etiLog.level(info) << "Digital Predistorter will use " << num_threads << " threads (set in config file)"; for (size_t i = 0; i < num_threads; i++) { m_workers.emplace_back(); } for (auto& worker : m_workers) { worker.thread = std::thread( &MemlessPoly::worker_thread, &worker); } } ifstream coefs_fstream(m_coefs_file); load_coefficients(coefs_fstream); start_pipeline_thread(); } MemlessPoly::~MemlessPoly() { stop_pipeline_thread(); } constexpr uint8_t file_format_odd_poly = 1; constexpr uint8_t file_format_lut = 2; std::string MemlessPoly::serialise_coefficients() const { stringstream ss; std::lock_guard lock(m_coefs_mutex); if (m_dpd_settings_valid) { switch (m_dpd_type) { case dpd_type_t::odd_only_poly: ss << (int)file_format_odd_poly << endl; ss << m_coefs_am.size() << endl; for (const auto& coef : m_coefs_am) { ss << coef << endl; } for (const auto& coef : m_coefs_pm) { ss << coef << endl; } break; case dpd_type_t::lookup_table: ss << (int)file_format_lut << endl; ss << m_lut.size() << endl; ss << m_lut_scalefactor << endl; for (const auto& l : m_lut) { ss << l << endl; } break; } } return ss.str(); } void MemlessPoly::load_coefficients(std::istream& coef_stream) { if (!coef_stream) { throw std::runtime_error("MemlessPoly: Could not open file with coefs!"); } uint32_t file_format_indicator; coef_stream >> file_format_indicator; if (file_format_indicator == file_format_odd_poly) { int n_coefs; coef_stream >> n_coefs; if (n_coefs <= 0) { throw std::runtime_error("MemlessPoly: coefs file has invalid format."); } else if (n_coefs != NUM_COEFS) { throw std::runtime_error("MemlessPoly: invalid number of coefs: " + std::to_string(n_coefs) + " expected " + std::to_string(NUM_COEFS)); } const int n_entries = 2 * n_coefs; std::vector coefs_am; std::vector coefs_pm; coefs_am.resize(n_coefs); coefs_pm.resize(n_coefs); for (int n = 0; n < n_entries; n++) { float a; coef_stream >> a; if (n < n_coefs) { coefs_am[n] = a; } else { coefs_pm[n - n_coefs] = a; } if (coef_stream.eof()) { etiLog.log(error, "MemlessPoly: coefs should contains %d coefs, " "but EOF reached after %d coefs !", n_entries, n); throw std::runtime_error("MemlessPoly: coefs file invalid !"); } } { std::lock_guard lock(m_coefs_mutex); m_dpd_type = dpd_type_t::odd_only_poly; m_coefs_am = coefs_am; m_coefs_pm = coefs_pm; m_dpd_settings_valid = true; } etiLog.log(info, "MemlessPoly loaded %zu poly coefs", m_coefs_am.size() + m_coefs_pm.size()); } else if (file_format_indicator == file_format_lut) { float scalefactor; coef_stream >> scalefactor; std::array lut; for (size_t n = 0; n < lut_entries; n++) { float a; coef_stream >> a; lut[n] = a; } { std::lock_guard lock(m_coefs_mutex); m_dpd_type = dpd_type_t::lookup_table; m_lut_scalefactor = scalefactor; m_lut = lut; m_dpd_settings_valid = true; } etiLog.log(info, "MemlessPoly loaded %zu LUT entries", m_lut.size()); } else { etiLog.log(error, "MemlessPoly: coef file has unknown format %d", file_format_indicator); m_dpd_settings_valid = false; } } /* The restrict keyword is C99, g++ and clang++ however support __restrict * instead, and this allows the compiler to auto-vectorize the loop. */ static void apply_coeff( const float *__restrict coefs_am, const float *__restrict coefs_pm, const complexf *__restrict in, size_t start, size_t stop, complexf *__restrict out) { for (size_t i = start; i < stop; i+=1) { float in_mag_sq = in[i].real() * in[i].real() + in[i].imag() * in[i].imag(); float amplitude_correction = ( coefs_am[0] + in_mag_sq * ( coefs_am[1] + in_mag_sq * ( coefs_am[2] + in_mag_sq * ( coefs_am[3] + in_mag_sq * coefs_am[4])))); float phase_correction = -1 * ( coefs_pm[0] + in_mag_sq * ( coefs_pm[1] + in_mag_sq * ( coefs_pm[2] + in_mag_sq * ( coefs_pm[3] + in_mag_sq * coefs_pm[4])))); float phase_correction_sq = phase_correction * phase_correction; // Approximation for Cosinus 1 - 1/2 x^2 + 1/24 x^4 - 1/720 x^6 float re = (1.0f - phase_correction_sq * ( -0.5f + phase_correction_sq * ( 0.486666f + phase_correction_sq * ( -0.00138888f)))); // Approximation for Sinus x + 1/6 x^3 + 1/120 x^5 float im = phase_correction * (1.0f + phase_correction_sq * (0.166666f + phase_correction_sq * (0.00833333f))); out[i] = in[i] * amplitude_correction * complex(re, im); } } static void apply_lut( const complexf *__restrict lut, const float scalefactor, const complexf *__restrict in, size_t start, size_t stop, complexf *__restrict out) { for (size_t i = start; i < stop; i++) { const float in_mag = std::abs(in[i]); // The scalefactor is chosen so as to map the input magnitude // to the range of uint32_t const uint32_t scaled_in = lrintf(in_mag * scalefactor); // lut_ix contains the number of leading 0-bits of the // scaled value, starting at the most significant bit position. // // This partitions the range 0 -- 0xFFFFFFFF into 32 bins. // // 0x00000000 to 0x07FFFFFF go into bin 0 // 0x08000000 to 0x0FFFFFFF go into bin 1 // 0x10000000 to 0x17FFFFFF go into bin 2 // ... // 0xF0000000 to 0xF7FFFFFF go into bin 30 // 0xF8000000 to 0xFFFFFFFF go into bin 31 // // The high 5 bits are therefore used as index. const uint8_t lut_ix = (scaled_in >> 27); // The LUT contains a complex correction factor that is close to // 1 + 0j out[i] = in[i] * lut[lut_ix]; } } void MemlessPoly::worker_thread(MemlessPoly::worker_t *workerdata) { set_realtime_prio(1); set_thread_name("MemlessPoly"); while (true) { worker_t::input_data_t in_data = {}; try { workerdata->in_queue.wait_and_pop(in_data); } catch (const ThreadsafeQueueWakeup&) { break; } switch (in_data.dpd_type) { case dpd_type_t::odd_only_poly: apply_coeff(in_data.coefs_am, in_data.coefs_pm, in_data.in, in_data.start, in_data.stop, in_data.out); break; case dpd_type_t::lookup_table: apply_lut(in_data.lut, in_data.lut_scalefactor, in_data.in, in_data.start, in_data.stop, in_data.out); break; } workerdata->out_queue.push(1); } } int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut) { dataOut->setLength(dataIn->getLength()); const complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); size_t sizeOut = dataOut->getLength() / sizeof(complexf); if (m_dpd_settings_valid) { std::lock_guard lock(m_coefs_mutex); const size_t num_threads = m_workers.size(); if (num_threads > 0) { const size_t step = sizeOut / num_threads; size_t start = 0; for (auto& worker : m_workers) { worker_t::input_data_t dat; dat.dpd_type = m_dpd_type; dat.lut_scalefactor = m_lut_scalefactor; dat.lut = m_lut.data(); dat.coefs_am = m_coefs_am.data(); dat.coefs_pm = m_coefs_pm.data(); dat.in = in; dat.start = start; dat.stop = start + step; dat.out = out; worker.in_queue.push(dat); start += step; } // Do the last in this thread switch (m_dpd_type) { case dpd_type_t::odd_only_poly: apply_coeff(m_coefs_am.data(), m_coefs_pm.data(), in, start, sizeOut, out); break; case dpd_type_t::lookup_table: apply_lut(m_lut.data(), m_lut_scalefactor, in, start, sizeOut, out); break; } // Wait for completion of the tasks for (auto& worker : m_workers) { int ret = 0; worker.out_queue.wait_and_pop(ret); } } else { switch (m_dpd_type) { case dpd_type_t::odd_only_poly: apply_coeff(m_coefs_am.data(), m_coefs_pm.data(), in, 0, sizeOut, out); break; case dpd_type_t::lookup_table: apply_lut(m_lut.data(), m_lut_scalefactor, in, 0, sizeOut, out); break; } } } else { memcpy(dataOut->getData(), dataIn->getData(), sizeOut); } return dataOut->getLength(); } void MemlessPoly::set_parameter(const string& parameter, const string& value) { if (parameter == "ncoefs") { throw ParameterError("Parameter 'ncoefs' is read-only"); } else if (parameter == "coeffile") { try { ifstream coefs_fstream(value); load_coefficients(coefs_fstream); m_coefs_file = value; } catch (const std::runtime_error &e) { throw ParameterError(e.what()); } } else if (parameter == "coefs") { try { stringstream ss(value); load_coefficients(ss); // Write back to the file to ensure we will start up // with the same settings next time ofstream coefs_fstream(m_coefs_file); coefs_fstream << value; } catch (const std::runtime_error &e) { throw ParameterError(e.what()); } } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string MemlessPoly::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "ncoefs") { ss << m_coefs_am.size(); } else if (parameter == "coefs") { ss << serialise_coefficients(); } else if (parameter == "coeffile") { ss << m_coefs_file; } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t MemlessPoly::get_all_values() const { json::map_t map; map["ncoefs"].v = m_coefs_am.size(); map["coefs"].v = serialise_coefficients(); map["coeffile"].v = m_coefs_file; return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/MemlessPoly.h000066400000000000000000000077031475762153200226410ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "RemoteControl.h" #include "ModPlugin.h" #include "ThreadsafeQueue.h" #include #include #include #include #include #include #include #define MEMLESSPOLY_PIPELINE_DELAY 1 enum class dpd_type_t { odd_only_poly, lookup_table }; class MemlessPoly : public PipelinedModCodec, public RemoteControllable { public: MemlessPoly(std::string& coefs_file, unsigned int num_threads); MemlessPoly(const MemlessPoly& other) = delete; MemlessPoly& operator=(const MemlessPoly& other) = delete; virtual ~MemlessPoly(); virtual const char* name() override { return "MemlessPoly"; } /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; private: int internal_process(Buffer* const dataIn, Buffer* dataOut) override; void load_coefficients(std::istream& coefData); std::string serialise_coefficients() const; struct worker_t { struct input_data_t { dpd_type_t dpd_type; // Valid for polynomial types const float *coefs_am = nullptr; const float *coefs_pm = nullptr; // Valid for LUT float lut_scalefactor = 0.0f; const complexf *lut = nullptr; const complexf *in = nullptr; size_t start = 0; size_t stop = 0; complexf *out = nullptr; }; worker_t() {} worker_t(const worker_t& other) = delete; worker_t operator=(const worker_t& other) = delete; worker_t operator=(worker_t&& other) = delete; // The move constructor creates a new in_queue and out_queue, // because ThreadsafeQueue is neither copy- nor move-constructible. // Not an issue because creating the workers happens at startup, before // the first work item. worker_t(worker_t&& other) : in_queue(), out_queue(), thread(std::move(other.thread)) {} ~worker_t() { if (thread.joinable()) { in_queue.trigger_wakeup(); thread.join(); } } ThreadsafeQueue in_queue; ThreadsafeQueue out_queue; std::thread thread; }; std::vector m_workers; static void worker_thread(worker_t *workerdata); bool m_dpd_settings_valid = false; dpd_type_t m_dpd_type; std::vector m_coefs_am; // AM/AM coefficients std::vector m_coefs_pm; // AM/PM coefficients float m_lut_scalefactor; // Scale value applied before looking up in LUT static constexpr size_t lut_entries = 32; std::array m_lut; // Lookup table correction factors std::string& m_coefs_file; mutable std::mutex m_coefs_mutex; }; Opendigitalradio-ODR-DabMod-f7eedef/src/ModPlugin.cpp000066400000000000000000000073611475762153200226210ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "ModPlugin.h" #include "PcDebug.h" #include "Utils.h" #include #include #include #define MODASSERT(cond) \ if (not (cond)) { \ throw std::runtime_error("Assertion failure: " #cond " for " + \ std::string(name())); \ } int ModInput::process( std::vector dataIn, std::vector dataOut) { MODASSERT(dataIn.empty()); MODASSERT(dataOut.size() == 1); return process(dataOut[0]); } int ModCodec::process( std::vector dataIn, std::vector dataOut) { MODASSERT(dataIn.size() == 1); MODASSERT(dataOut.size() == 1); return process(dataIn[0], dataOut[0]); } int ModMux::process( std::vector dataIn, std::vector dataOut) { MODASSERT(not dataIn.empty()); MODASSERT(dataOut.size() == 1); return process(dataIn, dataOut[0]); } int ModOutput::process( std::vector dataIn, std::vector dataOut) { MODASSERT(dataIn.size() == 1); MODASSERT(dataOut.empty()); return process(dataIn[0]); } void PipelinedModCodec::stop_pipeline_thread() { m_input_queue.push({}); if (m_thread.joinable()) { m_thread.join(); } } void PipelinedModCodec::start_pipeline_thread() { m_running = true; m_thread = std::thread(&PipelinedModCodec::process_thread, this); } int PipelinedModCodec::process(Buffer* dataIn, Buffer* dataOut) { if (!m_running) { return 0; } Buffer inbuffer; std::swap(inbuffer, *dataIn); m_input_queue.push(std::move(inbuffer)); if (m_ready_to_output_data) { Buffer outbuffer; m_output_queue.wait_and_pop(outbuffer); std::swap(outbuffer, *dataOut); } else { dataOut->setLength(dataIn->getLength()); if (dataOut->getLength() > 0) { memset(dataOut->getData(), 0, dataOut->getLength()); } m_ready_to_output_data = true; } return dataOut->getLength(); } meta_vec_t PipelinedModCodec::process_metadata(const meta_vec_t& metadataIn) { m_metadata_fifo.push_back(metadataIn); if (m_metadata_fifo.size() == 2) { auto r = std::move(m_metadata_fifo.front()); m_metadata_fifo.pop_front(); return r; } else { return {}; } } void PipelinedModCodec::process_thread() { set_thread_name(name()); set_realtime_prio(1); while (m_running) { Buffer dataIn; m_input_queue.wait_and_pop(dataIn); if (dataIn.getLength() == 0) { break; } Buffer dataOut; dataOut.setLength(dataIn.getLength()); if (internal_process(&dataIn, &dataOut) == 0) { m_running = false; } m_output_queue.push(std::move(dataOut)); } m_running = false; } Opendigitalradio-ODR-DabMod-f7eedef/src/ModPlugin.h000066400000000000000000000101541475762153200222600ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "Buffer.h" #include "ThreadsafeQueue.h" #include "TimestampDecoder.h" #include #include #include // All flowgraph elements derive from ModPlugin, or a variant of it. // Some ModPlugins also support handling metadata. struct flowgraph_metadata { frame_timestamp ts; }; using meta_vec_t = std::vector; /* ModPlugins that support metadata derive from ModMetadata */ class ModMetadata { public: // Receives metadata from all inputs, and process them, and output // a sequence of metadata. virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) = 0; }; /* Abstract base class for all flowgraph elements */ class ModPlugin { public: virtual int process( std::vector dataIn, std::vector dataOut) = 0; virtual const char* name() = 0; virtual ~ModPlugin() = default; }; /* Inputs are sources, the output buffers without reading any */ class ModInput : public ModPlugin { public: virtual int process( std::vector dataIn, std::vector dataOut); virtual int process(Buffer* dataOut) = 0; }; /* Codecs are 1-input 1-output flowgraph plugins */ class ModCodec : public ModPlugin { public: virtual int process( std::vector dataIn, std::vector dataOut); virtual int process(Buffer* const dataIn, Buffer* dataOut) = 0; }; /* Pipelined ModCodecs run their processing in a separate thread, and * have a one-call-to-process() latency. Because of this latency, they * must also handle the metadata */ class PipelinedModCodec : public ModCodec, public ModMetadata { public: virtual int process(Buffer* const dataIn, Buffer* dataOut) final; virtual const char* name() = 0; virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) final; protected: // Once the instance implementing PipelinedModCodec has been constructed, // it must call start_pipeline_thread() void start_pipeline_thread(void); // To avoid race conditions on teardown, plugins must call // stop_pipeline_thread in their destructor. void stop_pipeline_thread(void); // The real processing must be implemented in internal_process virtual int internal_process(Buffer* const dataIn, Buffer* dataOut) = 0; private: bool m_ready_to_output_data = false; ThreadsafeQueue m_input_queue; ThreadsafeQueue m_output_queue; std::deque m_metadata_fifo; std::atomic m_running = ATOMIC_VAR_INIT(false); std::thread m_thread; void process_thread(void); }; /* Muxes are N-input 1-output flowgraph plugins */ class ModMux : public ModPlugin { public: virtual int process( std::vector dataIn, std::vector dataOut); virtual int process(std::vector dataIn, Buffer* dataOut) = 0; }; /* Outputs do not create any output buffers */ class ModOutput : public ModPlugin { public: virtual int process( std::vector dataIn, std::vector dataOut); virtual int process(Buffer* dataIn) = 0; }; Opendigitalradio-ODR-DabMod-f7eedef/src/NullSymbol.cpp000066400000000000000000000030601475762153200230130ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "NullSymbol.h" #include "PcDebug.h" #include #include #include NullSymbol::NullSymbol(size_t numCarriers, size_t typeSize) : ModInput(), m_numCarriers(numCarriers), m_typeSize(typeSize) { PDEBUG("NullSymbol::NullSymbol(%zu) @ %p\n", numCarriers, this); } NullSymbol::~NullSymbol() { PDEBUG("NullSymbol::~NullSymbol() @ %p\n", this); } int NullSymbol::process(Buffer* dataOut) { PDEBUG("NullSymbol::process(dataOut: %p)\n", dataOut); dataOut->setLength(m_numCarriers * m_typeSize); memset(dataOut->getData(), 0, dataOut->getLength()); return dataOut->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/NullSymbol.h000066400000000000000000000024761475762153200224720ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "ModPlugin.h" #include #include class NullSymbol : public ModInput { public: NullSymbol(size_t nunCarriers, size_t typeSize); virtual ~NullSymbol(); int process(Buffer* dataOut); const char* name() { return "NullSymbol"; } private: size_t m_numCarriers; size_t m_typeSize; }; Opendigitalradio-ODR-DabMod-f7eedef/src/OfdmGenerator.cpp000066400000000000000000000663401475762153200234610ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "OfdmGenerator.h" #include "PcDebug.h" #include #include #include #include #include #include #include static const size_t MAX_CLIP_STATS = 10; using FFTW_TYPE = fftwf_complex; OfdmGeneratorCF32::OfdmGeneratorCF32(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool& enableCfr, float& cfrClip, float& cfrErrorClip, bool inverse) : ModCodec(), RemoteControllable("ofdm"), myFftPlan(nullptr), myFftIn(nullptr), myFftOut(nullptr), myNbSymbols(nbSymbols), myNbCarriers(nbCarriers), mySpacing(spacing), myCfr(enableCfr), myCfrClip(cfrClip), myCfrErrorClip(cfrErrorClip), myCfrFft(nullptr), // Initialise the PAPRStats to a few seconds worth of samples myPaprBeforeCFR(nbSymbols * 50), myPaprAfterCFR(nbSymbols * 50) { PDEBUG("OfdmGenerator::OfdmGenerator(%zu, %zu, %zu, %s) @ %p\n", nbSymbols, nbCarriers, spacing, inverse ? "true" : "false", this); if (nbCarriers > spacing) { throw std::runtime_error("OfdmGenerator nbCarriers > spacing!"); } /* register the parameters that can be remote controlled */ RC_ADD_PARAMETER(cfr, "Enable crest factor reduction"); RC_ADD_PARAMETER(clip, "CFR: Clip to amplitude"); RC_ADD_PARAMETER(errorclip, "CFR: Limit error"); RC_ADD_PARAMETER(clip_stats, "CFR: statistics (clip ratio, errorclip ratio)"); RC_ADD_PARAMETER(papr, "PAPR measurements (before CFR, after CFR)"); if (inverse) { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = 0; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = (nbCarriers + 1) / 2; myNegSize = nbCarriers / 2; } else { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = nbCarriers / 2; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = 0; myNegSize = nbCarriers / 2; } myZeroDst = myPosDst + myPosSize; myZeroSize = myNegDst - myZeroDst; PDEBUG(" myPosDst: %u\n", myPosDst); PDEBUG(" myPosSrc: %u\n", myPosSrc); PDEBUG(" myPosSize: %u\n", myPosSize); PDEBUG(" myNegDst: %u\n", myNegDst); PDEBUG(" myNegSrc: %u\n", myNegSrc); PDEBUG(" myNegSize: %u\n", myNegSize); PDEBUG(" myZeroDst: %u\n", myZeroDst); PDEBUG(" myZeroSize: %u\n", myZeroSize); const int N = mySpacing; // The size of the FFT myFftIn = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); myFftOut = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); fftwf_set_timelimit(2); myFftPlan = fftwf_plan_dft_1d(N, myFftIn, myFftOut, FFTW_BACKWARD, FFTW_MEASURE); myCfrPostClip = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); myCfrPostFft = (FFTW_TYPE*)fftwf_malloc(sizeof(FFTW_TYPE) * N); myCfrFft = fftwf_plan_dft_1d(N, myCfrPostClip, myCfrPostFft, FFTW_FORWARD, FFTW_MEASURE); if (sizeof(complexf) != sizeof(FFTW_TYPE)) { printf("sizeof(complexf) %zu\n", sizeof(complexf)); printf("sizeof(FFT_TYPE) %zu\n", sizeof(FFTW_TYPE)); throw std::runtime_error( "OfdmGenerator::process complexf size is not FFT_TYPE size!"); } } OfdmGeneratorCF32::~OfdmGeneratorCF32() { PDEBUG("OfdmGenerator::~OfdmGenerator() @ %p\n", this); if (myFftIn) { fftwf_free(myFftIn); } if (myFftOut) { fftwf_free(myFftOut); } if (myFftPlan) { fftwf_destroy_plan(myFftPlan); } if (myCfrPostClip) { fftwf_free(myCfrPostClip); } if (myCfrPostFft) { fftwf_free(myCfrPostFft); } if (myCfrFft) { fftwf_destroy_plan(myCfrFft); } } int OfdmGeneratorCF32::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("OfdmGenerator::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); dataOut->setLength(myNbSymbols * mySpacing * sizeof(complexf)); FFTW_TYPE *in = reinterpret_cast(dataIn->getData()); FFTW_TYPE *out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); size_t sizeOut = dataOut->getLength() / sizeof(complexf); if (sizeIn != myNbSymbols * myNbCarriers) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * myNbCarriers); throw std::runtime_error( "OfdmGenerator::process input size not valid!"); } if (sizeOut != myNbSymbols * mySpacing) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * mySpacing); throw std::runtime_error( "OfdmGenerator::process output size not valid!"); } // It is not guaranteed that fftw keeps the FFT input vector intact. // That's why we copy it to the reference. std::vector reference; // IFFT output before CFR applied, for MER calc std::vector before_cfr; size_t num_clip = 0; size_t num_error_clip = 0; // For performance reasons, do not calculate MER for every symbol. myMERCalcIndex = (myMERCalcIndex + 1) % myNbSymbols; // The PAPRStats' clear() is not threadsafe, do not access it // from the RC functions. if (myPaprClearRequest.exchange(false)) { myPaprBeforeCFR.clear(); myPaprAfterCFR.clear(); } for (size_t i = 0; i < myNbSymbols; i++) { myFftIn[0][0] = 0; myFftIn[0][1] = 0; /* For TM I this is: * ZeroDst=769 ZeroSize=511 * PosSrc=0 PosDst=1 PosSize=768 * NegSrc=768 NegDst=1280 NegSize=768 */ memset(&myFftIn[myZeroDst], 0, myZeroSize * sizeof(FFTW_TYPE)); memcpy(&myFftIn[myPosDst], &in[myPosSrc], myPosSize * sizeof(FFTW_TYPE)); memcpy(&myFftIn[myNegDst], &in[myNegSrc], myNegSize * sizeof(FFTW_TYPE)); if (myCfr) { reference.resize(mySpacing); memcpy(reinterpret_cast(reference.data()), myFftIn, mySpacing * sizeof(FFTW_TYPE)); } fftwf_execute(myFftPlan); // IFFT from myFftIn to myFftOut if (myCfr) { complexf *symbol = reinterpret_cast(myFftOut); myPaprBeforeCFR.process_block(symbol, mySpacing); if (myMERCalcIndex == i) { before_cfr.resize(mySpacing); memcpy(reinterpret_cast(before_cfr.data()), myFftOut, mySpacing * sizeof(FFTW_TYPE)); } /* cfr_one_iteration runs the myFftPlan again at the end, and * therefore writes the output data to myFftOut. */ const auto stat = cfr_one_iteration(symbol, reference.data()); // i == 0 always zero power, so the MER ends up being NaN if (i > 0) { myPaprAfterCFR.process_block(symbol, mySpacing); } if (i > 0 and myMERCalcIndex == i) { /* MER definition, ETSI ETR 290, Annex C * * \sum I^2 + Q^2 * MER[dB] = 10 log_10( ---------------- ) * \sum dI^2 + dQ^2 * Where I and Q are the ideal coordinates, and dI and dQ are * the errors in the received datapoints. * * In our case, we consider the constellation points given to the * OfdmGenerator as "ideal", and we compare the CFR output to it. */ double sum_iq = 0; double sum_delta = 0; for (size_t j = 0; j < mySpacing; j++) { sum_iq += (double)std::norm(before_cfr[j]); sum_delta += (double)std::norm(symbol[j] - before_cfr[j]); } // Clamp to 90dB, otherwise the MER average is going to be inf const double mer = sum_delta > 0 ? 10.0 * std::log10(sum_iq / sum_delta) : 90; myMERs.push_back(mer); } num_clip += stat.clip_count; num_error_clip += stat.errclip_count; } memcpy(out, myFftOut, mySpacing * sizeof(FFTW_TYPE)); in += myNbCarriers; out += mySpacing; } if (myCfr) { std::lock_guard lock(myCfrRcMutex); const double num_samps = myNbSymbols * mySpacing; const double clip_ratio = (double)num_clip / num_samps; myClipRatios.push_back(clip_ratio); while (myClipRatios.size() > MAX_CLIP_STATS) { myClipRatios.pop_front(); } const double errclip_ratio = (double)num_error_clip / num_samps; myErrorClipRatios.push_back(errclip_ratio); while (myErrorClipRatios.size() > MAX_CLIP_STATS) { myErrorClipRatios.pop_front(); } while (myMERs.size() > MAX_CLIP_STATS) { myMERs.pop_front(); } } return sizeOut; } OfdmGeneratorCF32::cfr_iter_stat_t OfdmGeneratorCF32::cfr_one_iteration( complexf *symbol, const complexf *reference) { // use std::norm instead of std::abs to avoid calculating the // square roots const float clip_squared = myCfrClip * myCfrClip; OfdmGeneratorCF32::cfr_iter_stat_t ret; // Clip for (size_t i = 0; i < mySpacing; i++) { const float mag_squared = std::norm(symbol[i]); if (mag_squared > clip_squared) { // normalise absolute value to myCfrClip: // x_clipped = x * clip / |x| // = x * sqrt(clip_squared) / sqrt(mag_squared) // = x * sqrt(clip_squared / mag_squared) symbol[i] *= std::sqrt(clip_squared / mag_squared); ret.clip_count++; } } // Take FFT of our clipped signal memcpy(myCfrPostClip, symbol, mySpacing * sizeof(FFTW_TYPE)); fftwf_execute(myCfrFft); // FFT from myCfrPostClip to myCfrPostFft // Calculate the error in frequency domain by subtracting our reference // and clip it to myCfrErrorClip. By adding this clipped error signal // to our FFT output, we compensate the introduced error to some // extent. const float err_clip_squared = myCfrErrorClip * myCfrErrorClip; std::vector error_norm(mySpacing); for (size_t i = 0; i < mySpacing; i++) { // FFTW computes an unnormalised transform, i.e. a FFT-IFFT pair // or vice-versa gives back the original vector scaled by a factor // FFT-size. Because we're comparing our constellation point // (calculated with IFFT-clip-FFT) against reference (input to // the IFFT), we need to divide by our FFT size. const complexf constellation_point = reinterpret_cast(myCfrPostFft)[i] / (float)mySpacing; complexf error = reference[i] - constellation_point; const float mag_squared = std::norm(error); error_norm[i] = mag_squared; if (mag_squared > err_clip_squared) { error *= std::sqrt(err_clip_squared / mag_squared); ret.errclip_count++; } // Update the input to the FFT directly to avoid another copy for the // subsequence IFFT complexf *fft_in = reinterpret_cast(myFftIn); fft_in[i] = constellation_point + error; } // Run our error-compensated symbol through the IFFT again fftwf_execute(myFftPlan); // IFFT from myFftIn to myFftOut return ret; } void OfdmGeneratorCF32::set_parameter(const std::string& parameter, const std::string& value) { using namespace std; stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "cfr") { ss >> myCfr; myPaprClearRequest.store(true); } else if (parameter == "clip") { ss >> myCfrClip; myPaprClearRequest.store(true); } else if (parameter == "errorclip") { ss >> myCfrErrorClip; myPaprClearRequest.store(true); } else if (parameter == "clip_stats" or parameter == "papr") { throw ParameterError("Parameter '" + parameter + "' is read-only"); } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const std::string OfdmGeneratorCF32::get_parameter(const std::string& parameter) const { using namespace std; stringstream ss; if (parameter == "cfr") { ss << myCfr; } else if (parameter == "clip") { ss << std::fixed << myCfrClip; } else if (parameter == "errorclip") { ss << std::fixed << myCfrErrorClip; } else if (parameter == "clip_stats") { std::lock_guard lock(myCfrRcMutex); if (myClipRatios.empty() or myErrorClipRatios.empty() or myMERs.empty()) { ss << "No stats available"; } else { const double avg_clip_ratio = std::accumulate(myClipRatios.begin(), myClipRatios.end(), 0.0) / myClipRatios.size(); const double avg_errclip_ratio = std::accumulate(myErrorClipRatios.begin(), myErrorClipRatios.end(), 0.0) / myErrorClipRatios.size(); const double avg_mer = std::accumulate(myMERs.begin(), myMERs.end(), 0.0) / myMERs.size(); ss << "Statistics : " << std::fixed << avg_clip_ratio * 100 << "%"" samples clipped, " << avg_errclip_ratio * 100 << "%"" errors clipped. " << "MER after CFR: " << avg_mer << " dB"; } } else if (parameter == "papr") { const double papr_before = myPaprBeforeCFR.calculate_papr(); const double papr_after = myPaprAfterCFR.calculate_papr(); ss << "PAPR [dB]: " << std::fixed << (papr_before == 0 ? string("N/A") : to_string(papr_before)) << ", " << (papr_after == 0 ? string("N/A") : to_string(papr_after)); } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t OfdmGeneratorCF32::get_all_values() const { json::map_t map; // TODO needs rework of the values return map; } OfdmGeneratorFixed::OfdmGeneratorFixed(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool inverse) : ModCodec(), myNbSymbols(nbSymbols), myNbCarriers(nbCarriers), mySpacing(spacing) { PDEBUG("OfdmGenerator::OfdmGenerator(%zu, %zu, %zu, %s) @ %p\n", nbSymbols, nbCarriers, spacing, inverse ? "true" : "false", this); etiLog.level(info) << "Using KISS FFT by Mark Borgerding for fixed-point transform"; if (nbCarriers > spacing) { throw std::runtime_error("OfdmGenerator nbCarriers > spacing!"); } if (inverse) { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = 0; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = (nbCarriers + 1) / 2; myNegSize = nbCarriers / 2; } else { myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = nbCarriers / 2; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = 0; myNegSize = nbCarriers / 2; } myZeroDst = myPosDst + myPosSize; myZeroSize = myNegDst - myZeroDst; PDEBUG(" myPosDst: %u\n", myPosDst); PDEBUG(" myPosSrc: %u\n", myPosSrc); PDEBUG(" myPosSize: %u\n", myPosSize); PDEBUG(" myNegDst: %u\n", myNegDst); PDEBUG(" myNegSrc: %u\n", myNegSrc); PDEBUG(" myNegSize: %u\n", myNegSize); PDEBUG(" myZeroDst: %u\n", myZeroDst); PDEBUG(" myZeroSize: %u\n", myZeroSize); const int N = mySpacing; // The size of the FFT const size_t nbytes = N * sizeof(kiss_fft_cpx); myFftIn = (kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); myFftOut = (kiss_fft_cpx*)KISS_FFT_MALLOC(nbytes); memset(myFftIn, 0, nbytes); myKissCfg = kiss_fft_alloc(N, inverse, nullptr, nullptr); } OfdmGeneratorFixed::~OfdmGeneratorFixed() { if (myKissCfg) KISS_FFT_FREE(myKissCfg); if (myFftIn) KISS_FFT_FREE(myFftIn); if (myFftOut) KISS_FFT_FREE(myFftOut); } int OfdmGeneratorFixed::process(Buffer* const dataIn, Buffer* dataOut) { dataOut->setLength(myNbSymbols * mySpacing * sizeof(kiss_fft_cpx)); kiss_fft_cpx *in = reinterpret_cast(dataIn->getData()); kiss_fft_cpx *out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(kiss_fft_cpx); size_t sizeOut = dataOut->getLength() / sizeof(kiss_fft_cpx); if (sizeIn != myNbSymbols * myNbCarriers) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * myNbCarriers); throw std::runtime_error( "OfdmGenerator::process input size not valid!"); } if (sizeOut != myNbSymbols * mySpacing) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * mySpacing); throw std::runtime_error( "OfdmGenerator::process output size not valid!"); } for (size_t i = 0; i < myNbSymbols; i++) { myFftIn[0].r = 0; myFftIn[0].i = 0; /* For TM I this is: * ZeroDst=769 ZeroSize=511 * PosSrc=0 PosDst=1 PosSize=768 * NegSrc=768 NegDst=1280 NegSize=768 */ memset(&myFftIn[myZeroDst], 0, myZeroSize * sizeof(kiss_fft_cpx)); memcpy(&myFftIn[myPosDst], &in[myPosSrc], myPosSize * sizeof(kiss_fft_cpx)); memcpy(&myFftIn[myNegDst], &in[myNegSrc], myNegSize * sizeof(kiss_fft_cpx)); kiss_fft(myKissCfg, myFftIn, myFftOut); memcpy(out, myFftOut, mySpacing * sizeof(kiss_fft_cpx)); in += myNbCarriers; out += mySpacing; } return sizeOut; } #ifdef HAVE_DEXTER OfdmGeneratorDEXTER::OfdmGeneratorDEXTER(size_t nbSymbols, size_t nbCarriers, size_t spacing) : ModCodec(), myNbSymbols(nbSymbols), myNbCarriers(nbCarriers), mySpacing(spacing) { PDEBUG("OfdmGeneratorDEXTER::OfdmGeneratorDEXTER(%zu, %zu, %zu) @ %p\n", nbSymbols, nbCarriers, spacing, this); etiLog.level(info) << "Using DEXTER FFT Accelerator for fixed-point transform"; if (nbCarriers > spacing) { throw std::runtime_error("OfdmGenerator nbCarriers > spacing!"); } myPosDst = (nbCarriers & 1 ? 0 : 1); myPosSrc = 0; myPosSize = (nbCarriers + 1) / 2; myNegDst = spacing - (nbCarriers / 2); myNegSrc = (nbCarriers + 1) / 2; myNegSize = nbCarriers / 2; myZeroDst = myPosDst + myPosSize; myZeroSize = myNegDst - myZeroDst; PDEBUG(" myPosDst: %u\n", myPosDst); PDEBUG(" myPosSrc: %u\n", myPosSrc); PDEBUG(" myPosSize: %u\n", myPosSize); PDEBUG(" myNegDst: %u\n", myNegDst); PDEBUG(" myNegSrc: %u\n", myNegSrc); PDEBUG(" myNegSize: %u\n", myNegSize); PDEBUG(" myZeroDst: %u\n", myZeroDst); PDEBUG(" myZeroSize: %u\n", myZeroSize); const size_t nbytes_in = mySpacing * sizeof(complexfix); const size_t nbytes_out = mySpacing * sizeof(complexfix_wide); #define IIO_ENSURE(expr, err) { \ if (!(expr)) { \ etiLog.log(error, "%s (%s:%d)\n", err, __FILE__, __LINE__); \ throw std::runtime_error("Failed to set FFT for OfdmGeneratorDEXTER"); \ } \ } IIO_ENSURE((m_ctx = iio_create_default_context()), "No context"); IIO_ENSURE(m_dev_in = iio_context_find_device(m_ctx, "fft-accelerator-in"), "no dev"); IIO_ENSURE(m_dev_out = iio_context_find_device(m_ctx, "fft-accelerator-out"), "no dev"); IIO_ENSURE(m_channel_in = iio_device_find_channel(m_dev_in, "voltage0", true), "no channel"); IIO_ENSURE(m_channel_out = iio_device_find_channel(m_dev_out, "voltage0", false), "no channel"); iio_channel_enable(m_channel_in); iio_channel_enable(m_channel_out); m_buf_in = iio_device_create_buffer(m_dev_in, nbytes_in, false); if (!m_buf_in) { throw std::runtime_error("OfdmGeneratorDEXTER could not create in buffer"); } m_buf_out = iio_device_create_buffer(m_dev_out, nbytes_out, false); if (!m_buf_out) { throw std::runtime_error("OfdmGeneratorDEXTER could not create out buffer"); } } OfdmGeneratorDEXTER::~OfdmGeneratorDEXTER() { if (m_buf_in) { iio_buffer_destroy(m_buf_in); m_buf_in = nullptr; } if (m_buf_out) { iio_buffer_destroy(m_buf_out); m_buf_out = nullptr; } if (m_channel_in) { iio_channel_disable(m_channel_in); m_channel_in = nullptr; } if (m_channel_out) { iio_channel_disable(m_channel_out); m_channel_out = nullptr; } if (m_ctx) { iio_context_destroy(m_ctx); m_ctx = nullptr; } } int OfdmGeneratorDEXTER::process(Buffer* const dataIn, Buffer* dataOut) { dataOut->setLength(myNbSymbols * mySpacing * sizeof(complexfix_wide)); complexfix *in = reinterpret_cast(dataIn->getData()); complexfix_wide *out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexfix); size_t sizeOut = dataOut->getLength() / sizeof(complexfix_wide); if (sizeIn != myNbSymbols * myNbCarriers) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * myNbCarriers); throw std::runtime_error( "OfdmGenerator::process input size not valid!"); } if (sizeOut != myNbSymbols * mySpacing) { PDEBUG("Nb symbols: %zu\n", myNbSymbols); PDEBUG("Nb carriers: %zu\n", myNbCarriers); PDEBUG("Spacing: %zu\n", mySpacing); PDEBUG("\n%zu != %zu\n", sizeIn, myNbSymbols * mySpacing); throw std::runtime_error("OfdmGenerator::process output size not valid!"); } ptrdiff_t iio_buf_size = (uint8_t*)iio_buffer_end(m_buf_in) - (uint8_t*)iio_buffer_start(m_buf_in); if (iio_buf_size != (ssize_t)(mySpacing * sizeof(complexfix))) { throw std::runtime_error("OfdmGenerator::process incorrect iio buffer size!"); } for (size_t i = 0; i < myNbSymbols; i++) { complexfix *fft_in = reinterpret_cast(iio_buffer_start(m_buf_in)); /* For TM I this is: * ZeroDst=769 ZeroSize=511 * PosSrc=0 PosDst=1 PosSize=768 * NegSrc=768 NegDst=1280 NegSize=768 */ fft_in[0] = static_cast(0); for (size_t i = 0; i < myZeroSize; i++) { fft_in[myZeroDst + i] = static_cast(0); } memcpy(&fft_in[myPosDst], &in[myPosSrc], myPosSize * sizeof(complexfix)); memcpy(&fft_in[myNegDst], &in[myNegSrc], myNegSize * sizeof(complexfix)); ssize_t nbytes_tx = iio_buffer_push(m_buf_in); if (nbytes_tx < 0) { throw std::runtime_error("OfdmGenerator::process error pushing IIO buffer!"); } in += myNbCarriers; // Keep one buffer in flight while we're doing shuffling data around here, // this improves performance. // I believe that, by default, IIO allocates four buffers in total. if (i > 0) { ssize_t nbytes_rx = iio_buffer_refill(m_buf_out); if (nbytes_rx < 0) { throw std::runtime_error("OfdmGenerator::process error refilling IIO buffer!"); } ptrdiff_t p_inc = iio_buffer_step(m_buf_out); if (p_inc != 1) { throw std::runtime_error("OfdmGenerator::process Wrong p_inc"); } // The FFT Accelerator takes 16-bit I + 16-bit Q, and outputs 32-bit I and 32-bit Q. // The formatconvert will take care of this const uint8_t *fft_out = (const uint8_t*)iio_buffer_first(m_buf_out, m_channel_out); const uint8_t *fft_out_end = (const uint8_t*)iio_buffer_end(m_buf_out); constexpr size_t sizeof_out_iq = sizeof(complexfix_wide); if ((fft_out_end - fft_out) != (ssize_t)(mySpacing * sizeof_out_iq)) { fprintf(stderr, "FFT_OUT: %p %p %zu %zu\n", fft_out, fft_out_end, (fft_out_end - fft_out), mySpacing * sizeof_out_iq); throw std::runtime_error("OfdmGenerator::process fft_out length invalid!"); } memcpy(out, fft_out, mySpacing * sizeof_out_iq); out += mySpacing; } } ssize_t nbytes_rx = iio_buffer_refill(m_buf_out); if (nbytes_rx < 0) { throw std::runtime_error("OfdmGenerator::process error refilling IIO buffer!"); } ptrdiff_t p_inc = iio_buffer_step(m_buf_out); if (p_inc != 1) { throw std::runtime_error("OfdmGenerator::process Wrong p_inc"); } // The FFT Accelerator takes 16-bit I + 16-bit Q, and outputs 32-bit I and 32-bit Q. // The formatconvert will take care of this const uint8_t *fft_out = (const uint8_t*)iio_buffer_first(m_buf_out, m_channel_out); const uint8_t *fft_out_end = (const uint8_t*)iio_buffer_end(m_buf_out); constexpr size_t sizeof_out_iq = sizeof(complexfix_wide); if ((fft_out_end - fft_out) != (ssize_t)(mySpacing * sizeof_out_iq)) { fprintf(stderr, "FFT_OUT: %p %p %zu %zu\n", fft_out, fft_out_end, (fft_out_end - fft_out), mySpacing * sizeof_out_iq); throw std::runtime_error("OfdmGenerator::process fft_out length invalid!"); } memcpy(out, fft_out, mySpacing * sizeof_out_iq); return sizeOut; } #endif // HAVE_DEXTER Opendigitalradio-ODR-DabMod-f7eedef/src/OfdmGenerator.h000066400000000000000000000134231475762153200231200ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "ModPlugin.h" #include "RemoteControl.h" #include "PAPRStats.h" #include "kiss_fft.h" #include #include #include #ifdef HAVE_DEXTER # include #endif // Complex Float uses FFTW class OfdmGeneratorCF32 : public ModCodec, public RemoteControllable { public: OfdmGeneratorCF32(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool& enableCfr, float& cfrClip, float& cfrErrorClip, bool inverse = true); virtual ~OfdmGeneratorCF32(); OfdmGeneratorCF32(const OfdmGeneratorCF32&) = delete; OfdmGeneratorCF32& operator=(const OfdmGeneratorCF32&) = delete; int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "OfdmGenerator"; } /* Functions for the remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; protected: struct cfr_iter_stat_t { size_t clip_count = 0; size_t errclip_count = 0; }; cfr_iter_stat_t cfr_one_iteration( complexf *symbol, const complexf *reference); fftwf_plan myFftPlan; fftwf_complex *myFftIn, *myFftOut; const size_t myNbSymbols; const size_t myNbCarriers; const size_t mySpacing; unsigned myPosSrc; unsigned myPosDst; unsigned myPosSize; unsigned myNegSrc; unsigned myNegDst; unsigned myNegSize; unsigned myZeroDst; unsigned myZeroSize; bool& myCfr; // Whether to enable crest factor reduction mutable std::mutex myCfrRcMutex; float& myCfrClip; float& myCfrErrorClip; fftwf_plan myCfrFft; fftwf_complex *myCfrPostClip; fftwf_complex *myCfrPostFft; // Statistics for CFR std::deque myClipRatios; std::deque myErrorClipRatios; // Measure PAPR before and after CFR PAPRStats myPaprBeforeCFR; PAPRStats myPaprAfterCFR; std::atomic myPaprClearRequest; size_t myMERCalcIndex = 0; std::deque myMERs; }; // Fixed point implementation uses KISS FFT with -DFIXED_POINT=32 class OfdmGeneratorFixed : public ModCodec { public: OfdmGeneratorFixed(size_t nbSymbols, size_t nbCarriers, size_t spacing, bool inverse = true); virtual ~OfdmGeneratorFixed(); OfdmGeneratorFixed(const OfdmGeneratorFixed&) = delete; OfdmGeneratorFixed& operator=(const OfdmGeneratorFixed&) = delete; int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "OfdmGenerator"; } private: kiss_fft_cfg myKissCfg = nullptr; kiss_fft_cpx *myFftIn, *myFftOut; const size_t myNbSymbols; const size_t myNbCarriers; const size_t mySpacing; unsigned myPosSrc; unsigned myPosDst; unsigned myPosSize; unsigned myNegSrc; unsigned myNegDst; unsigned myNegSize; unsigned myZeroDst; unsigned myZeroSize; }; #ifdef HAVE_DEXTER // The PrecisionWave DEXTER device contains an FFT accelerator in FPGA // It only does inverse FFTs class OfdmGeneratorDEXTER : public ModCodec { public: OfdmGeneratorDEXTER(size_t nbSymbols, size_t nbCarriers, size_t spacing); virtual ~OfdmGeneratorDEXTER(); OfdmGeneratorDEXTER(const OfdmGeneratorDEXTER&) = delete; OfdmGeneratorDEXTER& operator=(const OfdmGeneratorDEXTER&) = delete; int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "OfdmGenerator"; } private: struct iio_context *m_ctx = nullptr; // "in" and "out" are from the point of view of the FFT Accelerator block struct iio_device *m_dev_in = nullptr; struct iio_channel *m_channel_in = nullptr; struct iio_buffer *m_buf_in = nullptr; struct iio_device *m_dev_out = nullptr; struct iio_channel *m_channel_out = nullptr; struct iio_buffer *m_buf_out = nullptr; const size_t myNbSymbols; const size_t myNbCarriers; const size_t mySpacing; unsigned myPosSrc; unsigned myPosDst; unsigned myPosSize; unsigned myNegSrc; unsigned myNegDst; unsigned myNegSize; unsigned myZeroDst; unsigned myZeroSize; }; #endif // HAVE_DEXTER Opendigitalradio-ODR-DabMod-f7eedef/src/OutputFile.cpp000066400000000000000000000107641475762153200230240ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "OutputFile.h" #include "PcDebug.h" #include "Log.h" #include #include #include #include #include using namespace std; OutputFile::OutputFile(const std::string& filename, bool show_metadata) : ModOutput(), ModMetadata(), myShowMetadata(show_metadata), myFilename(filename) { PDEBUG("OutputFile::OutputFile(filename: %s) @ %p\n", filename.c_str(), this); FILE* fd = fopen(filename.c_str(), "w"); if (fd == nullptr) { perror(filename.c_str()); throw std::runtime_error( "OutputFile::OutputFile() unable to open file!"); } myFile.reset(fd); } int OutputFile::process(Buffer* dataIn) { PDEBUG("OutputFile::process(%p)\n", dataIn); assert(dataIn != nullptr); if (fwrite(dataIn->getData(), dataIn->getLength(), 1, myFile.get()) == 0) { throw std::runtime_error( "OutputFile::process() unable to write to file!"); } return dataIn->getLength(); } meta_vec_t OutputFile::process_metadata(const meta_vec_t& metadataIn) { if (myShowMetadata) { stringstream ss; frame_timestamp first_ts; for (const auto& md : metadataIn) { // The following code assumes TM I, where we get called every 96ms. // Support for other transmission modes skipped because this is mostly // debugging code. if (md.ts.fp == 0 or md.ts.fp == 4) { first_ts = md.ts; } ss << " FCT=" << md.ts.fct << " FP=" << (int)md.ts.fp; if (md.ts.timestamp_valid) { ss << " TS=" << md.ts.timestamp_sec << " + " << std::fixed << (double)md.ts.timestamp_pps / 163840000.0 << ";"; } else { ss << " TS invalid;"; } } if (myLastTimestamp.timestamp_valid) { if (first_ts.timestamp_valid) { uint32_t timestamp = myLastTimestamp.timestamp_pps; timestamp += 96 << 14; // Shift 96ms by 14 to Timestamp level 2 if (timestamp > 0xf9FFff) { timestamp -= 0xfa0000; // Substract 16384000, corresponding to one second myLastTimestamp.timestamp_sec += 1; } myLastTimestamp.timestamp_pps = timestamp; if (myLastTimestamp.timestamp_sec != first_ts.timestamp_sec or myLastTimestamp.timestamp_pps != first_ts.timestamp_pps) { ss << " TS wrong interval; "; } myLastTimestamp = first_ts; } else { ss << " TS of FP=0 MISSING; "; myLastTimestamp.timestamp_valid = false; } } else { // Includes invalid and valid cases myLastTimestamp = first_ts; } if (metadataIn.empty()) { etiLog.level(debug) << "Output File got no metadata"; } else { using namespace std::chrono; const auto now = system_clock::now(); const int64_t ticks_now = duration_cast(now.time_since_epoch()).count(); //const int64_t first_ts_ticks = first_ts.timestamp_sec * 1000 + first_ts.timestamp_pps / 16384; const int64_t first_ts_ticks = std::llrint(first_ts.get_real_secs() * 1000); ss << " DELTA: " << first_ts_ticks - ticks_now << "ms;"; etiLog.level(debug) << "Output File metadata: " << ss.str(); } } return {}; } Opendigitalradio-ODR-DabMod-f7eedef/src/OutputFile.h000066400000000000000000000032741475762153200224670ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include "EtiReader.h" #include "TimestampDecoder.h" #include #include #include #include class OutputFile : public ModOutput, public ModMetadata { public: OutputFile(const std::string& filename, bool show_metadata); virtual int process(Buffer* dataIn) override; const char* name() override { return "OutputFile"; } virtual meta_vec_t process_metadata( const meta_vec_t& metadataIn) override; protected: bool myShowMetadata = false; frame_timestamp myLastTimestamp; std::string myFilename; struct FILEDeleter{ void operator()(FILE* fd){ if (fd) fclose(fd); }}; std::unique_ptr myFile; }; Opendigitalradio-ODR-DabMod-f7eedef/src/OutputMemory.cpp000066400000000000000000000045711475762153200234140ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "OutputMemory.h" #include "PcDebug.h" #include OutputMemory::OutputMemory(Buffer* dataOut) : ModOutput() { PDEBUG("OutputMemory::OutputMemory(%p) @ %p\n", dataOut, this); m_dataOut = dataOut; #if OUTPUT_MEM_HISTOGRAM myMax = 0.0f; for (int i = 0; i < HIST_BINS; i++) { myHistogram[i] = 0.0f; } #endif } OutputMemory::~OutputMemory() { #if OUTPUT_MEM_HISTOGRAM fprintf(stderr, "* OutputMemory max %f\n", myMax); fprintf(stderr, "* HISTOGRAM\n"); for (int i = 0; i < HIST_BINS; i++) { fprintf(stderr, "** %5d - %5d: %ld\n", i * HIST_BIN_SIZE, (i+1) * HIST_BIN_SIZE - 1, myHistogram[i]); } #endif PDEBUG("OutputMemory::~OutputMemory() @ %p\n", this); } int OutputMemory::process(Buffer* dataIn) { PDEBUG("OutputMemory::process(dataIn: %p)\n", dataIn); *m_dataOut = *dataIn; #if OUTPUT_MEM_HISTOGRAM const float* in = (const float*)dataIn->getData(); const size_t len = dataIn->getLength() / sizeof(float); for (size_t i = 0; i < len; i++) { float absval = fabsf(in[i]); if (myMax < absval) myMax = absval; myHistogram[lrintf(absval) / HIST_BIN_SIZE]++; } #endif return m_dataOut->getLength(); } meta_vec_t OutputMemory::process_metadata(const meta_vec_t& metadataIn) { m_metadata = metadataIn; return {}; } meta_vec_t OutputMemory::get_latest_metadata() { return m_metadata; } Opendigitalradio-ODR-DabMod-f7eedef/src/OutputMemory.h000066400000000000000000000037531475762153200230620ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif // This enables a rudimentary histogram functionality // It gets printed when the OutputMemory gets destroyed #define OUTPUT_MEM_HISTOGRAM 0 #if OUTPUT_MEM_HISTOGRAM // The samples can go up to 100000 in value, make // sure that HIST_BINS * HIST_BIN_SIZE is large // enough ! # define HIST_BINS 10 # define HIST_BIN_SIZE 10000 #endif #include "ModPlugin.h" class OutputMemory : public ModOutput, public ModMetadata { public: OutputMemory(Buffer* dataOut); virtual ~OutputMemory(); OutputMemory(OutputMemory& other) = delete; OutputMemory& operator=(OutputMemory& other) = delete; virtual int process(Buffer* dataIn) override; const char* name() override { return "OutputMemory"; } virtual meta_vec_t process_metadata( const meta_vec_t& metadataIn) override; meta_vec_t get_latest_metadata(void); protected: Buffer* m_dataOut; meta_vec_t m_metadata; #if OUTPUT_MEM_HISTOGRAM // keep track of max value float myMax; long int myHistogram[HIST_BINS]; #endif }; Opendigitalradio-ODR-DabMod-f7eedef/src/OutputZeroMQ.cpp000066400000000000000000000041601475762153200233130ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "OutputZeroMQ.h" #include "PcDebug.h" #include #include #include #if defined(HAVE_ZEROMQ) OutputZeroMQ::OutputZeroMQ(std::string endpoint, int type, Buffer* dataOut) : ModOutput(), m_type(type), m_zmq_context(1), m_zmq_sock(m_zmq_context, type), m_endpoint(endpoint) { PDEBUG("OutputZeroMQ::OutputZeroMQ(%p) @ %p\n", dataOut, this); std::stringstream ss; ss << "OutputZeroMQ(" << m_endpoint << " "; if (type == ZMQ_PUB) { ss << "ZMQ_PUB"; } else if (type == ZMQ_REP) { ss << "ZMQ_REP"; } else { throw std::invalid_argument("ZMQ socket type unknown"); } ss << ")"; m_name = ss.str(); m_zmq_sock.bind(m_endpoint.c_str()); } int OutputZeroMQ::process(Buffer* dataIn) { PDEBUG("OutputZeroMQ::process" "(dataIn: %p)\n", dataIn); if (m_type == ZMQ_REP) { // A ZMQ_REP socket requires a request first zmq::message_t msg; const auto rr = m_zmq_sock.recv(msg, zmq::recv_flags::none); (void)rr; } m_zmq_sock.send(zmq::const_buffer{dataIn->getData(), dataIn->getLength()}); return dataIn->getLength(); } #endif // HAVE_ZEROMQ Opendigitalradio-ODR-DabMod-f7eedef/src/OutputZeroMQ.h000066400000000000000000000032561475762153200227650ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_ZEROMQ) #include "ModPlugin.h" #include "zmq.hpp" class OutputZeroMQ : public ModOutput { public: OutputZeroMQ(std::string endpoint, int type, Buffer* dataOut = nullptr); virtual int process(Buffer* dataIn) override; const char* name() override { return m_name.c_str(); } protected: int m_type; // zmq socket type zmq::context_t m_zmq_context; // handle for the zmq context zmq::socket_t m_zmq_sock; // handle for the zmq publisher socket std::string m_endpoint; // On which port to listen: e.g. // tcp://*:58300 std::string m_name; }; #endif // HAVE_ZEROMQ Opendigitalradio-ODR-DabMod-f7eedef/src/PAPRStats.cpp000066400000000000000000000073231475762153200225020ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "PAPRStats.h" #include #include #include #if defined(TEST) /* compile with g++ -std=c++11 -Wall -DTEST PAPRStats.cpp -o paprtest */ # include #endif PAPRStats::PAPRStats(size_t num_blocks_to_accumulate) : m_num_blocks_to_accumulate(num_blocks_to_accumulate) { } void PAPRStats::process_block(const complexf* data, size_t data_len) { double norm_peak = 0; double rms2 = 0; for (size_t i = 0; i < data_len; i++) { const double x_norm = std::norm(data[i]); if (x_norm > norm_peak) { norm_peak = x_norm; } rms2 += x_norm; } rms2 /= data_len; #if defined(TEST) std::cerr << "Accumulating peak " << norm_peak << " rms2 " << rms2 << std::endl; #endif m_squared_peaks.push_back(norm_peak); m_squared_mean.push_back(rms2); if (m_squared_mean.size() > m_num_blocks_to_accumulate) { m_squared_mean.pop_front(); m_squared_peaks.pop_front(); } } double PAPRStats::calculate_papr() const { if (m_squared_mean.size() < m_num_blocks_to_accumulate) { return 0; } if (m_squared_mean.size() != m_squared_peaks.size()) { throw std::logic_error("Invalid PAPR measurement sizes"); } double peak = 0; double rms2 = 0; for (size_t i = 0; i < m_squared_peaks.size(); i++) { if (m_squared_peaks[i] > peak) { peak = m_squared_peaks[i]; } rms2 += m_squared_mean[i]; } // This assumes all blocks given to process have the same length rms2 /= m_squared_peaks.size(); #if defined(TEST) std::cerr << "Calculate peak " << peak << " rms2 " << rms2 << std::endl; #endif return 10.0 * std::log10(peak / rms2); } void PAPRStats::clear() { m_squared_peaks.clear(); m_squared_mean.clear(); } #if defined(TEST) /* Test python code: import numpy as np vec = 0.5 * np.exp(np.complex(0, 0.3) * np.arange(40)) vec[26] = 10.0 * vec[26] rms = np.mean(vec * np.conj(vec)).real peak = np.amax(vec * np.conj(vec)).real print("rms {}".format(rms)) print("peak {}".format(peak)) print(10. * np.log10(peak / rms)) */ int main(int argc, char **argv) { using namespace std; vector vec(40); for (size_t i = 0; i < vec.size(); i++) { vec[i] = polar(0.5, 0.3 * i); if (i == 26) { vec[i] *= 10; } cout << " " << vec[i]; } cout << endl; PAPRStats stats(4); for (size_t i = 0; i < 3; i++) { stats.process_block(vec.data(), vec.size()); } const auto papr0 = stats.calculate_papr(); if (papr0 != 0) { cerr << "Expected 0, got " << papr0 << endl; } stats.process_block(vec.data(), vec.size()); const auto papr1 = stats.calculate_papr(); cout << "PAPR = " << papr1 << " dB" << endl; } #endif Opendigitalradio-ODR-DabMod-f7eedef/src/PAPRStats.h000066400000000000000000000041351475762153200221450ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include /* Helper class to calculate Peak-to-average-power ratio. * Definition of PAPR: * * PAPR_dB = 10 * log_10 ( abs(x_peak)^2 / x_rms^2 ) * * with abs(x_peak) the peak amplitude of the signal, and * x_rms the Root Mean Squared. * * x_rms^2 = 1/n * Sum abs(x_n)^2 * = 1/n * Sum norm(x_n) * * Given that peaks are rare in a DAB signal, we want to accumulate * several seconds worth of samples to do our calculation. */ class PAPRStats { typedef std::complex complexf; public: PAPRStats(size_t num_blocks_to_accumulate); /* Push in a new block of samples to measure. calculate_papr() * assumes all blocks have the same size. */ void process_block(const complexf* data, size_t data_len); /* Returns PAPR in dB if enough blocks were processed, or * 0 otherwise. */ double calculate_papr(void) const; void clear(void); private: size_t m_num_blocks_to_accumulate; std::deque m_squared_peaks; std::deque m_squared_mean; }; Opendigitalradio-ODR-DabMod-f7eedef/src/PcDebug.h000066400000000000000000000021651475762153200216760ustar00rootroot00000000000000/* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifndef PC_DEBUG_H #define PC_DEBUG_H #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #define LOG stderr #ifndef PDEBUG # ifdef TRACE # define PDEBUG(fmt, args...) fprintf (LOG, fmt , ## args) # else # define PDEBUG(fmt, args...) # endif #endif #endif // PC_DEBUG_H Opendigitalradio-ODR-DabMod-f7eedef/src/PhaseReference.cpp000066400000000000000000000130531475762153200235750ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "PhaseReference.h" #include "PcDebug.h" #include /* ETSI EN 300 401 Table 43 (Clause 14.3.2) * Contains h_{i,k} values */ static const uint8_t d_h[4][32] = { /* h0 */ { 0, 2, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 1, 1, 0, 2, 0, 0, 0, 0, 1, 1, 2, 0, 0, 0, 2, 2, 1, 1 }, /* h1 */ { 0, 3, 2, 3, 0, 1, 3, 0, 2, 1, 2, 3, 2, 3, 3, 0, 0, 3, 2, 3, 0, 1, 3, 0, 2, 1, 2, 3, 2, 3, 3, 0 }, /* h2 */ { 0, 0, 0, 2, 0, 2, 1, 3, 2, 2, 0, 2, 2, 0, 1, 3, 0, 0, 0, 2, 0, 2, 1, 3, 2, 2, 0, 2, 2, 0, 1, 3 }, /* h3 */ { 0, 1, 2, 1, 0, 3, 3, 2, 2, 3, 2, 1, 2, 1, 3, 2, 0, 1, 2, 1, 0, 3, 3, 2, 2, 3, 2, 1, 2, 1, 3, 2 } }; /* EN 300 401, Clause 14.3.2: * \phi_k = (\pi / 2) * (h_{i,k-k'} + n * * where "The indices i, k' and the parameter n are specified as functions of * the carrier index k for the four transmission modes in tables 44 to 47." * * Tables 44 to 47 describe the frequency interleaving done in * FrequencyInterleaver. */ PhaseReference::PhaseReference(unsigned int dabmode, bool fixedPoint) : ModInput(), d_dabmode(dabmode), d_fixedPoint(fixedPoint) { PDEBUG("PhaseReference::PhaseReference(%u) @ %p\n", dabmode, this); switch (d_dabmode) { case 1: d_carriers = 1536; break; case 2: d_carriers = 384; break; case 3: d_carriers = 192; break; case 4: d_dabmode = 0; case 0: d_carriers = 768; break; default: throw std::runtime_error( "PhaseReference::PhaseReference DAB mode not valid!"); } if (d_fixedPoint) { d_phaseRefFixed.fillData(d_dabmode, d_carriers); } else { d_phaseRefCF32.fillData(d_dabmode, d_carriers); } } static const int table[][48][2] = { { // Mode 0/4 // Positive part { 0, 0 }, { 3, 1 }, { 2, 0 }, { 1, 2 }, { 0, 0 }, { 3, 1 }, { 2, 2 }, { 1, 2 }, { 0, 2 }, { 3, 1 }, { 2, 3 }, { 1, 0 }, // Negative part { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 0, 2 }, { 1, 2 }, { 2, 0 }, { 3, 3 }, { 0, 3 }, { 1, 1 }, { 2, 3 }, { 3, 2 }, }, { // Mode 1 // Positive part { 0, 3 }, { 3, 1 }, { 2, 1 }, { 1, 1 }, { 0, 2 }, { 3, 2 }, { 2, 1 }, { 1, 0 }, { 0, 2 }, { 3, 2 }, { 2, 3 }, { 1, 3 }, { 0, 0 }, { 3, 2 }, { 2, 1 }, { 1, 3 }, { 0, 3 }, { 3, 3 }, { 2, 3 }, { 1, 0 }, { 0, 3 }, { 3, 0 }, { 2, 1 }, { 1, 1 }, // Negative part { 0, 1 }, { 1, 2 }, { 2, 0 }, { 3, 1 }, { 0, 3 }, { 1, 2 }, { 2, 2 }, { 3, 3 }, { 0, 2 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 3 }, { 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 1 }, { 0, 1 }, { 1, 3 }, { 2, 1 }, { 3, 2 }, }, { // Mode 2 // Positive part { 2, 0 }, { 1, 2 }, { 0, 2 }, { 3, 1 }, { 2, 0 }, { 1, 3 }, // Negative part { 0, 2 }, { 1, 3 }, { 2, 2 }, { 3, 2 }, { 0, 1 }, { 1, 2 }, }, { // Mode 3 // Positive part { 3, 2 }, { 2, 2 }, { 1, 2 }, // Negative part { 0, 2 }, { 1, 3 }, { 2, 0 }, }, }; template <> complexf PhaseRefGen::convert(uint8_t data) { const complexf value[] = { complexf(1, 0), complexf(0, 1), complexf(-1, 0), complexf(0, -1), }; return value[data % 4]; } template <> complexfix PhaseRefGen::convert(uint8_t data) { constexpr auto one = fixed_16{1}; constexpr auto zero = fixed_16{0}; const complexfix value[] = { complexfix(one, zero), complexfix(zero, one), complexfix(-one, zero), complexfix(zero, -one), }; return value[data % 4]; } template void PhaseRefGen::fillData(unsigned int dabmode, size_t carriers) { dataIn.resize(carriers); if (dataIn.size() != carriers) { throw std::runtime_error( "PhaseReference::fillData dataIn has incorrect size!"); } for (size_t index = 0, offset = 0; index < dataIn.size(); ++offset) { for (size_t k = 0; k < 32; ++k) { dataIn[index++] = convert( d_h[ table[dabmode][offset][0] ][k] + table[dabmode][offset][1] ); } } } int PhaseReference::process(Buffer* dataOut) { PDEBUG("PhaseReference::process(dataOut: %p)\n", dataOut); if (d_fixedPoint) { dataOut->setData(d_phaseRefFixed.dataIn.data(), d_carriers * sizeof(complexfix)); } else { dataOut->setData(d_phaseRefCF32.dataIn.data(), d_carriers * sizeof(complexf)); } return 1; } Opendigitalradio-ODR-DabMod-f7eedef/src/PhaseReference.h000066400000000000000000000032071475762153200232420ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include template struct PhaseRefGen { std::vector dataIn; void fillData(unsigned int dabmode, size_t carriers); private: T convert(uint8_t data); }; class PhaseReference : public ModInput { public: PhaseReference(unsigned int dabmode, bool fixedPoint); int process(Buffer* dataOut) override; const char* name() override { return "PhaseReference"; } protected: unsigned int d_dabmode; bool d_fixedPoint; size_t d_carriers; PhaseRefGen d_phaseRefCF32; PhaseRefGen d_phaseRefFixed; }; Opendigitalradio-ODR-DabMod-f7eedef/src/PrbsGenerator.cpp000066400000000000000000000120041475762153200234660ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "PrbsGenerator.h" #include "PcDebug.h" #include #include #include #include PrbsGenerator::PrbsGenerator(size_t framesize, uint32_t polynomial, uint32_t accum, size_t init) : ModPlugin(), d_framesize(framesize), d_polynomial(polynomial), d_accum(accum), d_accum_init(accum), d_init(init) { PDEBUG("PrbsGenerator::PrbsGenerator(%zu, %u, %u, %zu) @ %p\n", framesize, polynomial, accum, init, this); gen_prbs_table(); gen_weight_table(); } PrbsGenerator::~PrbsGenerator() { PDEBUG("PrbsGenerator::~PrbsGenerator() @ %p\n", this); } /* * Generate a table of matrix products to update a 32-bit PRBS generator. */ void PrbsGenerator::gen_prbs_table() { int i; for (i = 0; i < 4; ++i) { int j; for (j = 0; j < 256; ++j) { uint32_t prbs_accum = ((uint32_t)j << (i * 8)); int k; for (k = 0; k < 8; ++k) { prbs_accum = (prbs_accum << 1) ^ parity_check(prbs_accum & d_polynomial); } d_prbs_table[i][j] = (prbs_accum & 0xff); } } } /* * Generate the weight table. */ void PrbsGenerator::gen_weight_table() { int i; for (i = 0; i < 256; ++i) { unsigned char mask=1U, ones_count = 0U; int j; for (j = 0; j < 8; ++j) { ones_count += ((i & mask) != 0U); mask = mask << 1; } d_weight[i] = ones_count; } } /* * Generate a parity check for a 32-bit word. */ uint32_t PrbsGenerator::parity_check(uint32_t prbs_accum) { uint32_t mask=1UL, parity=0UL; int i; for (i = 0; i < 32; ++i) { parity ^= ((prbs_accum & mask) != 0UL); mask <<= 1; } return parity; } /* * Update a 32-bit PRBS generator eight bits at a time. */ uint32_t PrbsGenerator::update_prbs() { unsigned char acc_lsb = 0; int i; for (i = 0; i < 4; ++i) { // PDEBUG("0x%x = 0x%x ^ 0x%x\n", // acc_lsb ^ d_prbs_table [i][(d_accum >> (i * 8)) & 0xff], // acc_lsb, d_prbs_table [i][(d_accum >> (i * 8)) & 0xff]); acc_lsb ^= d_prbs_table[i][(d_accum >> (i * 8)) & 0xff]; } return (d_accum << 8) ^ ((uint32_t)acc_lsb); } int PrbsGenerator::process( std::vector dataIn, std::vector dataOut) { PDEBUG("PrbsGenerator::process(dataIn: %zu, dataOut: %zu)\n", dataIn.size(), dataOut.size()); if (dataIn.size() > 1) { throw std::runtime_error("Invalid dataIn size for PrbsGenerator " + std::to_string(dataIn.size())); } if (dataOut.size() != 1) { throw std::runtime_error("Invalid dataOut size for PrbsGenerator " + std::to_string(dataOut.size())); } dataOut[0]->setLength(d_framesize); unsigned char* out = reinterpret_cast(dataOut[0]->getData()); // Initialization if (d_accum_init) { d_accum = d_accum_init; } else { d_accum = 0; while (d_accum < d_polynomial) { d_accum <<= 1; d_accum |= 1; } } //PDEBUG("Polynomial: 0x%x\n", d_polynomial); //PDEBUG("Init accum: 0x%x\n", d_accum); size_t i = 0; while (i < d_init) { out[i++] = 0xff; } for (; i < d_framesize; ++i) { d_accum = update_prbs(); if ((d_accum_init == 0xa9) && (i % 188 == 0)) { // DVB energy dispersal out[i] = 0; } else { out[i] = (unsigned char)(d_accum & 0xff); } //PDEBUG("accum: 0x%x\n", d_accum); } if (not dataIn.empty()) { PDEBUG(" mixing input\n"); const unsigned char* in = reinterpret_cast(dataIn[0]->getData()); if (dataIn[0]->getLength() != dataOut[0]->getLength()) { PDEBUG("%zu != %zu\n", dataIn[0]->getLength(), dataOut[0]->getLength()); throw std::runtime_error("PrbsGenerator::process " "input size is not equal to output size!\n"); } for (size_t j = 0; j < dataOut[0]->getLength(); ++j) { out[j] ^= in[j]; } } return dataOut[0]->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/PrbsGenerator.h000066400000000000000000000040361475762153200231410ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include /* The PrbsGenerator can work as a ModInput generating a Prbs * sequence from the given parameters only, or as a ModCodec * XORing incoming data with the PRBS */ class PrbsGenerator : public ModPlugin { private: uint32_t parity_check(uint32_t prbs_accum); void gen_prbs_table(); uint32_t update_prbs(); void gen_weight_table(); size_t d_framesize; // table of matrix products used to update a 32-bit PRBS generator uint32_t d_prbs_table [4][256]; // table of weights for 8-bit bytes unsigned char d_weight[256]; // PRBS polynomial generator uint32_t d_polynomial; // PRBS accumulator uint32_t d_accum; uint32_t d_accum_init; // Initialization size size_t d_init; public: PrbsGenerator(size_t framesize, uint32_t polynomial, uint32_t accum = 0, size_t init = 0); virtual ~PrbsGenerator(); int process(std::vector dataIn, std::vector dataOut); const char* name() { return "PrbsGenerator"; } }; Opendigitalradio-ODR-DabMod-f7eedef/src/PuncturingEncoder.cpp000066400000000000000000000137151475762153200243610ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #include #include #include #include #include "PuncturingEncoder.h" #include "PcDebug.h" PuncturingEncoder::PuncturingEncoder() : ModCodec(), d_num_cu(0), d_in_block_size(0), d_out_block_size(0) { PDEBUG("PuncturingEncoder() @ %p\n", this); } PuncturingEncoder::PuncturingEncoder( size_t num_cu) : ModCodec(), d_num_cu(num_cu), d_in_block_size(0), d_out_block_size(0) { PDEBUG("PuncturingEncoder(%zu) @ %p\n", num_cu, this); } void PuncturingEncoder::adjust_item_size() { PDEBUG("PuncturingEncoder::adjust_item_size()\n"); int in_size = 0; int out_size = 0; int length = 0; for (const auto& rule : d_rules) { for (length = rule.length(); length > 0; length -= 4) { out_size += rule.bit_size(); in_size += 4; } } if (d_tail_rule) { in_size += d_tail_rule->length(); out_size += d_tail_rule->bit_size(); } d_in_block_size = in_size; d_out_block_size = (out_size + 7) / 8; PDEBUG(" Puncturing encoder ratio (out/in): %zu / %zu\n", d_out_block_size, d_in_block_size); } void PuncturingEncoder::append_rule(const PuncturingRule& rule) { PDEBUG("append_rule(rule(%zu, 0x%x))\n", rule.length(), rule.pattern()); d_rules.push_back(rule); adjust_item_size(); } void PuncturingEncoder::append_tail_rule(const PuncturingRule& rule) { PDEBUG("append_tail_rule(rule(%zu, 0x%x))\n", rule.length(), rule.pattern()); d_tail_rule.reset(new PuncturingRule(rule)); adjust_item_size(); } int PuncturingEncoder::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("PuncturingEncoder::process" "(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); size_t in_count = 0; size_t out_count = 0; size_t bit_count = 0; size_t length; unsigned char data; uint32_t mask; uint32_t pattern; auto rule_it = d_rules.begin(); PDEBUG(" in block size: %zu\n", d_in_block_size); PDEBUG(" out block size: %zu\n", d_out_block_size); if (d_num_cu > 0) { if (d_num_cu * 8 == d_out_block_size + 1) { /* EN 300 401 Table 31 in 11.3.1 UEP coding specifies * that we need one byte of padding */ d_out_block_size = d_num_cu * 8; } if (d_num_cu * 8 != d_out_block_size) { throw std::runtime_error( "PuncturingEncoder encoder initialisation failed. " " CU: " + std::to_string(d_num_cu) + " block_size: " + std::to_string(d_out_block_size)); } } dataOut->setLength(d_out_block_size); const unsigned char* in = reinterpret_cast(dataIn->getData()); unsigned char* out = reinterpret_cast(dataOut->getData()); if (dataIn->getLength() != d_in_block_size) { throw std::runtime_error( "PuncturingEncoder::process wrong input size"); } if (d_tail_rule) { d_in_block_size -= d_tail_rule->length(); } while (in_count < d_in_block_size) { for (length = rule_it->length(); length > 0; length -= 4) { mask = 0x80000000; pattern = rule_it->pattern(); for (int i = 0; i < 4; ++i) { data = in[in_count++]; for (int j = 0; j < 8; ++j) { if (pattern & mask) { out[out_count] <<= 1; out[out_count] |= data >> 7; if (++bit_count == 8) { bit_count = 0; ++out_count; } } data <<= 1; mask >>= 1; } } } if (++rule_it == d_rules.end()) { rule_it = d_rules.begin(); } } if (d_tail_rule) { d_in_block_size += d_tail_rule->length(); mask = 0x800000; pattern = d_tail_rule->pattern(); length = d_tail_rule->length(); for (size_t i = 0; i < length; ++i) { data = in[in_count++]; for (int j = 0; j < 8; ++j) { if (pattern & mask) { out[out_count] <<= 1; out[out_count] |= data >> 7; if (++bit_count == 8) { bit_count = 0; ++out_count; } } data <<= 1; mask >>= 1; } } } while (bit_count) { out[out_count] <<= 1; if (++bit_count == 8) { bit_count = 0; ++out_count; } } for (size_t i = out_count; i < dataOut->getLength(); ++i) { out[out_count++] = 0; } assert(out_count == dataOut->getLength()); if (out_count != dataOut->getLength()) { throw std::runtime_error("PuncturingEncoder::process output size " "does not correspond!"); } return d_out_block_size; } Opendigitalradio-ODR-DabMod-f7eedef/src/PuncturingEncoder.h000066400000000000000000000044051475762153200240220ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include "PuncturingRule.h" #include "ModPlugin.h" class PuncturingEncoder : public ModCodec { public: /* Initialise a puncturer that does not check if the * outgoing data requires padding. To be used for the * FIC. The size of the output buffer is derived from * the puncturing rules only */ PuncturingEncoder(void); /* Initialise a puncturer that checks if there is up to * one byte padding needed in the output buffer. See * EN 300 401 Table 31 in 11.3.1 UEP coding. Up to one * byte of padding is added */ PuncturingEncoder(size_t num_cu); void append_rule(const PuncturingRule& rule); void append_tail_rule(const PuncturingRule& rule); int process(Buffer* const dataIn, Buffer* dataOut); const char* name() { return "PuncturingEncoder"; } size_t getInputSize() { return d_in_block_size; } size_t getOutputSize() { return d_out_block_size; } private: size_t d_num_cu; size_t d_in_block_size; size_t d_out_block_size; std::vector d_rules; // We use a unique_ptr because we don't want to depend // on boost::optional here std::unique_ptr d_tail_rule; void adjust_item_size(); }; Opendigitalradio-ODR-DabMod-f7eedef/src/PuncturingRule.cpp000066400000000000000000000023421475762153200237030ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "PcDebug.h" #include "PuncturingRule.h" #include PuncturingRule::PuncturingRule( const size_t length, const uint32_t pattern) : d_length(length), d_pattern(pattern) { } size_t PuncturingRule::bit_size() const { size_t bits = 0; for (uint32_t mask = 0x80000000; mask != 0; mask >>= 1) { if (d_pattern & mask) { ++bits; } } return bits; } Opendigitalradio-ODR-DabMod-f7eedef/src/PuncturingRule.h000066400000000000000000000023401475762153200233460ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include #include class PuncturingRule { public: PuncturingRule( const size_t length, const uint32_t pattern); size_t length() const { return d_length; } size_t bit_size() const; const uint32_t pattern() const { return d_pattern; } private: size_t d_length; uint32_t d_pattern; }; Opendigitalradio-ODR-DabMod-f7eedef/src/QpskSymbolMapper.cpp000066400000000000000000000221021475762153200241620ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #include #include #include #include #ifdef __SSE__ # include #endif // __SSE__ #include "QpskSymbolMapper.h" #include "PcDebug.h" QpskSymbolMapper::QpskSymbolMapper(size_t carriers, bool fixedPoint) : ModCodec(), m_fixedPoint(fixedPoint), m_carriers(carriers) { } int QpskSymbolMapper::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("QpskSymbolMapper::process" "(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); // 4 output complex symbols per input byte if (m_fixedPoint) { dataOut->setLength(dataIn->getLength() * 4 * sizeof(complexfix)); using fixed_t = complexfix::value_type; const uint8_t* in = reinterpret_cast(dataIn->getData()); fixed_t* out = reinterpret_cast(dataOut->getData()); if (dataIn->getLength() % (m_carriers / 4) != 0) { throw std::runtime_error( "QpskSymbolMapper::process input size not valid!"); } constexpr fixed_t v = static_cast(M_SQRT1_2); const static fixed_t symbols[16][4] = { { v, v, v, v}, { v, v, v, -v}, { v, -v, v, v}, { v, -v, v, -v}, { v, v, -v, v}, { v, v, -v, -v}, { v, -v, -v, v}, { v, -v, -v, -v}, {-v, v, v, v}, {-v, v, v, -v}, {-v, -v, v, v}, {-v, -v, v, -v}, {-v, v, -v, v}, {-v, v, -v, -v}, {-v, -v, -v, v}, {-v, -v, -v, -v} }; size_t inOffset = 0; size_t outOffset = 0; uint8_t tmp; for (size_t i = 0; i < dataIn->getLength(); i += m_carriers / 4) { for (size_t j = 0; j < m_carriers / 8; ++j) { tmp = (in[inOffset] & 0xc0) >> 4; tmp |= (in[inOffset + (m_carriers / 8)] & 0xc0) >> 6; memcpy(&out[outOffset], symbols[tmp], sizeof(fixed_t) * 4); tmp = (in[inOffset] & 0x30) >> 2; tmp |= (in[inOffset + (m_carriers / 8)] & 0x30) >> 4; memcpy(&out[outOffset + 4], symbols[tmp], sizeof(fixed_t) * 4); tmp = (in[inOffset] & 0x0c); tmp |= (in[inOffset + (m_carriers / 8)] & 0x0c) >> 2; memcpy(&out[outOffset + 8], symbols[tmp], sizeof(fixed_t) * 4); tmp = (in[inOffset] & 0x03) << 2; tmp |= (in[inOffset + (m_carriers / 8)] & 0x03); memcpy(&out[outOffset + 12], symbols[tmp], sizeof(fixed_t) * 4); ++inOffset; outOffset += 4*4; } inOffset += m_carriers / 8; } } else { dataOut->setLength(dataIn->getLength() * 4 * sizeof(complexf)); #ifdef __SSE__ const uint8_t* in = reinterpret_cast(dataIn->getData()); __m128* out = reinterpret_cast<__m128*>(dataOut->getData()); if (dataIn->getLength() % (m_carriers / 4) != 0) { throw std::runtime_error( "QpskSymbolMapper::process input size not valid: " + std::to_string(dataIn->getLength()) + "(input size) % (" + std::to_string(m_carriers) + " (carriers) / 4) != 0"); } const static __m128 symbols[16] = { _mm_setr_ps( M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2), _mm_setr_ps( M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2), _mm_setr_ps(-M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2) }; size_t inOffset = 0; size_t outOffset = 0; uint8_t tmp = 0; for (size_t i = 0; i < dataIn->getLength(); i += m_carriers / 4) { for (size_t j = 0; j < m_carriers / 8; ++j) { tmp = (in[inOffset] & 0xc0) >> 4; tmp |= (in[inOffset + (m_carriers / 8)] & 0xc0) >> 6; out[outOffset] = symbols[tmp]; tmp = (in[inOffset] & 0x30) >> 2; tmp |= (in[inOffset + (m_carriers / 8)] & 0x30) >> 4; out[outOffset + 1] = symbols[tmp]; tmp = (in[inOffset] & 0x0c); tmp |= (in[inOffset + (m_carriers / 8)] & 0x0c) >> 2; out[outOffset + 2] = symbols[tmp]; tmp = (in[inOffset] & 0x03) << 2; tmp |= (in[inOffset + (m_carriers / 8)] & 0x03); out[outOffset + 3] = symbols[tmp]; ++inOffset; outOffset += 4; } inOffset += m_carriers / 8; } #else // !__SSE__ const uint8_t* in = reinterpret_cast(dataIn->getData()); float* out = reinterpret_cast(dataOut->getData()); if (dataIn->getLength() % (m_carriers / 4) != 0) { throw std::runtime_error( "QpskSymbolMapper::process input size not valid!"); } if (dataOut->getLength() / sizeof(float) != dataIn->getLength() * 4 * 2) { // 4 output complex symbols per input byte throw std::runtime_error( "QpskSymbolMapper::process output size not valid!"); } const static float symbols[16][4] = { { M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, M_SQRT1_2}, { M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2}, { M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, M_SQRT1_2}, { M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2}, { M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2}, { M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2}, { M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2}, { M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2}, {-M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, M_SQRT1_2}, {-M_SQRT1_2, M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2}, {-M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, M_SQRT1_2}, {-M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2}, {-M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2}, {-M_SQRT1_2, M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2}, {-M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, M_SQRT1_2}, {-M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2, -M_SQRT1_2} }; size_t inOffset = 0; size_t outOffset = 0; uint8_t tmp; for (size_t i = 0; i < dataIn->getLength(); i += m_carriers / 4) { for (size_t j = 0; j < m_carriers / 8; ++j) { tmp = (in[inOffset] & 0xc0) >> 4; tmp |= (in[inOffset + (m_carriers / 8)] & 0xc0) >> 6; memcpy(&out[outOffset], symbols[tmp], sizeof(float) * 4); tmp = (in[inOffset] & 0x30) >> 2; tmp |= (in[inOffset + (m_carriers / 8)] & 0x30) >> 4; memcpy(&out[outOffset + 4], symbols[tmp], sizeof(float) * 4); tmp = (in[inOffset] & 0x0c); tmp |= (in[inOffset + (m_carriers / 8)] & 0x0c) >> 2; memcpy(&out[outOffset + 8], symbols[tmp], sizeof(float) * 4); tmp = (in[inOffset] & 0x03) << 2; tmp |= (in[inOffset + (m_carriers / 8)] & 0x03); memcpy(&out[outOffset + 12], symbols[tmp], sizeof(float) * 4); ++inOffset; outOffset += 4*4; } inOffset += m_carriers / 8; } #endif // __SSE__ } return 1; } Opendigitalradio-ODR-DabMod-f7eedef/src/QpskSymbolMapper.h000066400000000000000000000023071475762153200236340ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include class QpskSymbolMapper : public ModCodec { public: QpskSymbolMapper(size_t carriers, bool fixedPoint); int process(Buffer* const dataIn, Buffer* dataOut); const char* name() { return "QpskSymbolMapper"; } protected: bool m_fixedPoint; size_t m_carriers; }; Opendigitalradio-ODR-DabMod-f7eedef/src/Resampler.cpp000066400000000000000000000151701475762153200226520ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2014 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "Resampler.h" #include "PcDebug.h" #include #include #include #include #include #include #define FFT_REAL(x) x[0] #define FFT_IMAG(x) x[1] template T gcd(T a, T b) { if (b == 0) { return a; } return gcd(b, a % b); } Resampler::Resampler(size_t inputRate, size_t outputRate, size_t resolution) : ModCodec(), myFftPlan1(nullptr), myFftPlan2(nullptr), myFftIn(nullptr), myFftOut(nullptr), myBufferIn(nullptr), myBufferOut(nullptr), myFront(nullptr), myBack(nullptr), myWindow(nullptr) { PDEBUG("Resampler::Resampler(%zu, %zu) @ %p\n", inputRate, outputRate, this); size_t divisor = gcd(inputRate, outputRate); L = outputRate / divisor; M = inputRate / divisor; PDEBUG(" gcd: %zu, L: %zu, M: %zu\n", divisor, L, M); { size_t factor = resolution * 2 / M; if (factor & 1) { ++factor; } myFftSizeIn = factor * M; myFftSizeOut = factor * L; } PDEBUG(" FFT size in: %zu, FFT size out: %zu\n", myFftSizeIn, myFftSizeOut); if (myFftSizeIn > myFftSizeOut) { myFactor = 1.0f / myFftSizeIn * outputRate / inputRate; } else { myFactor = 1.0f / myFftSizeOut * outputRate / inputRate; } const int ret = posix_memalign((void**)(&myWindow), 16, myFftSizeIn * sizeof(float)); if (ret != 0) { throw std::runtime_error("memory allocation failed: " + std::to_string(ret)); } for (size_t i = 0; i < myFftSizeIn; ++i) { myWindow[i] = (float)(0.5 * (1.0 - cos(2.0 * M_PI * i / (myFftSizeIn - 1)))); PDEBUG("Window[%zu] = %f\n", i, myWindow[i]); } myFftIn = (FFT_TYPE*)fftwf_malloc(sizeof(FFT_TYPE) * myFftSizeIn); myFront = (FFT_TYPE*)fftwf_malloc(sizeof(FFT_TYPE) * myFftSizeIn); fftwf_set_timelimit(2); myFftPlan1 = fftwf_plan_dft_1d(myFftSizeIn, myFftIn, myFront, FFTW_FORWARD, FFTW_MEASURE); myBack = (FFT_TYPE*)fftwf_malloc(sizeof(FFT_TYPE) * myFftSizeOut); myFftOut = (FFT_TYPE*)fftwf_malloc(sizeof(FFT_TYPE) * myFftSizeOut); myFftPlan2 = fftwf_plan_dft_1d(myFftSizeOut, myBack, myFftOut, FFTW_BACKWARD, FFTW_MEASURE); myBufferIn = (FFT_TYPE*)fftwf_malloc(sizeof(FFT_TYPE) * myFftSizeIn / 2); myBufferOut = (FFT_TYPE*)fftwf_malloc(sizeof(FFT_TYPE) * myFftSizeOut / 2); memset(myBufferIn, 0, myFftSizeIn / 2 * sizeof(FFT_TYPE)); memset(myBufferOut, 0, myFftSizeOut / 2 * sizeof(FFT_TYPE)); } Resampler::~Resampler() { PDEBUG("Resampler::~Resampler() @ %p\n", this); if (myFftIn != nullptr) { fftwf_free(myFftIn); } if (myFftOut != nullptr) { fftwf_free(myFftOut); } if (myBufferIn != nullptr) { fftwf_free(myBufferIn); } if (myBufferOut != nullptr) { fftwf_free(myBufferOut); } if (myFront != nullptr) { fftwf_free(myFront); } if (myBack != nullptr) { fftwf_free(myBack); } if (myWindow != nullptr) { fftwf_free(myWindow); } fftwf_destroy_plan(myFftPlan1); fftwf_destroy_plan(myFftPlan2); } int Resampler::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("Resampler::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); dataOut->setLength(dataIn->getLength() * L / M); FFT_TYPE* in = reinterpret_cast(dataIn->getData()); FFT_TYPE* out = reinterpret_cast(dataOut->getData()); size_t sizeIn = dataIn->getLength() / sizeof(complexf); for (size_t i = 0, j = 0; i < sizeIn; i += myFftSizeIn / 2, j += myFftSizeOut / 2) { memcpy(myFftIn, myBufferIn, myFftSizeIn / 2 * sizeof(FFT_TYPE)); memcpy(myFftIn + (myFftSizeIn / 2), in + i, myFftSizeIn / 2 * sizeof(FFT_TYPE)); memcpy(myBufferIn, in + i, myFftSizeIn / 2 * sizeof(FFT_TYPE)); for (size_t k = 0; k < myFftSizeIn; ++k) { FFT_REAL(myFftIn[k]) *= myWindow[k]; FFT_IMAG(myFftIn[k]) *= myWindow[k]; } fftwf_execute(myFftPlan1); if (myFftSizeOut > myFftSizeIn) { memset(myBack, 0, myFftSizeOut * sizeof(FFT_TYPE)); memcpy(myBack, myFront, myFftSizeIn / 2 * sizeof(FFT_TYPE)); memcpy(&myBack[myFftSizeOut - (myFftSizeIn / 2)], &myFront[myFftSizeIn / 2], myFftSizeIn / 2 * sizeof(FFT_TYPE)); // Copy input Fs FFT_REAL(myBack[myFftSizeIn / 2]) = FFT_REAL(myFront[myFftSizeIn / 2]); FFT_IMAG(myBack[myFftSizeIn / 2]) = FFT_IMAG(myFront[myFftSizeIn / 2]); } else { memcpy(myBack, myFront, myFftSizeOut / 2 * sizeof(FFT_TYPE)); memcpy(&myBack[myFftSizeOut / 2], &myFront[myFftSizeIn - (myFftSizeOut / 2)], myFftSizeOut / 2 * sizeof(FFT_TYPE)); // Average output Fs from input FFT_REAL(myBack[myFftSizeOut / 2]) += FFT_REAL(myFront[myFftSizeOut / 2]); FFT_IMAG(myBack[myFftSizeOut / 2]) += FFT_IMAG(myFront[myFftSizeOut / 2]); FFT_REAL(myBack[myFftSizeOut / 2]) *= 0.5f; FFT_IMAG(myBack[myFftSizeOut / 2]) *= 0.5f; } for (size_t k = 0; k < myFftSizeOut; ++k) { FFT_REAL(myBack[k]) *= myFactor; FFT_IMAG(myBack[k]) *= myFactor; } fftwf_execute(myFftPlan2); for (size_t k = 0; k < myFftSizeOut / 2; ++k) { FFT_REAL(out[j + k]) = FFT_REAL(myBufferOut[k]) + FFT_REAL(myFftOut[k]); FFT_IMAG(out[j + k]) = FFT_IMAG(myBufferOut[k]) + FFT_IMAG(myFftOut[k]); } memcpy(myBufferOut, myFftOut + (myFftSizeOut / 2), (myFftSizeOut / 2) * sizeof(FFT_TYPE)); } return 1; } Opendigitalradio-ODR-DabMod-f7eedef/src/Resampler.h000066400000000000000000000033741475762153200223220ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2014 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include #define FFT_TYPE fftwf_complex #define FFT_PLAN fftwf_plan class Resampler : public ModCodec { public: Resampler(size_t inputRate, size_t outputRate, size_t resolution = 512); virtual ~Resampler(); Resampler(const Resampler&); Resampler& operator=(const Resampler&); int process(Buffer* const dataIn, Buffer* dataOut); const char* name() { return "Resampler"; } protected: FFT_PLAN myFftPlan1; FFT_PLAN myFftPlan2; size_t L; size_t M; size_t myFftSizeIn; size_t myFftSizeOut; FFT_TYPE* myFftIn; FFT_TYPE* myFftOut; FFT_TYPE* myBufferIn; FFT_TYPE* myBufferOut; FFT_TYPE* myFront; FFT_TYPE* myBack; float *myWindow; float myFactor; }; Opendigitalradio-ODR-DabMod-f7eedef/src/SignalMultiplexer.cpp000066400000000000000000000040001475762153200243560ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "SignalMultiplexer.h" #include "PcDebug.h" #include #include SignalMultiplexer::SignalMultiplexer() : ModMux() { PDEBUG("SignalMultiplexer::SignalMultiplexer() @ %p\n", this); } SignalMultiplexer::~SignalMultiplexer() { PDEBUG("SignalMultiplexer::~SignalMultiplexer() @ %p\n", this); } // dataIn[0] -> null symbol // dataIn[1] -> MSC symbols // dataIn[2] -> (optional) TII symbol int SignalMultiplexer::process(std::vector dataIn, Buffer* dataOut) { #ifdef TRACE fprintf(stderr, "SignalMultiplexer::process (dataIn:"); for (unsigned i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %p", dataIn[i]); } fprintf(stderr, ", sizeIn: "); for (unsigned i = 0; i < dataIn.size(); ++i) { fprintf(stderr, " %zu", dataIn[i]->getLength()); } fprintf(stderr, ", dataOut: %p, sizeOut: %zu)\n", dataOut, dataOut->getLength()); #endif assert(dataIn.size() == 2 or dataIn.size() == 3); if (dataIn.size() == 2) { *dataOut = *dataIn[0]; *dataOut += *dataIn[1]; } else if (dataIn.size() == 3) { *dataOut = *dataIn[2]; *dataOut += *dataIn[1]; } return dataOut->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/SignalMultiplexer.h000066400000000000000000000024331475762153200240330ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include class SignalMultiplexer : public ModMux { public: SignalMultiplexer(); virtual ~SignalMultiplexer(); SignalMultiplexer(const SignalMultiplexer&); SignalMultiplexer& operator=(const SignalMultiplexer&); int process(std::vector dataIn, Buffer* dataOut); const char* name() { return "SignalMultiplexer"; } }; Opendigitalradio-ODR-DabMod-f7eedef/src/SubchannelSource.cpp000066400000000000000000001114611475762153200241630ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "SubchannelSource.h" #include "PcDebug.h" #include "Log.h" #include #include #include #include #include #define P1 0xc8888888 #define P2 0xc888c888 #define P3 0xc8c8c888 #define P4 0xc8c8c8c8 #define P5 0xccc8c8c8 #define P6 0xccc8ccc8 #define P7 0xccccccc8 #define P8 0xcccccccc #define P9 0xeccccccc #define P10 0xeccceccc #define P11 0xecececcc #define P12 0xecececec #define P13 0xeeececec #define P14 0xeeeceeec #define P15 0xeeeeeeec #define P16 0xeeeeeeee #define P17 0xfeeeeeee #define P18 0xfeeefeee #define P19 0xfefefeee #define P20 0xfefefefe #define P21 0xfffefefe #define P22 0xfffefffe #define P23 0xfffffffe #define P24 0xffffffff const std::vector& SubchannelSource::get_rules() const { return d_puncturing_rules; } SubchannelSource::SubchannelSource( uint16_t sad, uint16_t stl, uint8_t tpl ) : ModInput(), d_start_address(sad), d_framesize(stl * 8), d_protection(tpl) { PDEBUG("SubchannelSource::SubchannelSource(...) @ %p\n", this); PDEBUG(" Start address: %zu\n", d_start_address); PDEBUG(" Framesize: %zu\n", d_framesize); PDEBUG(" Protection: %zu\n", d_protection); if (protectionForm()) { if (protectionOption() == 0) { switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back( ((6 * bitrate() / 8) - 3) * 16, P24); d_puncturing_rules.emplace_back(3 * 16, P23); break; case 2: if (bitrate() == 8) { d_puncturing_rules.emplace_back(5 * 16, P13); d_puncturing_rules.emplace_back(1 * 16, P12); } else { d_puncturing_rules.emplace_back( ((2 * bitrate() / 8) - 3) * 16, P14); d_puncturing_rules.emplace_back( ((4 * bitrate() / 8) + 3) * 16, P13); } break; case 3: d_puncturing_rules.emplace_back( ((6 * bitrate() / 8) - 3) * 16, P8); d_puncturing_rules.emplace_back(3 * 16, P7); break; case 4: d_puncturing_rules.emplace_back( ((4 * bitrate() / 8) - 3) * 16, P3); d_puncturing_rules.emplace_back( ((2 * bitrate() / 8) + 3) * 16, P2); break; default: fprintf(stderr, "Protection form(%zu), option(%zu) and level(%zu)\n", protectionForm(), protectionOption(), protectionLevel()); fprintf(stderr, "Subchannel TPL: 0x%x (%u)\n", tpl, tpl); throw std::runtime_error("SubchannelSource::SubchannelSource " "unknown protection level!"); } } else if (protectionOption() == 1) { switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back( ((24 * bitrate() / 32) - 3) * 16, P10); d_puncturing_rules.emplace_back( 3 * 16, P9); break; case 2: d_puncturing_rules.emplace_back( ((24 * bitrate() / 32) - 3) * 16, P6); d_puncturing_rules.emplace_back( 3 * 16, P5); break; case 3: d_puncturing_rules.emplace_back( ((24 * bitrate() / 32) - 3) * 16, P4); d_puncturing_rules.emplace_back( 3 * 16, P3); break; case 4: d_puncturing_rules.emplace_back( ((24 * bitrate() / 32) - 3) * 16, P2); d_puncturing_rules.emplace_back( 3 * 16, P1); break; default: fprintf(stderr, "Protection form(%zu), option(%zu) and level(%zu)\n", protectionForm(), protectionOption(), protectionLevel()); fprintf(stderr, "Subchannel TPL: 0x%x (%u)\n", tpl, tpl); throw std::runtime_error("SubchannelSource::SubchannelSource " "unknown protection level!"); } } else { fprintf(stderr, "Protection form(%zu), option(%zu) and level(%zu)\n", protectionForm(), protectionOption(), protectionLevel()); fprintf(stderr, "Subchannel TPL: 0x%x (%u)\n", tpl, tpl); throw std::runtime_error("SubchannelSource::SubchannelSource " "unknown protection option!"); } } else { bool rule_error = false; switch (bitrate()) { case 32: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(3 * 16, P24); d_puncturing_rules.emplace_back(5 * 16, P17); d_puncturing_rules.emplace_back(13 * 16, P12); d_puncturing_rules.emplace_back(3 * 16, P17); break; case 2: d_puncturing_rules.emplace_back(3 * 16, P22); d_puncturing_rules.emplace_back(4 * 16, P13); d_puncturing_rules.emplace_back(14 * 16, P8 ); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(3 * 16, P15); d_puncturing_rules.emplace_back(4 * 16, P9 ); d_puncturing_rules.emplace_back(14 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P8 ); break; case 4: d_puncturing_rules.emplace_back(3 * 16, P11); d_puncturing_rules.emplace_back(3 * 16, P6 ); d_puncturing_rules.emplace_back(18 * 16, P5 ); break; case 5: d_puncturing_rules.emplace_back(3 * 16, P5 ); d_puncturing_rules.emplace_back(4 * 16, P3 ); d_puncturing_rules.emplace_back(17 * 16, P2 ); break; default: rule_error = true; } break; case 48: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(3 * 16, P24); d_puncturing_rules.emplace_back(5 * 16, P18); d_puncturing_rules.emplace_back(25 * 16, P13); d_puncturing_rules.emplace_back(3 * 16, P18); break; case 2: d_puncturing_rules.emplace_back(3 * 16, P24); d_puncturing_rules.emplace_back(4 * 16, P14); d_puncturing_rules.emplace_back(26 * 16, P8 ); d_puncturing_rules.emplace_back(3 * 16, P15); break; case 3: d_puncturing_rules.emplace_back(3 * 16, P15); d_puncturing_rules.emplace_back(4 * 16, P10); d_puncturing_rules.emplace_back(26 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P9 ); break; case 4: d_puncturing_rules.emplace_back(3 * 16, P9 ); d_puncturing_rules.emplace_back(4 * 16, P6 ); d_puncturing_rules.emplace_back(26 * 16, P4 ); d_puncturing_rules.emplace_back(3 * 16, P6 ); break; case 5: d_puncturing_rules.emplace_back(4 * 16, P5 ); d_puncturing_rules.emplace_back(3 * 16, P4 ); d_puncturing_rules.emplace_back(26 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P3 ); break; default: rule_error = true; } break; case 56: switch (protectionLevel()) { case 2: d_puncturing_rules.emplace_back(6 * 16, P23); d_puncturing_rules.emplace_back(10 * 16, P13); d_puncturing_rules.emplace_back(23 * 16, P8 ); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(6 * 16, P16); d_puncturing_rules.emplace_back(12 * 16, P7 ); d_puncturing_rules.emplace_back(21 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P9 ); break; case 4: d_puncturing_rules.emplace_back(6 * 16, P9 ); d_puncturing_rules.emplace_back(10 * 16, P6 ); d_puncturing_rules.emplace_back(23 * 16, P4 ); d_puncturing_rules.emplace_back(3 * 16, P5 ); break; case 5: d_puncturing_rules.emplace_back(6 * 16, P5 ); d_puncturing_rules.emplace_back(10 * 16, P4 ); d_puncturing_rules.emplace_back(23 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P3 ); break; default: rule_error = true; } break; case 64: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(6 * 16, P24); d_puncturing_rules.emplace_back(11 * 16, P18); d_puncturing_rules.emplace_back(28 * 16, P12); d_puncturing_rules.emplace_back(3 * 16, P18); break; case 2: d_puncturing_rules.emplace_back(6 * 16, P23); d_puncturing_rules.emplace_back(10 * 16, P13); d_puncturing_rules.emplace_back(29 * 16, P8 ); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(6 * 16, P16); d_puncturing_rules.emplace_back(12 * 16, P8 ); d_puncturing_rules.emplace_back(27 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P9 ); break; case 4: d_puncturing_rules.emplace_back(6 * 16, P11); d_puncturing_rules.emplace_back(9 * 16, P6 ); d_puncturing_rules.emplace_back(33 * 16, P5 ); break; case 5: d_puncturing_rules.emplace_back(6 * 16, P5 ); d_puncturing_rules.emplace_back(9 * 16, P3 ); d_puncturing_rules.emplace_back(31 * 16, P2 ); d_puncturing_rules.emplace_back(2 * 16, P3 ); break; default: rule_error = true; } break; case 80: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(6 * 16, P24); d_puncturing_rules.emplace_back(10 * 16, P17); d_puncturing_rules.emplace_back(41 * 16, P12); d_puncturing_rules.emplace_back(3 * 16, P18); break; case 2: d_puncturing_rules.emplace_back(6 * 16, P23); d_puncturing_rules.emplace_back(10 * 16, P13); d_puncturing_rules.emplace_back(41 * 16, P8 ); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(6 * 16, P16); d_puncturing_rules.emplace_back(11 * 16, P8 ); d_puncturing_rules.emplace_back(40 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P7 ); break; case 4: d_puncturing_rules.emplace_back(6 * 16, P11); d_puncturing_rules.emplace_back(10 * 16, P6 ); d_puncturing_rules.emplace_back(41 * 16, P5 ); d_puncturing_rules.emplace_back(3 * 16, P6 ); break; case 5: d_puncturing_rules.emplace_back(6 * 16, P6 ); d_puncturing_rules.emplace_back(10 * 16, P3 ); d_puncturing_rules.emplace_back(41 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P3 ); break; default: rule_error = true; } break; case 96: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(6 * 16, P24); d_puncturing_rules.emplace_back(13 * 16, P18); d_puncturing_rules.emplace_back(50 * 16, P13); d_puncturing_rules.emplace_back(3 * 16, P19); break; case 2: d_puncturing_rules.emplace_back(6 * 16, P22); d_puncturing_rules.emplace_back(10 * 16, P12); d_puncturing_rules.emplace_back(53 * 16, P9 ); d_puncturing_rules.emplace_back(3 * 16, P12); break; case 3: d_puncturing_rules.emplace_back(6 * 16, P16); d_puncturing_rules.emplace_back(12 * 16, P9 ); d_puncturing_rules.emplace_back(51 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P10); break; case 4: d_puncturing_rules.emplace_back(7 * 16, P9 ); d_puncturing_rules.emplace_back(10 * 16, P6 ); d_puncturing_rules.emplace_back(52 * 16, P4 ); d_puncturing_rules.emplace_back(3 * 16, P6 ); break; case 5: d_puncturing_rules.emplace_back(7 * 16, P5 ); d_puncturing_rules.emplace_back(9 * 16, P4 ); d_puncturing_rules.emplace_back(53 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P4 ); break; default: rule_error = true; } break; case 112: switch (protectionLevel()) { case 2: d_puncturing_rules.emplace_back(11 * 16, P23); d_puncturing_rules.emplace_back(21 * 16, P12); d_puncturing_rules.emplace_back(49 * 16, P9 ); d_puncturing_rules.emplace_back(3 * 16, P14); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(23 * 16, P8 ); d_puncturing_rules.emplace_back(47 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P9 ); break; case 4: d_puncturing_rules.emplace_back(11 * 16, P9 ); d_puncturing_rules.emplace_back(21 * 16, P6 ); d_puncturing_rules.emplace_back(49 * 16, P4 ); d_puncturing_rules.emplace_back(3 * 16, P8 ); break; case 5: d_puncturing_rules.emplace_back(14 * 16, P5 ); d_puncturing_rules.emplace_back(17 * 16, P4 ); d_puncturing_rules.emplace_back(50 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P5 ); break; default: rule_error = true; } break; case 128: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(20 * 16, P17); d_puncturing_rules.emplace_back(62 * 16, P13); d_puncturing_rules.emplace_back(3 * 16, P19); break; case 2: d_puncturing_rules.emplace_back(11 * 16, P22); d_puncturing_rules.emplace_back(21 * 16, P12); d_puncturing_rules.emplace_back(61 * 16, P9 ); d_puncturing_rules.emplace_back(3 * 16, P14); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(22 * 16, P9 ); d_puncturing_rules.emplace_back(60 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P10); break; case 4: d_puncturing_rules.emplace_back(11 * 16, P11); d_puncturing_rules.emplace_back(21 * 16, P6 ); d_puncturing_rules.emplace_back(61 * 16, P5 ); d_puncturing_rules.emplace_back(3 * 16, P7 ); break; case 5: d_puncturing_rules.emplace_back(12 * 16, P5 ); d_puncturing_rules.emplace_back(19 * 16, P3 ); d_puncturing_rules.emplace_back(62 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P4 ); break; default: rule_error = true; } break; case 160: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(22 * 16, P18); d_puncturing_rules.emplace_back(84 * 16, P12); d_puncturing_rules.emplace_back(3 * 16, P19); break; case 2: d_puncturing_rules.emplace_back(11 * 16, P22); d_puncturing_rules.emplace_back(21 * 16, P11); d_puncturing_rules.emplace_back(85 * 16, P9 ); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(24 * 16, P8 ); d_puncturing_rules.emplace_back(82 * 16, P6 ); d_puncturing_rules.emplace_back(3 * 16, P11); break; case 4: d_puncturing_rules.emplace_back(11 * 16, P11); d_puncturing_rules.emplace_back(23 * 16, P6 ); d_puncturing_rules.emplace_back(83 * 16, P5 ); d_puncturing_rules.emplace_back(3 * 16, P9 ); break; case 5: d_puncturing_rules.emplace_back(11 * 16, P5 ); d_puncturing_rules.emplace_back(19 * 16, P4 ); d_puncturing_rules.emplace_back(87 * 16, P2 ); d_puncturing_rules.emplace_back(3 * 16, P4 ); break; default: rule_error = true; } break; case 192: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(21 * 16, P20); d_puncturing_rules.emplace_back(109 * 16, P13); d_puncturing_rules.emplace_back(3 * 16, P24); break; case 2: d_puncturing_rules.emplace_back(11 * 16, P22); d_puncturing_rules.emplace_back(20 * 16, P13); d_puncturing_rules.emplace_back(110 * 16, P9); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(24 * 16, P10); d_puncturing_rules.emplace_back(106 * 16, P6); d_puncturing_rules.emplace_back(3 * 16, P11); break; case 4: d_puncturing_rules.emplace_back(11 * 16, P10); d_puncturing_rules.emplace_back(22 * 16, P6); d_puncturing_rules.emplace_back(108 * 16, P4); d_puncturing_rules.emplace_back(3 * 16, P9); break; case 5: d_puncturing_rules.emplace_back(11 * 16, P6); d_puncturing_rules.emplace_back(20 * 16, P4); d_puncturing_rules.emplace_back(110 * 16, P2); d_puncturing_rules.emplace_back(3 * 16, P5); break; default: rule_error = true; } break; case 224: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(24 * 16, P20); d_puncturing_rules.emplace_back(130 * 16, P12); d_puncturing_rules.emplace_back(3 * 16, P20); break; case 2: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(22 * 16, P16); d_puncturing_rules.emplace_back(132 * 16, P10); d_puncturing_rules.emplace_back(3 * 16, P15); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(20 * 16, P10); d_puncturing_rules.emplace_back(134 * 16, P7); d_puncturing_rules.emplace_back(3 * 16, P9); break; case 4: d_puncturing_rules.emplace_back(12 * 16, P12); d_puncturing_rules.emplace_back(26 * 16, P8); d_puncturing_rules.emplace_back(127 * 16, P4); d_puncturing_rules.emplace_back(3 * 16, P11); break; case 5: d_puncturing_rules.emplace_back(12 * 16, P8); d_puncturing_rules.emplace_back(22 * 16, P6); d_puncturing_rules.emplace_back(131 * 16, P2); d_puncturing_rules.emplace_back(3 * 16, P6); break; default: rule_error = true; } break; case 256: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(26 * 16, P19); d_puncturing_rules.emplace_back(152 * 16, P14); d_puncturing_rules.emplace_back(3 * 16, P18); break; case 2: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(22 * 16, P14); d_puncturing_rules.emplace_back(156 * 16, P10); d_puncturing_rules.emplace_back(3 * 16, P13); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(27 * 16, P10); d_puncturing_rules.emplace_back(151 * 16, P7); d_puncturing_rules.emplace_back(3 * 16, P10); break; case 4: d_puncturing_rules.emplace_back(11 * 16, P12); d_puncturing_rules.emplace_back(24 * 16, P9); d_puncturing_rules.emplace_back(154 * 16, P5); d_puncturing_rules.emplace_back(3 * 16, P10); break; case 5: d_puncturing_rules.emplace_back(11 * 16, P6); d_puncturing_rules.emplace_back(24 * 16, P5); d_puncturing_rules.emplace_back(154 * 16, P2); d_puncturing_rules.emplace_back(3 * 16, P5); break; default: rule_error = true; } break; case 320: switch (protectionLevel()) { case 2: d_puncturing_rules.emplace_back(11 * 16, P24); d_puncturing_rules.emplace_back(26 * 16, P17); d_puncturing_rules.emplace_back(200 * 16, P9 ); d_puncturing_rules.emplace_back(3 * 16, P17); break; case 4: d_puncturing_rules.emplace_back(11 * 16, P13); d_puncturing_rules.emplace_back(25 * 16, P9); d_puncturing_rules.emplace_back(201 * 16, P5); d_puncturing_rules.emplace_back(3 * 16, P10); break; case 5: d_puncturing_rules.emplace_back(11 * 16, P8); d_puncturing_rules.emplace_back(26 * 16, P5); d_puncturing_rules.emplace_back(200 * 16, P2); d_puncturing_rules.emplace_back(3 * 16, P6); break; default: rule_error = true; } break; case 384: switch (protectionLevel()) { case 1: d_puncturing_rules.emplace_back(12 * 16, P24); d_puncturing_rules.emplace_back(28 * 16, P20); d_puncturing_rules.emplace_back(245 * 16, P14); d_puncturing_rules.emplace_back(3 * 16, P23); break; case 3: d_puncturing_rules.emplace_back(11 * 16, P16); d_puncturing_rules.emplace_back(24 * 16, P9); d_puncturing_rules.emplace_back(250 * 16, P7); d_puncturing_rules.emplace_back(3 * 16, P10); break; case 5: d_puncturing_rules.emplace_back(11 * 16, P8); d_puncturing_rules.emplace_back(27 * 16, P6); d_puncturing_rules.emplace_back(247 * 16, P2); d_puncturing_rules.emplace_back(3 * 16, P7); break; default: rule_error = true; } break; default: rule_error = true; } if (rule_error) { etiLog.log(error, " Protection: UEP-%zu @ %zukb/s\n", protectionLevel(), bitrate()); throw std::runtime_error("ubchannelSource " "UEP puncturing rules do not exist!"); } } } size_t SubchannelSource::startAddress() const { return d_start_address; } size_t SubchannelSource::framesize() const { return d_framesize; } size_t SubchannelSource::framesizeCu() const { size_t framesizeCu = 0; if (protectionForm()) { // Long form if ((d_protection >> 2) & 0x07) { // Option switch (d_protection & 0x03) { case 0: framesizeCu = (bitrate() / 32) * 27; break; case 1: framesizeCu = (bitrate() / 32) * 21; break; case 2: framesizeCu = (bitrate() / 32) * 18; break; case 3: framesizeCu = (bitrate() / 32) * 15; break; default: framesizeCu = 0xffff; break; } } else { switch (d_protection & 0x03) { case 0: framesizeCu = (bitrate() / 8) * 12; break; case 1: framesizeCu = (bitrate() / 8) * 8; break; case 2: framesizeCu = (bitrate() / 8) * 6; break; case 3: framesizeCu = (bitrate() / 8) * 4; break; default: framesizeCu = 0xffff; break; } } } else { // Short form switch (bitrate()) { case 32: switch (protectionLevel()) { case 1: framesizeCu = 35; break; case 2: framesizeCu = 29; break; case 3: framesizeCu = 24; break; case 4: framesizeCu = 21; break; case 5: framesizeCu = 16; break; default: framesizeCu = 0xffff; break; } break; case 48: switch (protectionLevel()) { case 1: framesizeCu = 52; break; case 2: framesizeCu = 42; break; case 3: framesizeCu = 35; break; case 4: framesizeCu = 29; break; case 5: framesizeCu = 24; break; default: framesizeCu = 0xffff; break; } break; case 56: switch (protectionLevel()) { case 2: framesizeCu = 52; break; case 3: framesizeCu = 42; break; case 4: framesizeCu = 35; break; case 5: framesizeCu = 29; break; default: framesizeCu = 0xffff; break; } break; case 64: switch (protectionLevel()) { case 1: framesizeCu = 70; break; case 2: framesizeCu = 58; break; case 3: framesizeCu = 48; break; case 4: framesizeCu = 42; break; case 5: framesizeCu = 32; break; default: framesizeCu = 0xffff; break; } break; case 80: switch (protectionLevel()) { case 1: framesizeCu = 84; break; case 2: framesizeCu = 70; break; case 3: framesizeCu = 58; break; case 4: framesizeCu = 52; break; case 5: framesizeCu = 40; break; default: framesizeCu = 0xffff; break; } break; case 96: switch (protectionLevel()) { case 1: framesizeCu = 104; break; case 2: framesizeCu = 84; break; case 3: framesizeCu = 70; break; case 4: framesizeCu = 58; break; case 5: framesizeCu = 48; break; default: framesizeCu = 0xffff; break; } break; case 112: switch (protectionLevel()) { case 2: framesizeCu = 104; break; case 3: framesizeCu = 84; break; case 4: framesizeCu = 70; break; case 5: framesizeCu = 58; break; default: framesizeCu = 0xffff; break; } break; case 128: switch (protectionLevel()) { case 1: framesizeCu = 140; break; case 2: framesizeCu = 116; break; case 3: framesizeCu = 96; break; case 4: framesizeCu = 84; break; case 5: framesizeCu = 64; break; default: framesizeCu = 0xffff; break; } break; case 160: switch (protectionLevel()) { case 1: framesizeCu = 168; break; case 2: framesizeCu = 140; break; case 3: framesizeCu = 116; break; case 4: framesizeCu = 104; break; case 5: framesizeCu = 80; break; default: framesizeCu = 0xffff; break; } break; case 192: switch (protectionLevel()) { case 1: framesizeCu = 208; break; case 2: framesizeCu = 168; break; case 3: framesizeCu = 140; break; case 4: framesizeCu = 116; break; case 5: framesizeCu = 96; break; default: framesizeCu = 0xffff; break; } break; case 224: switch (protectionLevel()) { case 1: framesizeCu = 232; break; case 2: framesizeCu = 208; break; case 3: framesizeCu = 168; break; case 4: framesizeCu = 140; break; case 5: framesizeCu = 116; break; default: framesizeCu = 0xffff; break; } break; case 256: switch (protectionLevel()) { case 1: framesizeCu = 280; break; case 2: framesizeCu = 232; break; case 3: framesizeCu = 192; break; case 4: framesizeCu = 168; break; case 5: framesizeCu = 128; break; default: framesizeCu = 0xffff; break; } break; case 320: switch (protectionLevel()) { case 2: framesizeCu = 280; break; case 4: framesizeCu = 208; break; case 5: framesizeCu = 160; break; default: framesizeCu = 0xffff; break; } break; case 384: switch (protectionLevel()) { case 1: framesizeCu = 416; break; case 3: framesizeCu = 280; break; case 5: framesizeCu = 192; break; default: framesizeCu = 0xffff; break; } break; default: framesizeCu = 0xffff; break; } } if (framesizeCu == 0) { fprintf(stderr, " Protection %zu @ %zu kb/s\n", protectionLevel(), bitrate()); throw std::runtime_error("SubchannelSource::framesizeCu protection " "not yet coded!"); } if (framesizeCu == 0xffff) { fprintf(stderr, " Protection %zu @ %zu kb/s\n", protectionLevel(), bitrate()); throw std::runtime_error("SubchannelSource::framesizeCu invalid " "protection!"); } return framesizeCu; } size_t SubchannelSource::bitrate() const { return d_framesize / 3; } size_t SubchannelSource::protection() const { return d_protection; } size_t SubchannelSource::protectionForm() const { return (d_protection >> 5) & 1; } size_t SubchannelSource::protectionLevel() const { if (protectionForm()) { // Long form return (d_protection & 0x3) + 1; } // Short form return (d_protection & 0x7) + 1; } size_t SubchannelSource::protectionOption() const { if (protectionForm()) { // Long form return (d_protection >> 2) & 0x7; } // Short form return 0; } void SubchannelSource::loadSubchannelData(Buffer&& data) { d_buffer = std::move(data); } int SubchannelSource::process(Buffer* outputData) { PDEBUG("SubchannelSource::process(outputData: %p, outputSize: %zu)\n", outputData, outputData->getLength()); if (d_buffer.getLength() != d_framesize) { throw std::runtime_error( "ERROR: Subchannel::process: d_buffer != d_framesize: " + std::to_string(d_buffer.getLength()) + " != " + std::to_string(d_framesize)); } *outputData = d_buffer; return outputData->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/SubchannelSource.h000066400000000000000000000036141475762153200236300ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "PuncturingRule.h" #include "Eti.h" #include "ModPlugin.h" #include class SubchannelSource : public ModInput { public: SubchannelSource( uint16_t sad, uint16_t stl, uint8_t tpl ); size_t startAddress() const; size_t framesize() const; size_t framesizeCu() const; size_t bitrate() const; size_t protection() const; /* Return 1 if long form is used, 0 otherwise */ size_t protectionForm() const; size_t protectionLevel() const; size_t protectionOption() const; const std::vector& get_rules() const; void loadSubchannelData(Buffer&& data); int process(Buffer* outputData); const char* name() { return "SubchannelSource"; } private: size_t d_start_address; size_t d_framesize; size_t d_protection; Buffer d_buffer; std::vector d_puncturing_rules; }; Opendigitalradio-ODR-DabMod-f7eedef/src/TII.cpp000066400000000000000000000273041475762153200213470ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "TII.h" #include "PcDebug.h" #include #include /* TII pattern for TM I, II, IV */ const int pattern_tm1_2_4[][8] = { // {{{ {0,0,0,0,1,1,1,1}, {0,0,0,1,0,1,1,1}, {0,0,0,1,1,0,1,1}, {0,0,0,1,1,1,0,1}, {0,0,0,1,1,1,1,0}, {0,0,1,0,0,1,1,1}, {0,0,1,0,1,0,1,1}, {0,0,1,0,1,1,0,1}, {0,0,1,0,1,1,1,0}, {0,0,1,1,0,0,1,1}, {0,0,1,1,0,1,0,1}, {0,0,1,1,0,1,1,0}, {0,0,1,1,1,0,0,1}, {0,0,1,1,1,0,1,0}, {0,0,1,1,1,1,0,0}, {0,1,0,0,0,1,1,1}, {0,1,0,0,1,0,1,1}, {0,1,0,0,1,1,0,1}, {0,1,0,0,1,1,1,0}, {0,1,0,1,0,0,1,1}, {0,1,0,1,0,1,0,1}, {0,1,0,1,0,1,1,0}, {0,1,0,1,1,0,0,1}, {0,1,0,1,1,0,1,0}, {0,1,0,1,1,1,0,0}, {0,1,1,0,0,0,1,1}, {0,1,1,0,0,1,0,1}, {0,1,1,0,0,1,1,0}, {0,1,1,0,1,0,0,1}, {0,1,1,0,1,0,1,0}, {0,1,1,0,1,1,0,0}, {0,1,1,1,0,0,0,1}, {0,1,1,1,0,0,1,0}, {0,1,1,1,0,1,0,0}, {0,1,1,1,1,0,0,0}, {1,0,0,0,0,1,1,1}, {1,0,0,0,1,0,1,1}, {1,0,0,0,1,1,0,1}, {1,0,0,0,1,1,1,0}, {1,0,0,1,0,0,1,1}, {1,0,0,1,0,1,0,1}, {1,0,0,1,0,1,1,0}, {1,0,0,1,1,0,0,1}, {1,0,0,1,1,0,1,0}, {1,0,0,1,1,1,0,0}, {1,0,1,0,0,0,1,1}, {1,0,1,0,0,1,0,1}, {1,0,1,0,0,1,1,0}, {1,0,1,0,1,0,0,1}, {1,0,1,0,1,0,1,0}, {1,0,1,0,1,1,0,0}, {1,0,1,1,0,0,0,1}, {1,0,1,1,0,0,1,0}, {1,0,1,1,0,1,0,0}, {1,0,1,1,1,0,0,0}, {1,1,0,0,0,0,1,1}, {1,1,0,0,0,1,0,1}, {1,1,0,0,0,1,1,0}, {1,1,0,0,1,0,0,1}, {1,1,0,0,1,0,1,0}, {1,1,0,0,1,1,0,0}, {1,1,0,1,0,0,0,1}, {1,1,0,1,0,0,1,0}, {1,1,0,1,0,1,0,0}, {1,1,0,1,1,0,0,0}, {1,1,1,0,0,0,0,1}, {1,1,1,0,0,0,1,0}, {1,1,1,0,0,1,0,0}, {1,1,1,0,1,0,0,0}, {1,1,1,1,0,0,0,0} }; // }}} TII::TII(unsigned int dabmode, tii_config_t& tii_config, bool fixedPoint) : ModCodec(), RemoteControllable("tii"), m_dabmode(dabmode), m_conf(tii_config), m_fixedPoint(fixedPoint) { PDEBUG("TII::TII(%u) @ %p\n", dabmode, this); RC_ADD_PARAMETER(enable, "enable TII [0-1]"); RC_ADD_PARAMETER(comb, "TII comb number [0-23]"); RC_ADD_PARAMETER(pattern, "TII pattern number [0-69]"); RC_ADD_PARAMETER(old_variant, "select old TII variant for old (buggy) receivers [0-1]"); switch (m_dabmode) { case 1: m_carriers = 1536; if (not(0 <= m_conf.pattern and m_conf.pattern <= 69) ) { throw TIIError("TII::TII pattern not valid!"); } break; case 2: m_carriers = 384; if (not(0 <= m_conf.pattern and m_conf.pattern <= 69) ) { throw TIIError("TII::TII pattern not valid!"); } break; /* unsupported case 3: m_carriers = 192; break; case 4: d_dabmode = 0; case 0: */ default: std::stringstream ss_exception; ss_exception << "TII::TII DAB mode " << m_dabmode << " not valid!"; throw TIIError(ss_exception.str()); } if (not(0 <= m_conf.comb and m_conf.comb <= 23) ) { throw TIIError("TII::TII comb not valid!"); } m_Acp.clear(); m_Acp.resize(m_carriers); prepare_pattern(); } const char* TII::name() { // Calculate name on demand because comb and pattern are // modifiable through RC std::stringstream ss; ss << "TII(c:" << m_conf.comb << " p:" << m_conf.pattern << " vrnt:" << (m_conf.old_variant ? "old" : "new") << ")"; m_name = ss.str(); return m_name.c_str(); } template void do_process(size_t carriers, bool old_variant, const std::vector& Acp, Buffer* dataIn, Buffer* dataOut) { const T* in = reinterpret_cast(dataIn->getData()); T* out = reinterpret_cast(dataOut->getData()); /* Normalise the TII carrier power according to ETSI TR 101 496-3 * Clause 5.4.2.2 Paragraph 7: * * > The ratio of carriers in a TII symbol to a normal DAB symbol * > is 1:48 for all Modes, so that the signal power in a TII symbol is * > 16 dB below the signal power of the other symbols. * * This is because we only enable 32 out of 1536 carriers, not because * every carrier is lower power. */ for (size_t i = 0; i < Acp.size(); i++) { /* See header file for an explanation of the old variant. * * A_{c,p}(k) and A_{c,p}(k-1) are never both simultaneously true, * so instead of doing the sum inside z_{m,0,k}, we could do * * if (m_Acp[i]) out[i] = in[i]; * if (m_Acp[i-1]) out[i] = in[i-1] * * (Considering only the new variant) * * To avoid messing with indices, we substitute j = i-1 * * if (m_Acp[i]) out[i] = in[i]; * if (m_Acp[j]) out[j+1] = in[j] * * and fuse the two conditionals together: */ if (Acp[i]) { out[i] = in[i]; out[i+1] = (old_variant ? in[i+1] : in[i]); } } } int TII::process(Buffer* dataIn, Buffer* dataOut) { const size_t sizeof_samples = m_fixedPoint ? sizeof(complexfix) : sizeof(complexf); PDEBUG("TII::process(dataOut: %p)\n", dataOut); if ( (dataIn == NULL) or (dataIn->getLength() != m_carriers * sizeof_samples)) { throw TIIError("TII::process input size not valid!"); } dataOut->setLength(m_carriers * sizeof_samples); memset(dataOut->getData(), 0, dataOut->getLength()); if (m_conf.enable and m_insert) { std::lock_guard lock(m_enabled_carriers_mutex); if (m_fixedPoint) { do_process( m_carriers, m_conf.old_variant, m_Acp, dataIn, dataOut); } else { do_process( m_carriers, m_conf.old_variant, m_Acp, dataIn, dataOut); } } // Align with frames containing the right data (when FC.fp is first quarter) m_insert = not m_insert; return 1; } void TII::enable_carrier(int k) { /* The OFDMGenerator shifts all positive frequencies by one, * i.e. index 0 is not the DC component, it's the first positive * frequency. Because this is different from the definition of k * from the spec, we need to compensate this here. * * Positive frequencies are k > 0 */ int ix = m_carriers/2 + k + (k>=0 ? -1 : 0); if (ix < 0 or ix+1 >= (ssize_t)m_Acp.size()) { throw TIIError("TII::enable_carrier invalid k!"); } m_Acp[ix] = true; } void TII::prepare_pattern() { int comb = m_conf.comb; // Convert from unsigned to signed std::lock_guard lock(m_enabled_carriers_mutex); // Clear previous pattern for (size_t i = 0; i < m_Acp.size(); i++) { m_Acp[i] = false; } // This could be written more efficiently, but since it is // not performance-critial, it makes sense to write it // in the same way as the specification in // ETSI EN 300 401 Clause 14.8 if (m_dabmode == 1) { for (int k = -768; k < -384; k++) { for (int b = 0; b < 8; b++) { if ( k == -768 + 2 * comb + 48 * b and pattern_tm1_2_4[m_conf.pattern][b]) { enable_carrier(k); } } } for (int k = -384; k < -0; k++) { for (int b = 0; b < 8; b++) { if ( k == -384 + 2 * comb + 48 * b and pattern_tm1_2_4[m_conf.pattern][b]) { enable_carrier(k); } } } for (int k = 1; k <= 384; k++) { for (int b = 0; b < 8; b++) { if ( k == 1 + 2 * comb + 48 * b and pattern_tm1_2_4[m_conf.pattern][b]) { enable_carrier(k); } } } for (int k = 385; k <= 768; k++) { for (int b = 0; b < 8; b++) { if ( k == 385 + 2 * comb + 48 * b and pattern_tm1_2_4[m_conf.pattern][b]) { enable_carrier(k); } } } } else if (m_dabmode == 2) { for (int k = -192; k <= 192; k++) { for (int b = 0; b < 4; b++) { if ( k == -192 + 2 * comb + 48 * b and pattern_tm1_2_4[m_conf.pattern][b]) { enable_carrier(k); } } for (int b = 4; b < 8; b++) { if ( k == -191 + 2 * comb + 48 * b and pattern_tm1_2_4[m_conf.pattern][b]) { enable_carrier(k); } } } } else { throw TIIError("TII::TII DAB mode not valid!"); } } void TII::set_parameter(const std::string& parameter, const std::string& value) { using namespace std; stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "enable") { ss >> m_conf.enable; } else if (parameter == "pattern") { int new_pattern; ss >> new_pattern; if ( (m_dabmode == 1 or m_dabmode == 2) and not(0 <= new_pattern and new_pattern <= 69) ) { throw TIIError("TII pattern not valid!"); } m_conf.pattern = new_pattern; prepare_pattern(); } else if (parameter == "comb") { int new_comb; ss >> new_comb; if (not(0 <= new_comb and new_comb <= 23) ) { throw TIIError("TII comb not valid!"); } m_conf.comb = new_comb; prepare_pattern(); } else if (parameter == "old_variant") { ss >> m_conf.old_variant; } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const std::string TII::get_parameter(const std::string& parameter) const { using namespace std; stringstream ss; if (parameter == "enable") { ss << (m_conf.enable ? 1 : 0); } else if (parameter == "pattern") { ss << m_conf.pattern; } else if (parameter == "comb") { ss << m_conf.comb; } else if (parameter == "old_variant") { ss << (m_conf.old_variant ? 1 : 0); } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t TII::get_all_values() const { json::map_t map; map["enable"].v = m_conf.enable; map["pattern"].v = m_conf.pattern; map["comb"].v = m_conf.comb; map["old_variant"].v = m_conf.old_variant; return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/TII.h000066400000000000000000000075561475762153200210230ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org TII generation according to ETSI EN 300 401 Clause 14.8 */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include "RemoteControl.h" #include #include #include struct tii_config_t { bool enable = false; int comb = 0; int pattern = 0; /* EN 300 401 clause 14.8 describes how to generate the TII signal, and * defines z_{m,0,k}: * * z_{m,0,k} = A_{c,p}(k) e^{j \psi_k} + A_{c,p}(k-1) e^{j \psi{k-1}} * * What was implemented in the old variant was * * z_{m,0,k} = A_{c,p}(k) e^{j \psi_k} + A_{c,p}(k-1) e^{j \psi{k}} * ^ * | * Wrong phase on the second * carrier of the pair. * * Correctly implemented decoders ought to be able to decode such a TII, * but will not be able to correctly estimate the delay of different * transmitters. * * The option 'old_variant' allows the user to choose between this * old incorrect implementation and the new conforming one. */ bool old_variant = false; }; class TIIError : public std::runtime_error { public: TIIError(const char* msg) : std::runtime_error(msg) {} TIIError(const std::string& msg) : std::runtime_error(msg) {} }; class TII : public ModCodec, public RemoteControllable { public: TII(unsigned int dabmode, tii_config_t& tii_config, bool fixedPoint); virtual ~TII() {} int process(Buffer* dataIn, Buffer* dataOut) override; const char* name() override; /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; protected: // Fill m_Acp with the correct carriers for the pattern/comb // combination void prepare_pattern(void); // prerequisites: calling thread must hold m_Acp mutex void enable_carrier(int k); // Configuration settings unsigned int m_dabmode; // Remote-controllable settings tii_config_t& m_conf; bool m_fixedPoint = false; // Internal flag when to insert TII bool m_insert = true; size_t m_carriers = 0; std::string m_name; // m_Acp is read by modulator thread, and written // to by RC thread. mutable std::mutex m_enabled_carriers_mutex; // m_Acp corresponds to the A_{c,p}(k) function from the spec, except // that the leftmost carrier is at index 0, and not at -m_carriers/2 like // in the spec. std::vector m_Acp; }; Opendigitalradio-ODR-DabMod-f7eedef/src/TimeInterleaver.cpp000066400000000000000000000061561475762153200240230ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "TimeInterleaver.h" #include "PcDebug.h" #include #include #include TimeInterleaver::TimeInterleaver(size_t framesize) : ModCodec(), d_framesize(framesize) { PDEBUG("TimeInterleaver::TimeInterleaver(%zu) @ %p\n", framesize, this); if (framesize & 1) { throw std::invalid_argument("framesize must be 16 bits multiple"); } for (int i = 0; i < 16; ++i) { d_history.emplace_back(framesize, 0); } } TimeInterleaver::~TimeInterleaver() { PDEBUG("TimeInterleaver::~TimeInterleaver() @ %p\n", this); } int TimeInterleaver::process(Buffer* const dataIn, Buffer* dataOut) { PDEBUG("TimeInterleaver::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); if (dataIn->getLength() != d_framesize) { throw std::invalid_argument("Interleaver buffer input size " + std::to_string(dataIn->getLength()) + " expected " + std::to_string(d_framesize)); } dataOut->setLength(dataIn->getLength()); const unsigned char* in = reinterpret_cast(dataIn->getData()); unsigned char* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < dataOut->getLength();) { d_history.push_front(move(d_history.back())); d_history.pop_back(); for (uint_fast16_t j = 0; j < d_framesize;) { d_history[0][j] = in[i]; out[i] = d_history[0] [j] & 0x80; out[i] |= d_history[8] [j] & 0x40; out[i] |= d_history[4] [j] & 0x20; out[i] |= d_history[12][j] & 0x10; out[i] |= d_history[2] [j] & 0x08; out[i] |= d_history[10][j] & 0x04; out[i] |= d_history[6] [j] & 0x02; out[i] |= d_history[14][j] & 0x01; ++i; ++j; d_history[0][j] = in[i]; out[i] = d_history[1] [j] & 0x80; out[i] |= d_history[9] [j] & 0x40; out[i] |= d_history[5] [j] & 0x20; out[i] |= d_history[13][j] & 0x10; out[i] |= d_history[3] [j] & 0x08; out[i] |= d_history[11][j] & 0x04; out[i] |= d_history[7] [j] & 0x02; out[i] |= d_history[15][j] & 0x01; ++i; ++j; } } return dataOut->getLength(); } Opendigitalradio-ODR-DabMod-f7eedef/src/TimeInterleaver.h000066400000000000000000000024731475762153200234660ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include #include #include #include class TimeInterleaver : public ModCodec { private: protected: size_t d_framesize; std::deque > d_history; public: TimeInterleaver(size_t framesize); virtual ~TimeInterleaver(); int process(Buffer* const dataIn, Buffer* dataOut); const char* name() { return "TimeInterleaver"; } }; Opendigitalradio-ODR-DabMod-f7eedef/src/TimestampDecoder.cpp000066400000000000000000000222241475762153200241470ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include #include #include #include #include "PcDebug.h" #include "TimestampDecoder.h" #include "Log.h" #include "Eti.h" //#define MDEBUG(fmt, args...) fprintf (LOG, "*****" fmt , ## args) #define MDEBUG(fmt, args...) PDEBUG(fmt, ## args) double frame_timestamp::offset_to_system_time() const { if (not timestamp_valid) { throw new std::runtime_error("Cannot calculate offset for invalid timestamp"); } struct timespec t; if (clock_gettime(CLOCK_REALTIME, &t) != 0) { throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); } return get_real_secs() - (double)t.tv_sec - (t.tv_nsec / 1000000000.0); } std::string frame_timestamp::to_string() const { time_t s = timestamp_sec; std::stringstream ss; char timestr[100]; if (std::strftime(timestr, sizeof(timestr), "%Y-%m-%dZ%H:%M:%S", std::gmtime(&s))) { ss << timestr << " + " << ((double)timestamp_pps / 16384000.0); } return ss.str(); } frame_timestamp& frame_timestamp::operator+=(const double& diff) { double offset_pps, offset_secs; offset_pps = modf(diff, &offset_secs); this->timestamp_sec += lrint(offset_secs); int64_t new_pps = (int64_t)this->timestamp_pps + llrint(offset_pps * 16384000.0); while (new_pps < 0) { this->timestamp_sec -= 1; new_pps += 16384000; } while (new_pps >= 16384000) { this->timestamp_sec += 1; new_pps -= 16384000; } this->timestamp_pps = new_pps; return *this; } TimestampDecoder::TimestampDecoder(double& offset_s) : RemoteControllable("tist"), timestamp_offset(offset_s) { // Properly initialise temp_time memset(&temp_time, 0, sizeof(temp_time)); const time_t timep = 0; gmtime_r(&timep, &temp_time); RC_ADD_PARAMETER(offset, "TIST offset [s]"); RC_ADD_PARAMETER(timestamp, "FCT and timestamp [s]"); RC_ADD_PARAMETER(timestamp0, "Timestamp of frame with FCT=0 [s]"); etiLog.level(info) << "Setting up timestamp decoder with " << timestamp_offset << " offset"; } frame_timestamp TimestampDecoder::getTimestamp() { frame_timestamp ts; ts.timestamp_valid = full_timestamp_received; ts.timestamp_sec = time_secs; ts.timestamp_pps = time_pps; ts.fct = latestFCT; ts.fp = latestFP; ts.timestamp_offset = timestamp_offset; ts.offset_changed = offset_changed; offset_changed = false; ts += timestamp_offset; return ts; } void TimestampDecoder::pushMNSCData(uint8_t framephase, uint16_t mnsc) { struct eti_MNSC_TIME_0 *mnsc0; struct eti_MNSC_TIME_1 *mnsc1; struct eti_MNSC_TIME_2 *mnsc2; struct eti_MNSC_TIME_3 *mnsc3; switch (framephase) { case 0: mnsc0 = (struct eti_MNSC_TIME_0*)&mnsc; enableDecode = (mnsc0->type == 0) && (mnsc0->identifier == 0); { const time_t timep = 0; gmtime_r(&timep, &temp_time); } break; case 1: mnsc1 = (struct eti_MNSC_TIME_1*)&mnsc; temp_time.tm_sec = mnsc1->second_tens * 10 + mnsc1->second_unit; temp_time.tm_min = mnsc1->minute_tens * 10 + mnsc1->minute_unit; if (!mnsc1->sync_to_frame) { enableDecode = false; PDEBUG("TimestampDecoder: " "MNSC time info is not synchronised to frame\n"); } break; case 2: mnsc2 = (struct eti_MNSC_TIME_2*)&mnsc; temp_time.tm_hour = mnsc2->hour_tens * 10 + mnsc2->hour_unit; temp_time.tm_mday = mnsc2->day_tens * 10 + mnsc2->day_unit; break; case 3: mnsc3 = (struct eti_MNSC_TIME_3*)&mnsc; temp_time.tm_mon = (mnsc3->month_tens * 10 + mnsc3->month_unit) - 1; temp_time.tm_year = (mnsc3->year_tens * 10 + mnsc3->year_unit) + 100; if (enableDecode) { updateTimestampSeconds(mktime(&temp_time)); } break; } MDEBUG("TimestampDecoder::pushMNSCData(%d, 0x%x)\n", framephase, mnsc); MDEBUG(" -> %s\n", asctime(&temp_time)); MDEBUG(" -> %zu\n", mktime(&temp_time)); } void TimestampDecoder::updateTimestampSeconds(uint32_t secs) { if (inhibit_second_update > 0) { MDEBUG("TimestampDecoder::updateTimestampSeconds(%d) inhibit\n", secs); inhibit_second_update--; } else { MDEBUG("TimestampDecoder::updateTimestampSeconds(%d) apply\n", secs); time_secs = secs; full_timestamp_received = true; } } void TimestampDecoder::updateTimestampPPS(uint32_t pps) { MDEBUG("TimestampDecoder::updateTimestampPPS(%f)\n", (double)pps / 16384000.0); if (time_pps > pps) { // Second boundary crossed MDEBUG("TimestampDecoder::updateTimestampPPS crossed second\n"); // The second for the next eight frames will not // be defined by the MNSC inhibit_second_update = 2; time_secs += 1; } time_pps = pps; } void TimestampDecoder::updateTimestampEti( uint8_t framephase, uint16_t mnsc, uint32_t pps, // In units of 1/16384000 s int32_t fct) { updateTimestampPPS(pps); pushMNSCData(framephase, mnsc); latestFCT = fct; latestFP = framephase; if (full_timestamp_received and fct == 0) { time_secs_of_frame0 = time_secs; time_pps_of_frame0 = time_pps; } } void TimestampDecoder::updateTimestampEdi( uint32_t seconds_utc, uint32_t pps, // In units of 1/16384000 s int32_t fct, uint8_t framephase) { time_secs = seconds_utc; time_pps = pps; latestFCT = fct; latestFP = framephase; full_timestamp_received = true; if (fct == 0) { time_secs_of_frame0 = time_secs; time_pps_of_frame0 = time_pps; } } void TimestampDecoder::set_parameter( const std::string& parameter, const std::string& value) { using namespace std; stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "offset") { ss >> timestamp_offset; offset_changed = true; } else if (parameter == "timestamp") { throw ParameterError("timestamp is read-only"); } else if (parameter == "timestamp0") { throw ParameterError("timestamp0 is read-only"); } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const std::string TimestampDecoder::get_parameter( const std::string& parameter) const { using namespace std; stringstream ss; if (parameter == "offset") { ss << timestamp_offset; } else if (parameter == "timestamp") { if (full_timestamp_received) { ss.setf(std::ios_base::fixed, std::ios_base::floatfield); ss << time_secs + ((double)time_pps / 16384000.0) << " for frame FCT " << latestFCT; } else { throw ParameterError("Not available yet"); } } else if (parameter == "timestamp0") { if (full_timestamp_received) { ss.setf(std::ios_base::fixed, std::ios_base::floatfield); ss << time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0) << " for frame FCT 0"; } else { throw ParameterError("Not available yet"); } } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t TimestampDecoder::get_all_values() const { json::map_t map; map["offset"].v = timestamp_offset; if (full_timestamp_received) { map["timestamp"].v = time_secs + ((double)time_pps / 16384000.0); } else { map["timestamp"].v = std::nullopt; } if (full_timestamp_received) { map["timestamp0"].v = time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0); } else { map["timestamp0"].v = std::nullopt; } return map; } Opendigitalradio-ODR-DabMod-f7eedef/src/TimestampDecoder.h000066400000000000000000000122311475762153200236110ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #include #include #include #include #include #include "RemoteControl.h" struct frame_timestamp { // Which frame count does this timestamp apply to int32_t fct; uint8_t fp; // Frame Phase uint32_t timestamp_sec; // seconds in unix epoch uint32_t timestamp_pps; // In units of 1/16384000 s bool timestamp_valid = false; double timestamp_offset = 0.0; // copy of the configured modulator offset bool offset_changed = false; frame_timestamp& operator+=(const double& diff); const frame_timestamp operator+(const double diff) const { frame_timestamp ts = *this; ts += diff; return ts; } double pps_offset() const { return timestamp_pps / 16384000.0; } double offset_to_system_time() const; double get_real_secs() const { double t = timestamp_sec; t += pps_offset(); return t; } long long int get_ns() const { long long int ns = timestamp_sec * 1000000000ull; ns += llrint((double)timestamp_pps / 0.016384); return ns; } void set_ns(long long int time_ns) { timestamp_sec = time_ns / 1000000000ull; const long long int subsecond = time_ns % 1000000000ull; timestamp_pps = lrint(subsecond * 16384000.0); } std::string to_string() const; void print(const char* t) const { etiLog.log(debug, "%s \n", t, this->timestamp_valid ? "valid" : "invalid", this->timestamp_sec, pps_offset(), this->fct); } }; /* This module decodes MNSC time information from an ETI source and * EDI time information*/ class TimestampDecoder : public RemoteControllable { public: /* offset_s: The modulator adds this offset to the TIST to define time of * frame transmission */ TimestampDecoder(double& offset_s); virtual ~TimestampDecoder() {} frame_timestamp getTimestamp(void); /* Update timestamp data from ETI */ void updateTimestampEti( uint8_t framephase, uint16_t mnsc, uint32_t pps, // In units of 1/16384000 s int32_t fct); /* Update timestamp data from EDI */ void updateTimestampEdi( uint32_t seconds_utc, uint32_t pps, // In units of 1/16384000 s int32_t fct, uint8_t framephase); /*********** REMOTE CONTROL ***************/ /* Base function to set parameters. */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; const char* name() { return "TS"; } protected: /* Push a new MNSC field into the decoder */ void pushMNSCData(uint8_t framephase, uint16_t mnsc); /* Each frame contains the TIST field with the PPS offset. * For each frame, this function must be called to update * the timestamp. * * pps is in units of 1/16384000 s * * This function also takes care of updating the second when * the pps rolls over. */ void updateTimestampPPS(uint32_t pps); /* Update the timestamp when a full set of MNSC data is * known. This function can be called at most every four * frames when the data is transferred using the MNSC. */ void updateTimestampSeconds(uint32_t secs); struct tm temp_time; uint32_t time_secs = 0; int32_t latestFCT = 0; uint32_t latestFP = 0; uint32_t time_pps = 0; double& timestamp_offset; int inhibit_second_update = 0; bool offset_changed = false; uint32_t time_secs_of_frame0 = 0; uint32_t time_pps_of_frame0 = 0; /* When the type or identifier don't match, the decoder must * be disabled */ bool enableDecode = false; /* Disable timstamps until full time has been received */ bool full_timestamp_received = false; }; Opendigitalradio-ODR-DabMod-f7eedef/src/Utils.cpp000066400000000000000000000326041475762153200220210ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "Utils.h" #include #include #include #include #include #if defined(HAVE_PRCTL) # include #endif static void printHeader() { std::cerr << "ODR-DabMod version " << #if defined(GITVERSION) GITVERSION #else VERSION #endif ", compiled at " << __DATE__ << ", " << __TIME__ << std::endl; std::cerr << "Compiled with features: " << #if defined(HAVE_ZEROMQ) "zeromq " << #endif #if defined(HAVE_OUTPUT_UHD) "output_uhd " << #endif #if defined(HAVE_SOAPYSDR) "output_soapysdr " << #endif #if defined(HAVE_LIMESDR) "output_limesdr " << #endif #if defined(__FAST_MATH__) "fast-math " << #endif #if defined(__SSE__) "SSE " << #endif #if defined(__ARM_NEON) "NEON " << #endif "\n"; } void printUsage(const char* progName) { FILE* out = stderr; fprintf(out, "Usage with configuration file:\n"); fprintf(out, "\t%s config_file.ini\n\n", progName); fprintf(out, "Usage with command line options:\n"); fprintf(out, "\t%s" " input" " (-f filename -F format" #if defined(HAVE_OUTPUT_UHD) " | -u uhddevice -F frequency" #endif // defined(HAVE_OUTPUT_UHD) ") [-o offset]" "\n\t" #if defined(HAVE_OUTPUT_UHD) " [-G txgain]" #endif // defined(HAVE_OUTPUT_UHD) " [-T filter_taps_file]" " [-a gain]" " [-c clockrate]" "\n\t" " [-g gainMode]" " [-m dabMode]" " [-r samplingRate]" " [-l]" " [-h]" "\n", progName); fprintf(out, "Where:\n"); fprintf(out, "input: ETI input filename (default: stdin), or\n"); fprintf(out, " tcp://source:port for ETI-over-TCP input, or\n"); fprintf(out, " udp://:port for EDI input.\n"); fprintf(out, "-f name: Use file output with given filename. (use /dev/stdout for standard output)\n"); fprintf(out, "-F format: Set the output format (see doc/example.ini for formats) for the file output.\n"); fprintf(out, "-o: Set the timestamp offset added to the timestamp in the ETI. The offset is a double.\n"); fprintf(out, " Specifying this option has two implications: It enables synchronous transmission,\n" " requiring an external REFCLK and PPS signal and frames that do not contain a valid timestamp\n" " get muted.\n\n"); #if defined(HAVE_OUTPUT_UHD) fprintf(out, "-u device: Use UHD output with given device string. (use "" for default device)\n"); fprintf(out, "-F frequency: Set the transmit frequency when using UHD output. (mandatory option when using UHD)\n"); fprintf(out, "-G txgain: Set the transmit gain for the UHD driver (default: 0)\n"); #endif // defined(HAVE_OUTPUT_UHD) fprintf(out, "-T taps_file: Enable filtering before the output, using the specified file containing the filter taps.\n"); fprintf(out, " Use 'default' as taps_file to use the internal taps.\n"); fprintf(out, "-a gain: Apply digital amplitude gain.\n"); fprintf(out, "-c rate: Set the DAC clock rate and enable Cic Equalisation.\n"); fprintf(out, "-g gainmode: Set computation gain mode: fix, max or var\n"); fprintf(out, "-m mode: Set DAB mode: (0: auto, 1-4: force).\n"); fprintf(out, "-r rate: Set output sampling rate (default: 2048000).\n\n"); fprintf(out, "-l: Loop file when reach end of file.\n"); fprintf(out, "-h: Print this help.\n"); } void printVersion(void) { FILE *out = stderr; fprintf(out, " ODR-DabMod is copyright (C) Her Majesty the Queen in Right of Canada,\n" " 2005 -- 2012 Communications Research Centre (CRC),\n" " and\n" " Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li\n" "\n" " http://opendigitalradio.org\n" "\n" " ODR-DabMod is free software: you can redistribute it and/or modify it\n" " under the terms of the GNU General Public License as published by the\n" " Free Software Foundation, either version 3 of the License, or (at your\n" " option) any later version.\n" "\n" " ODR-DabMod is distributed in the hope that it will be useful, but\n" " WITHOUT ANY WARRANTY; without even the implied warranty of\n" " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" " General Public License for more details.\n" "\n" " You should have received a copy of the GNU General Public License along\n" " with ODR-DabMod. If not, see .\n" "\n" ); } void printStartupInfo() { printHeader(); } void printModSettings(const mod_settings_t& mod_settings) { std::stringstream ss; // Print settings ss << "Input\n"; ss << " Type: " << mod_settings.inputTransport << "\n"; ss << " Source: " << mod_settings.inputName << "\n"; ss << "Output\n"; if (mod_settings.useFileOutput) { ss << " Name: " << mod_settings.outputName << "\n"; } #if defined(HAVE_OUTPUT_UHD) else if (mod_settings.useUHDOutput) { ss << " UHD\n" << " Device: " << mod_settings.sdr_device_config.device << "\n" << " Subdevice: " << mod_settings.sdr_device_config.subDevice << "\n" << " master_clock_rate: " << mod_settings.sdr_device_config.masterClockRate << "\n" << " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n" << " pps source: " << mod_settings.sdr_device_config.pps_src << "\n"; } #endif #if defined(HAVE_SOAPYSDR) else if (mod_settings.useSoapyOutput) { ss << " SoapySDR\n" " Device: " << mod_settings.sdr_device_config.device << "\n" << " master_clock_rate: " << mod_settings.sdr_device_config.masterClockRate << "\n"; } #endif #if defined(HAVE_DEXTER) else if (mod_settings.useDexterOutput) { ss << " PrecisionWave DEXTER\n"; } #endif #if defined(HAVE_LIMESDR) else if (mod_settings.useLimeOutput) { ss << " LimeSDR\n" " Device: " << mod_settings.sdr_device_config.device << "\n" << " master_clock_rate: " << mod_settings.sdr_device_config.masterClockRate << "\n"; } #endif #if defined(HAVE_BLADERF) else if (mod_settings.useBladeRFOutput) { ss << " BladeRF\n" " Device: " << mod_settings.sdr_device_config.device << "\n" << " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; } #endif else if (mod_settings.useZeroMQOutput) { ss << " ZeroMQ\n" << " Listening on: " << mod_settings.outputName << "\n" << " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; } ss << " Sampling rate: "; if (mod_settings.outputRate > 1000) { if (mod_settings.outputRate > 1000000) { ss << std::fixed << std::setprecision(4) << mod_settings.outputRate / 1000000.0 << " MHz\n"; } else { ss << std::fixed << std::setprecision(4) << mod_settings.outputRate / 1000.0 << " kHz\n"; } } else { ss << std::fixed << std::setprecision(4) << mod_settings.outputRate << " Hz\n"; } fprintf(stderr, "%s", ss.str().c_str()); } int set_realtime_prio(int prio) { // Set thread priority to realtime const int policy = SCHED_RR; sched_param sp; sp.sched_priority = sched_get_priority_min(policy) + prio; int ret = pthread_setschedparam(pthread_self(), policy, &sp); return ret; } void set_thread_name(const char *name) { #if defined(HAVE_PRCTL) prctl(PR_SET_NAME,name,0,0,0); #endif } double parse_channel(const std::string& chan) { double freq; if (chan == "5A") freq = 174928000; else if (chan == "5B") freq = 176640000; else if (chan == "5C") freq = 178352000; else if (chan == "5D") freq = 180064000; else if (chan == "6A") freq = 181936000; else if (chan == "6B") freq = 183648000; else if (chan == "6C") freq = 185360000; else if (chan == "6D") freq = 187072000; else if (chan == "7A") freq = 188928000; else if (chan == "7B") freq = 190640000; else if (chan == "7C") freq = 192352000; else if (chan == "7D") freq = 194064000; else if (chan == "8A") freq = 195936000; else if (chan == "8B") freq = 197648000; else if (chan == "8C") freq = 199360000; else if (chan == "8D") freq = 201072000; else if (chan == "9A") freq = 202928000; else if (chan == "9B") freq = 204640000; else if (chan == "9C") freq = 206352000; else if (chan == "9D") freq = 208064000; else if (chan == "10A") freq = 209936000; else if (chan == "10B") freq = 211648000; else if (chan == "10C") freq = 213360000; else if (chan == "10D") freq = 215072000; else if (chan == "11A") freq = 216928000; else if (chan == "11B") freq = 218640000; else if (chan == "11C") freq = 220352000; else if (chan == "11D") freq = 222064000; else if (chan == "12A") freq = 223936000; else if (chan == "12B") freq = 225648000; else if (chan == "12C") freq = 227360000; else if (chan == "12D") freq = 229072000; else if (chan == "13A") freq = 230784000; else if (chan == "13B") freq = 232496000; else if (chan == "13C") freq = 234208000; else if (chan == "13D") freq = 235776000; else if (chan == "13E") freq = 237488000; else if (chan == "13F") freq = 239200000; else { std::cerr << "Channel " << chan << " does not exist in table\n"; throw std::out_of_range("channel out of range"); } return freq; } std::optional convert_frequency_to_channel(double frequency) { const int freq = round(frequency); std::string chan; if (freq == 174928000) chan = "5A"; else if (freq == 176640000) chan = "5B"; else if (freq == 178352000) chan = "5C"; else if (freq == 180064000) chan = "5D"; else if (freq == 181936000) chan = "6A"; else if (freq == 183648000) chan = "6B"; else if (freq == 185360000) chan = "6C"; else if (freq == 187072000) chan = "6D"; else if (freq == 188928000) chan = "7A"; else if (freq == 190640000) chan = "7B"; else if (freq == 192352000) chan = "7C"; else if (freq == 194064000) chan = "7D"; else if (freq == 195936000) chan = "8A"; else if (freq == 197648000) chan = "8B"; else if (freq == 199360000) chan = "8C"; else if (freq == 201072000) chan = "8D"; else if (freq == 202928000) chan = "9A"; else if (freq == 204640000) chan = "9B"; else if (freq == 206352000) chan = "9C"; else if (freq == 208064000) chan = "9D"; else if (freq == 209936000) chan = "10A"; else if (freq == 211648000) chan = "10B"; else if (freq == 213360000) chan = "10C"; else if (freq == 215072000) chan = "10D"; else if (freq == 216928000) chan = "11A"; else if (freq == 218640000) chan = "11B"; else if (freq == 220352000) chan = "11C"; else if (freq == 222064000) chan = "11D"; else if (freq == 223936000) chan = "12A"; else if (freq == 225648000) chan = "12B"; else if (freq == 227360000) chan = "12C"; else if (freq == 229072000) chan = "12D"; else if (freq == 230784000) chan = "13A"; else if (freq == 232496000) chan = "13B"; else if (freq == 234208000) chan = "13C"; else if (freq == 235776000) chan = "13D"; else if (freq == 237488000) chan = "13E"; else if (freq == 239200000) chan = "13F"; else { return std::nullopt; } return chan; } std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode) { using namespace std::chrono; switch (dabmode) { case 1: return milliseconds(96); case 2: return milliseconds(24); case 3: return milliseconds(24); case 4: return milliseconds(48); default: throw std::runtime_error("invalid DAB mode"); } } time_t get_clock_realtime_seconds() { struct timespec t; if (clock_gettime(CLOCK_REALTIME, &t) != 0) { throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); } return t.tv_sec; } Opendigitalradio-ODR-DabMod-f7eedef/src/Utils.h000066400000000000000000000036421475762153200214660ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include "ConfigParser.h" void printUsage(const char* progName); void printVersion(void); void printStartupInfo(void); void printModSettings(const mod_settings_t& mod_settings); // Set SCHED_RR with priority prio (0=lowest) int set_realtime_prio(int prio); // Set the name of the thread void set_thread_name(const char *name); // Convert a channel like 10A to a frequency in Hz double parse_channel(const std::string& chan); // Convert a frequency in Hz to a channel. std::optional convert_frequency_to_channel(double frequency); // dabMode is either 1, 2, 3, 4, corresponding to TM I, TM II, TM III and TM IV. // throws a runtime_error if dabMode is not one of these values. std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode); time_t get_clock_realtime_seconds(); Opendigitalradio-ODR-DabMod-f7eedef/src/output/000077500000000000000000000000001475762153200215505ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/src/output/BladeRF.cpp000066400000000000000000000224741475762153200235240ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li Copyright (C) 2021 Steven Rossel, steven.rossel@bluewin.ch http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "BladeRF.h" #ifdef HAVE_BLADERF #include #include #include #include #include #include #include "Log.h" #include "Utils.h" using namespace std; namespace Output { BladeRF::BladeRF(SDRDeviceConfig &config) : SDRDevice(), m_conf(config) { etiLog.level(info) << "BladeRF:Creating the device with: " << m_conf.device; struct bladerf_devinfo devinfo; // init device infos bladerf_init_devinfo(&devinfo); // this function does not return a status int status = bladerf_open_with_devinfo(&m_device, &devinfo); // open device with info if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot open BladeRF output device"); } if (m_conf.refclk_src == "pps") { status = bladerf_set_vctcxo_tamer_mode(m_device, BLADERF_VCTCXO_TAMER_1_PPS); // 1 PPS tames the clock if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot set BladeRF refclk to pps"); } } else if (m_conf.refclk_src == "10mhz") { status = bladerf_set_vctcxo_tamer_mode(m_device, BLADERF_VCTCXO_TAMER_10_MHZ); // 10 MHz tames the clock if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot set BladeRF refclk to 10 MHz"); } } status = bladerf_set_sample_rate(m_device, m_channel, (bladerf_sample_rate)m_conf.sampleRate, NULL); if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot set BladeRF sample rate"); } bladerf_sample_rate host_sample_rate = 0; status = bladerf_get_sample_rate(m_device, m_channel, &host_sample_rate); if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot get BladeRF sample rate"); } etiLog.level(info) << "BladeRF sample rate set to " << std::to_string(host_sample_rate / 1000.0) << " kHz"; tune(m_conf.lo_offset, m_conf.frequency); bladerf_frequency cur_frequency = 0; status = bladerf_get_frequency(m_device, m_channel, &cur_frequency); if(status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot get BladeRF frequency"); } etiLog.level(info) << "BladeRF:Actual frequency: " << fixed << setprecision(3) << cur_frequency / 1000.0 << " kHz."; status = bladerf_set_gain(m_device, m_channel, (bladerf_gain)m_conf.txgain); // gain in [dB] if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot set BladeRF gain"); } bladerf_bandwidth cur_bandwidth = 0; status = bladerf_set_bandwidth(m_device, m_channel, (bladerf_bandwidth)m_conf.bandwidth, &cur_bandwidth); if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot set BladeRF bandwidth"); } /* ---------------------------- Streaming Config ---------------------------- */ const unsigned int num_buffers = 16; // Number of buffers to use in the underlying data stream const unsigned int buffer_size = 8192; // "to hold 2048 samples for one channel, a buffer must be at least 8192 bytes large" const unsigned int num_transfers = 8; // active USB transfers const unsigned int timeout_ms = 3500; /* Configure the device's x1 TX (SISO) channel for use with the * synchronous interface. SC16 Q11 samples *without* metadata are used. */ status = bladerf_sync_config(m_device, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, num_buffers, buffer_size, num_transfers, timeout_ms); if (status != 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot setup BladeRF stream"); } status = bladerf_enable_module(m_device, m_channel, true); if(status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); throw runtime_error("Cannot enable BladeRF channel"); } } BladeRF::~BladeRF() { if (m_device != nullptr) { //bladerf_deinit_stream(m_stream); /* Asynchronous API function*/ bladerf_enable_module(m_device, m_channel, false); bladerf_close(m_device); } } void BladeRF::tune(double lo_offset, double frequency) { int status; if (not m_device) throw runtime_error("BladeRF device not set up"); if (lo_offset != 0) { etiLog.level(info) << "lo_offset cannot be set at "<< std::to_string(lo_offset) << " with BladeRF output, has to be 0" << "\nlo_offset is now set to 0"; m_conf.lo_offset = 0; } status = bladerf_set_frequency(m_device, m_channel, (bladerf_frequency)m_conf.frequency); if (status < 0) { etiLog.level(error) << "Error setting BladeRF TX frequency: %s " << bladerf_strerror(status); } } double BladeRF::get_tx_freq(void) const { if (not m_device) throw runtime_error("Lime device not set up"); int status; bladerf_frequency cur_frequency = 0; status = bladerf_get_frequency(m_device, m_channel, &cur_frequency); if (status < 0) { etiLog.level(error) << "Error getting BladeRF TX frequency: %s " << bladerf_strerror(status); } return (double)cur_frequency; } void BladeRF::set_txgain(double txgain) { m_conf.txgain = txgain; if (not m_device) throw runtime_error("Lime device not set up"); int status; status = bladerf_set_gain(m_device, m_channel, (bladerf_gain)m_conf.txgain); // gain in [dB] if (status < 0) { etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); } } double BladeRF::get_txgain(void) const { if (not m_device) throw runtime_error("BladeRF device not set up"); bladerf_gain txgain = 0; int status; status = bladerf_get_gain(m_device, m_channel, &txgain); if (status < 0) { etiLog.level(error) << "Error getting BladeRF TX gain: %s " << bladerf_strerror(status); } return (double)txgain; } void BladeRF::set_bandwidth(double bandwidth) { bladerf_set_bandwidth(m_device, m_channel, (bladerf_bandwidth)m_conf.bandwidth, NULL); } double BladeRF::get_bandwidth(void) const { bladerf_bandwidth bw; bladerf_get_bandwidth(m_device, m_channel, &bw); return (double)bw; } SDRDevice::run_statistics_t BladeRF::get_run_statistics(void) const { run_statistics_t rs; rs["frames"].v = num_frames_modulated; return rs; } double BladeRF::get_real_secs(void) const { // TODO return 0.0; } void BladeRF::set_rxgain(double rxgain) { // TODO } double BladeRF::get_rxgain(void) const { // TODO return 0.0; } size_t BladeRF::receive_frame( complexf *buf, size_t num_samples, frame_timestamp &ts, double timeout_secs) { // TODO return 0; } bool BladeRF::is_clk_source_ok() { // TODO return true; } const char *BladeRF::device_name(void) const { return "BladeRF"; } std::optional BladeRF::get_temperature(void) const { if (not m_device) throw runtime_error("BladeRF device not set up"); float temp = 0.0; int status = bladerf_get_rfic_temperature(m_device, &temp); if (status >= 0) { return (double)temp; } else { etiLog.level(error) << "Error getting BladeRF temperature: %s " << bladerf_strerror(status); return std::nullopt; } } void BladeRF::transmit_frame(struct FrameData&& frame) // SC16 frames { const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); const int status = bladerf_sync_tx(m_device, frame.buf.data(), num_samples, NULL, 0); if (status < 0) { etiLog.level(error) << "Error transmitting samples with BladeRF: %s " << bladerf_strerror(status); throw runtime_error("Cannot transmit TX samples"); } num_frames_modulated++; } } // namespace Output #endif // HAVE_BLADERF Opendigitalradio-ODR-DabMod-f7eedef/src/output/BladeRF.h000066400000000000000000000062661475762153200231720ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li Copyright (C) 2021 Steven Rossel, steven.rossel@bluewin.ch http://opendigitalradio.org DESCRIPTION: It is an output driver for the BladeRF family of devices, and uses the libbladerf library. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_BLADERF #define SAMPLES_LEN 10000 // for transmit_frame() purpose, may be any (reasonable) size #include #include #include #include #include #include #include "Log.h" #include "output/SDR.h" #include "TimestampDecoder.h" #include "RemoteControl.h" #include "ThreadsafeQueue.h" #include #include namespace Output { class BladeRF : public Output::SDRDevice { public: BladeRF(SDRDeviceConfig& config); BladeRF(const BladeRF& other) = delete; BladeRF& operator=(const BladeRF& other) = delete; ~BladeRF(); virtual void tune(double lo_offset, double frequency) override; virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; virtual double get_rxgain(void) const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) const override; virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig& m_conf; struct bladerf *m_device; bladerf_channel m_channel = BLADERF_CHANNEL_TX(0); // channel TX0 //struct bladerf_stream* m_stream; /* used for asynchronous api */ size_t num_frames_modulated = 0; }; } // namespace Output #endif // HAVE_BLADERF Opendigitalradio-ODR-DabMod-f7eedef/src/output/Dexter.cpp000066400000000000000000000623241475762153200235160ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver using libiio targeting the PrecisionWave DEXTER board. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "output/Dexter.h" #ifdef HAVE_DEXTER #include #include #include #include #include "Log.h" #include "Utils.h" using namespace std; namespace Output { static constexpr uint64_t DSP_CLOCK = 2048000uLL * 80; static constexpr uint64_t IIO_TIMEOUT_MS = 1000; static constexpr size_t TRANSMISSION_FRAME_LEN_SAMPS = (2656 + 76 * 2552) * /* I+Q */ 2; static constexpr size_t IIO_BUFFERS = 2; static constexpr size_t IIO_BUFFER_LEN_SAMPS = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS; static string get_iio_error(int err) { char dst[256]; iio_strerror(-err, dst, sizeof(dst)); return string(dst); } static void fill_time(struct timespec *t) { if (clock_gettime(CLOCK_REALTIME, t) != 0) { throw std::runtime_error(string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); } } Dexter::Dexter(SDRDeviceConfig& config) : SDRDevice(), m_conf(config) { etiLog.level(info) << "Dexter:Creating the device"; m_ctx = iio_create_local_context(); if (not m_ctx) { throw std::runtime_error("Dexter: Unable to create iio context"); } int r; if ((r = iio_context_set_timeout(m_ctx, IIO_TIMEOUT_MS)) != 0) { etiLog.level(error) << "Failed to set IIO timeout " << get_iio_error(r); } m_dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx"); if (not m_dexter_dsp_tx) { throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device"); } m_ad9957 = iio_context_find_device(m_ctx, "ad9957"); if (not m_ad9957) { throw std::runtime_error("Dexter: Unable to find ad9957 iio device"); } m_ad9957_tx0 = iio_context_find_device(m_ctx, "ad9957_tx0"); if (not m_ad9957_tx0) { throw std::runtime_error("Dexter: Unable to find ad9957_tx0 iio device"); } // TODO make DC offset configurable and add to RC if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc0", 0)) != 0) { throw std::runtime_error("Failed to set dexter_dsp_tx.dc0 = false: " + get_iio_error(r)); } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc1", 0)) != 0) { throw std::runtime_error("Failed to set dexter_dsp_tx.dc1 = false: " + get_iio_error(r)); } if (m_conf.sampleRate != 2048000) { throw std::runtime_error("Dexter: Only 2048000 samplerate supported"); } tune(m_conf.lo_offset, m_conf.frequency); const double actual_freq = get_tx_freq(); etiLog.level(info) << "Dexter:Actual frequency: " << std::fixed << std::setprecision(3) << actual_freq / 1000.0 << " kHz."; const auto actual_freq_long = llrint(round(actual_freq)); const auto configured_freq_long = llrint(round(m_conf.frequency - m_conf.lo_offset)); if (actual_freq_long != configured_freq_long) { etiLog.level(error) << "Frequency tune: should " << std::fixed << std::setprecision(3) << (m_conf.frequency - m_conf.lo_offset) << " (" << configured_freq_long << ") " << " read back " << actual_freq << " (" << actual_freq_long << ")"; throw std::runtime_error("Could not set frequency!"); } // skip: Set bandwidth // skip: antenna // The FIFO should not contain data, but setting gain=0 before setting start_clks to zero is an additional security if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0)) != 0) { throw std::runtime_error("Failed to set dexter_dsp_tx.gain0 = 0 : " + get_iio_error(r)); } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_flush_fifo_trigger", 1)) != 0) { throw std::runtime_error("Failed to set dexter_dsp_tx.stream0_flush_fifo_trigger = 1 : " + get_iio_error(r)); } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) { throw std::runtime_error("Failed to set dexter_dsp_tx.stream0_start_clks = 0 : " + get_iio_error(r)); } constexpr int CHANNEL_INDEX = 0; m_tx_channel = iio_device_get_channel(m_ad9957_tx0, CHANNEL_INDEX); if (m_tx_channel == nullptr) { throw std::runtime_error("Dexter: Cannot create IIO channel."); } iio_channel_enable(m_tx_channel); m_buffer = iio_device_create_buffer(m_ad9957_tx0, IIO_BUFFER_LEN_SAMPS, 0); if (not m_buffer) { throw std::runtime_error("Dexter: Cannot create IIO buffer."); } // Flush the FPGA FIFO { constexpr size_t buflen_samps = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS; constexpr size_t buflen = buflen_samps * sizeof(int16_t); memset(iio_buffer_start(m_buffer), 0, buflen); ssize_t pushed = iio_buffer_push(m_buffer); if (pushed < 0) { etiLog.level(error) << "Dexter: init push buffer " << get_iio_error(pushed); } this_thread::sleep_for(chrono::milliseconds(200)); } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) { etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = " << m_conf.txgain << " : " << get_iio_error(r); } m_running = true; m_underflow_read_thread = std::thread(&Dexter::underflow_read_process, this); } void Dexter::channel_up() { int r; if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) { etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = " << m_conf.txgain << " : " << get_iio_error(r); } m_channel_is_up = true; etiLog.level(debug) << "DEXTER CHANNEL_UP"; } void Dexter::channel_down() { int r; if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0)) != 0) { etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); } // Setting stream0_start_clocks to 0 will flush out the FIFO, but we need to wait a bit before // we "up" the channel again if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) { etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0 : " << get_iio_error(r); } long long underflows_old = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows_old)) != 0) { etiLog.level(warn) << "Failed to read dexter_dsp_tx.buffer_underflows0 : " << get_iio_error(r); } long long underflows = underflows_old; // Limiting to 10*96ms is just a safety to avoid running into an infinite loop for (size_t i = 0; underflows == underflows_old && i < 10; i++) { if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows)) != 0) { etiLog.level(warn) << "Failed to read dexter_dsp_tx.buffer_underflows0 : " << get_iio_error(r); } this_thread::sleep_for(chrono::milliseconds(96)); } if (underflows == underflows_old) { etiLog.level(warn) << "DEXTER CHANNEL_DOWN, no underflow detected! " << underflows; } m_channel_is_up = false; etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; } void Dexter::handle_hw_time() { /* * On startup, wait until `gpsdo_locked==1` and `pps_loss_of_signal==0`, * then do the clocks alignment and go to normal state. * * In normal state, if `pps_loss_of_signal==1`, go to holdover state. * * If we've been in holdover state for longer than the configured time, or * if `pps_loss_of_signal==0` stop the mod and restart. */ int r; switch (m_clock_state) { case DexterClockState::Startup: { long long gpsdo_locked = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gpsdo_locked", &gpsdo_locked)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.gpsdo_locked: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } long long pps_loss_of_signal = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_loss_of_signal", &pps_loss_of_signal)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_loss_of_signal: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } if (gpsdo_locked == 1 and pps_loss_of_signal == 0) { /* Procedure: * Wait 200ms after second change, fetch pps_clks attribute * idem at the next second, and check that pps_clks incremented by DSP_CLOCK * If ok, store the correspondence between current second change (measured in UTC clock time) * and the counter value at pps rising edge. */ etiLog.level(info) << "Dexter: Waiting for second change..."; struct timespec time_at_startup; fill_time(&time_at_startup); time_at_startup.tv_nsec = 0; struct timespec time_now; do { fill_time(&time_now); this_thread::sleep_for(chrono::milliseconds(1)); } while (time_at_startup.tv_sec == time_now.tv_sec); this_thread::sleep_for(chrono::milliseconds(200)); long long pps_clks = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } time_t tnow = time_now.tv_sec; etiLog.level(info) << "Dexter: pps_clks " << pps_clks << " at UTC " << put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); time_at_startup.tv_sec = time_now.tv_sec; do { fill_time(&time_now); this_thread::sleep_for(chrono::milliseconds(1)); } while (time_at_startup.tv_sec == time_now.tv_sec); this_thread::sleep_for(chrono::milliseconds(200)); long long pps_clks2 = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks2)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } tnow = time_now.tv_sec; etiLog.level(info) << "Dexter: pps_clks increased by " << pps_clks2 - pps_clks << " at UTC " << put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); if ((uint64_t)pps_clks + DSP_CLOCK != (uint64_t)pps_clks2) { throw std::runtime_error("Dexter: Wrong increase of pps_clks, expected " + to_string(DSP_CLOCK)); } m_utc_seconds_at_startup = time_now.tv_sec; m_clock_count_at_startup = pps_clks2; m_holdover_since = chrono::steady_clock::time_point::min(); m_holdover_since_t = 0; m_clock_state = DexterClockState::Normal; etiLog.level(debug) << "Dexter: switch clock state Startup -> Normal"; } } break; case DexterClockState::Normal: { long long pps_loss_of_signal = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_loss_of_signal", &pps_loss_of_signal)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_loss_of_signal: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } if (pps_loss_of_signal == 1) { m_holdover_since = chrono::steady_clock::now(); m_holdover_since_t = chrono::system_clock::to_time_t(chrono::system_clock::now()); m_clock_state = DexterClockState::Holdover; etiLog.level(debug) << "Dexter: switch clock state Normal -> Holdover"; } } break; case DexterClockState::Holdover: { using namespace chrono; long long pps_loss_of_signal = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_loss_of_signal", &pps_loss_of_signal)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_loss_of_signal: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } const duration d = steady_clock::now() - m_holdover_since; const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); if (d > max_holdover_duration or pps_loss_of_signal == 0) { m_clock_state = DexterClockState::Startup; m_utc_seconds_at_startup = 0; m_clock_count_at_startup = 0; m_holdover_since = chrono::steady_clock::time_point::min(); m_holdover_since_t = 0; etiLog.level(debug) << "Dexter: switch clock state Holdover -> Startup"; } } break; } } void Dexter::tune(double lo_offset, double frequency) { // lo_offset is applied to the DSP, and frequency is given to the ad9957, this gives lower spurs int r = 0; const long long freq = frequency - lo_offset; if ((r = iio_device_attr_write_longlong(m_ad9957, "center_frequency", freq)) != 0) { etiLog.level(warn) << "Failed to set ad9957.center_frequency = " << freq << " : " << get_iio_error(r); } long long lo_offs = std::round(lo_offset); if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "frequency0", lo_offs)) != 0) { etiLog.level(warn) << "Failed to set dexter_dsp_tx.frequency0 = " << lo_offs << " : " << get_iio_error(r); } m_conf.frequency = get_tx_freq(); } double Dexter::get_tx_freq(void) const { int r = 0; long long lo_offset = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "frequency0", &lo_offset)) != 0) { etiLog.level(warn) << "Failed to read dexter_dsp_tx.frequency0: " << get_iio_error(r); return 0; } double frequency = 0; if ((r = iio_device_attr_read_double(m_ad9957, "center_frequency", &frequency)) != 0) { etiLog.level(warn) << "Failed to read ad9957.center_frequency: " << get_iio_error(r); return 0; } return frequency + lo_offset; } void Dexter::set_txgain(double txgain) { int r = 0; if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", txgain)) != 0) { etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = " << txgain << ": " << get_iio_error(r); } long long txgain_readback = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gain0", &txgain_readback)) != 0) { etiLog.level(warn) << "Failed to read dexter_dsp_tx.gain0: " << get_iio_error(r); } else { m_conf.txgain = txgain_readback; } } double Dexter::get_txgain(void) const { long long txgain_readback = 0; int r = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gain0", &txgain_readback)) != 0) { etiLog.level(warn) << "Failed to read dexter_dsp_tx.gain0: " << get_iio_error(r); } return txgain_readback; } void Dexter::set_bandwidth(double bandwidth) { return; } double Dexter::get_bandwidth(void) const { return 0; } SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const { run_statistics_t rs; { std::unique_lock lock(m_attr_thread_mutex); rs["underruns"].v = underflows; } rs["latepackets"].v = num_late; rs["frames"].v = num_frames_modulated; rs["in_holdover_since"].v = 0; rs["remaining_holdover_s"].v = m_conf.maxGPSHoldoverTime; switch (m_clock_state) { case DexterClockState::Startup: rs["clock_state"].v = "startup"; break; case DexterClockState::Normal: rs["clock_state"].v = "normal"; break; case DexterClockState::Holdover: rs["clock_state"].v = "holdover"; rs["in_holdover_since"].v = m_holdover_since_t; { using namespace std::chrono; const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); const duration remaining = max_holdover_duration - (steady_clock::now() - m_holdover_since); rs["remaining_holdover_s"].v = (ssize_t)duration_cast(remaining).count(); } break; } return rs; } double Dexter::get_real_secs(void) const { long long clks = 0; int r = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } switch (m_clock_state) { case DexterClockState::Startup: return 0; case DexterClockState::Normal: case DexterClockState::Holdover: return (double)m_utc_seconds_at_startup + (double)(clks - m_clock_count_at_startup) / (double)DSP_CLOCK; } throw std::logic_error("Unhandled switch"); } void Dexter::set_rxgain(double rxgain) { // TODO } double Dexter::get_rxgain(void) const { // TODO return 0; } size_t Dexter::receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) { // TODO return 0; } bool Dexter::is_clk_source_ok() { if (m_conf.enableSync) { handle_hw_time(); return m_clock_state != DexterClockState::Startup; } else { return true; } } const char* Dexter::device_name(void) const { return "Dexter"; } std::optional Dexter::get_temperature(void) const { std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/temp1_input", std::ios::in | std::ios::binary); if (in) { double tbaseboard; in >> tbaseboard; return tbaseboard / 1000.0; } else { return {}; } } void Dexter::transmit_frame(struct FrameData&& frame) { constexpr size_t frame_len_bytes = TRANSMISSION_FRAME_LEN_SAMPS * sizeof(int16_t); if (frame.buf.size() != frame_len_bytes) { etiLog.level(debug) << "Dexter::transmit_frame Expected " << frame_len_bytes << " got " << frame.buf.size(); throw std::runtime_error("Dexter: invalid buffer size"); } const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); if (not m_channel_is_up) { if (require_timestamped_tx) { if (m_clock_state == DexterClockState::Startup) { return; // not ready } else { constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; // TIMESTAMP_PPS_PER_DSP_CLOCKS=10 because timestamp_pps is represented in 16.384 MHz clocks uint64_t frame_start_clocks = // at second level ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK + m_clock_count_at_startup + // at subsecond level (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS; const double margin_s = frame.ts.offset_to_system_time(); long long clks = 0; int r = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) != 0) { etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } const double margin_device_s = (double)(frame_start_clocks - clks) / DSP_CLOCK; etiLog.level(debug) << "DEXTER FCT " << frame.ts.fct << " TS CLK " << ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << m_clock_count_at_startup << " + " << (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS << " = " << frame_start_clocks << " DELTA " << margin_s << " " << margin_device_s; // Ensure we hand the frame over to HW with a bit of margin if (margin_s < 0.2) { etiLog.level(warn) << "Skip frame short margin " << margin_s; num_late++; return; } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_start_clocks)) != 0) { etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_start_clocks << " : " << get_iio_error(r); num_late++; return; } m_require_timestamp_refresh = false; } } channel_up(); } if (m_require_timestamp_refresh) { etiLog.level(debug) << "DEXTER REQUIRE REFRESH"; channel_down(); m_require_timestamp_refresh = false; } // DabMod::launch_modulator ensures we get int16_t IQ here //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); //const int16_t *buf = reinterpret_cast(frame.buf.data()); if (m_channel_is_up) { for (size_t i = 0; i < IIO_BUFFERS; i++) { constexpr size_t buflen_samps = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS; constexpr size_t buflen = buflen_samps * sizeof(int16_t); memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen); ssize_t pushed = iio_buffer_push(m_buffer); if (pushed < 0) { etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed) << " after " << num_buffers_pushed << " bufs"; num_buffers_pushed = 0; channel_down(); break; } num_buffers_pushed++; } num_frames_modulated++; } { std::unique_lock lock(m_attr_thread_mutex); size_t u = underflows; lock.unlock(); if (u != 0 and u != prev_underflows) { etiLog.level(warn) << "Dexter: underflow! " << prev_underflows << " -> " << u; } prev_underflows = u; } } void Dexter::underflow_read_process() { m_underflow_ctx = iio_create_local_context(); if (not m_underflow_ctx) { throw std::runtime_error("Dexter: Unable to create iio context for underflow"); } auto dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx"); if (not dexter_dsp_tx) { throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device"); } set_thread_name("dexter_underflow"); while (m_running) { this_thread::sleep_for(chrono::seconds(1)); long long underflows_attr = 0; int r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows_attr); if (r == 0) { size_t underflows_new = underflows_attr; std::unique_lock lock(m_attr_thread_mutex); if (underflows_new != underflows and underflows_attr != 0) { underflows = underflows_new; } } } m_running = false; } Dexter::~Dexter() { m_running = false; if (m_underflow_read_thread.joinable()) { m_underflow_read_thread.join(); } if (m_ctx) { if (m_dexter_dsp_tx) { iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); } if (m_buffer) { iio_buffer_destroy(m_buffer); m_buffer = nullptr; } if (m_tx_channel) { iio_channel_disable(m_tx_channel); } iio_context_destroy(m_ctx); m_ctx = nullptr; } if (m_underflow_ctx) { iio_context_destroy(m_underflow_ctx); m_underflow_ctx = nullptr; } } } // namespace Output #endif // HAVE_DEXTER Opendigitalradio-ODR-DabMod-f7eedef/src/output/Dexter.h000066400000000000000000000101401475762153200231500ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver using libiio targeting the PrecisionWave DEXTER board. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_DEXTER #include "iio.h" #include #include #include #include #include #include #include "output/SDR.h" #include "ModPlugin.h" #include "EtiReader.h" #include "RemoteControl.h" namespace Output { enum class DexterClockState { Startup, Normal, Holdover }; class Dexter : public Output::SDRDevice { public: Dexter(SDRDeviceConfig& config); Dexter(const Dexter& other) = delete; Dexter& operator=(const Dexter& other) = delete; virtual ~Dexter(); virtual void tune(double lo_offset, double frequency) override; virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain() const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth() const override; virtual void transmit_frame(struct FrameData&& frame) override; virtual run_statistics_t get_run_statistics() const override; virtual double get_real_secs() const override; virtual void set_rxgain(double rxgain) override; virtual double get_rxgain() const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok() override; virtual const char* device_name() const override; virtual std::optional get_temperature() const override; private: void channel_up(); void channel_down(); void handle_hw_time(); bool m_channel_is_up = false; SDRDeviceConfig& m_conf; struct iio_context *m_ctx = nullptr; struct iio_device *m_dexter_dsp_tx = nullptr; struct iio_device *m_ad9957 = nullptr; struct iio_device *m_ad9957_tx0 = nullptr; struct iio_channel *m_tx_channel = nullptr; struct iio_buffer *m_buffer = nullptr; /* Underflows are counted in a separate thread */ struct iio_context *m_underflow_ctx = nullptr; std::atomic m_running = ATOMIC_VAR_INIT(false); std::thread m_underflow_read_thread; void underflow_read_process(); mutable std::mutex m_attr_thread_mutex; size_t underflows = 0; size_t prev_underflows = 0; size_t num_late = 0; size_t num_frames_modulated = 0; size_t num_buffers_pushed = 0; DexterClockState m_clock_state = DexterClockState::Startup; // Only valid when m_clock_state is not Startup uint64_t m_utc_seconds_at_startup = 0; uint64_t m_clock_count_at_startup = 0; // Only valid when m_clock_state Holdover std::chrono::steady_clock::time_point m_holdover_since = std::chrono::steady_clock::time_point::min(); std::time_t m_holdover_since_t = 0; }; } // namespace Output #endif //HAVE_DEXTER Opendigitalradio-ODR-DabMod-f7eedef/src/output/Feedback.cpp000066400000000000000000000256011475762153200237440ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: This presents a TCP socket to an external tool which calculates a Digital Predistortion model from a short sequence of transmit samples and corresponding receive samples. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "output/Feedback.h" #include "Utils.h" #include "Socket.h" using namespace std; namespace Output { DPDFeedbackServer::DPDFeedbackServer( std::shared_ptr device, uint16_t port, uint32_t sampleRate) : m_port(port), m_sampleRate(sampleRate), m_device(device) { if (m_port) { m_running.store(true); rx_burst_thread = std::thread( &DPDFeedbackServer::ReceiveBurstThread, this); burst_tcp_thread = std::thread( &DPDFeedbackServer::ServeFeedbackThread, this); } } DPDFeedbackServer::~DPDFeedbackServer() { m_running.store(false); burstRequest.mutex_notification.notify_all(); if (rx_burst_thread.joinable()) { rx_burst_thread.join(); } if (burst_tcp_thread.joinable()) { burst_tcp_thread.join(); } } void DPDFeedbackServer::set_tx_frame( const std::vector &buf, const frame_timestamp &buf_ts) { if (not m_running) { throw runtime_error("DPDFeedbackServer not running"); } unique_lock lock(burstRequest.mutex); if (buf.size() % sizeof(complexf) != 0) { throw logic_error("Buffer for tx frame has incorrect size"); } if (burstRequest.state == BurstRequestState::SaveTransmitFrame) { const size_t n = std::min( burstRequest.num_samples * sizeof(complexf), buf.size()); burstRequest.num_samples = n / sizeof(complexf); burstRequest.tx_samples.clear(); burstRequest.tx_samples.resize(n); // A frame will always begin with the NULL symbol, which contains // no power. Instead of taking n samples at the beginning of the // frame, we take them at the end and adapt the timestamp accordingly. const size_t start_ix = buf.size() - n; copy(buf.begin() + start_ix, buf.end(), burstRequest.tx_samples.begin()); frame_timestamp ts = buf_ts; ts += (1.0 * start_ix) / (sizeof(complexf) * m_sampleRate); burstRequest.tx_second = ts.timestamp_sec; burstRequest.tx_pps = ts.timestamp_pps; // Prepare the next state burstRequest.rx_second = ts.timestamp_sec; burstRequest.rx_pps = ts.timestamp_pps; burstRequest.state = BurstRequestState::SaveReceiveFrame; lock.unlock(); burstRequest.mutex_notification.notify_one(); } else { lock.unlock(); } } void DPDFeedbackServer::ReceiveBurstThread() { try { set_thread_name("dpdreceiveburst"); while (m_running) { unique_lock lock(burstRequest.mutex); while (burstRequest.state != BurstRequestState::SaveReceiveFrame) { if (not m_running) break; burstRequest.mutex_notification.wait(lock); } if (not m_running) break; const size_t num_samps = burstRequest.num_samples; frame_timestamp ts; ts.timestamp_sec = burstRequest.rx_second; ts.timestamp_pps = burstRequest.rx_pps; ts.timestamp_valid = true; // We need to free the mutex while we recv(), because otherwise we block the // TX thread lock.unlock(); const double device_time = m_device->get_real_secs(); const double cmd_time = ts.get_real_secs(); std::vector buf(num_samps * sizeof(complexf)); const double timeout = 60; size_t samples_read = m_device->receive_frame( reinterpret_cast(buf.data()), num_samps, ts, timeout); lock.lock(); burstRequest.rx_samples = std::move(buf); burstRequest.rx_samples.resize(samples_read * sizeof(complexf)); // The recv might have happened at another time than requested burstRequest.rx_second = ts.timestamp_sec; burstRequest.rx_pps = ts.timestamp_pps; etiLog.level(debug) << "DPD: acquired " << samples_read << " RX feedback samples " << "at time " << burstRequest.tx_second << " + " << std::fixed << burstRequest.tx_pps / 16384000.0 << " Delta=" << cmd_time - device_time; burstRequest.state = BurstRequestState::Acquired; lock.unlock(); burstRequest.mutex_notification.notify_one(); } } catch (const runtime_error &e) { etiLog.level(error) << "DPD Feedback RX runtime error: " << e.what(); } catch (const std::exception &e) { etiLog.level(error) << "DPD Feedback RX exception: " << e.what(); } catch (...) { etiLog.level(error) << "DPD Feedback RX unknown exception!"; } m_running.store(false); } void DPDFeedbackServer::ServeFeedback() { Socket::TCPSocket m_server_sock; m_server_sock.listen(m_port, "127.0.0.1"); etiLog.level(info) << "DPD Feedback server listening on port " << m_port; while (m_running) { auto client_sock = m_server_sock.accept(1000); if (not m_running) { break; } if (not client_sock.valid()) { // No connection request received continue; } uint8_t request_version = 0; ssize_t read = client_sock.recv(&request_version, 1, 0); if (!read) break; // done reading if (read < 0) { etiLog.level(info) << "DPD Feedback Server Client read request version failed: " << strerror(errno); break; } if (request_version != 1) { etiLog.level(info) << "DPD Feedback Server wrong request version"; break; } uint32_t num_samples = 0; read = client_sock.recv(&num_samples, 4, 0); if (!read) break; // done reading if (read < 0) { etiLog.level(info) << "DPD Feedback Server Client read num samples failed"; break; } // We are ready to issue the request now { unique_lock lock(burstRequest.mutex); burstRequest.num_samples = num_samples; burstRequest.state = BurstRequestState::SaveTransmitFrame; } // Wait for the result to be ready unique_lock lock(burstRequest.mutex); while (burstRequest.state != BurstRequestState::Acquired) { if (not m_running) break; burstRequest.mutex_notification.wait(lock); } burstRequest.state = BurstRequestState::None; lock.unlock(); burstRequest.num_samples = std::min(burstRequest.num_samples, std::min( burstRequest.tx_samples.size() / sizeof(complexf), burstRequest.rx_samples.size() / sizeof(complexf))); uint32_t num_samples_32 = burstRequest.num_samples; if (client_sock.sendall(&num_samples_32, sizeof(num_samples_32)) < 0) { etiLog.level(info) << "DPD Feedback Server Client send num_samples failed"; break; } if (client_sock.sendall( &burstRequest.tx_second, sizeof(burstRequest.tx_second)) < 0) { etiLog.level(info) << "DPD Feedback Server Client send tx_second failed"; break; } if (client_sock.sendall( &burstRequest.tx_pps, sizeof(burstRequest.tx_pps)) < 0) { etiLog.level(info) << "DPD Feedback Server Client send tx_pps failed"; break; } const size_t frame_bytes = burstRequest.num_samples * sizeof(complexf); if (burstRequest.tx_samples.size() < frame_bytes) { throw logic_error("DPD Feedback burstRequest invalid: not enough TX samples"); } if (client_sock.sendall( &burstRequest.tx_samples[0], frame_bytes) < 0) { etiLog.level(info) << "DPD Feedback Server Client send tx_frame failed"; break; } if (client_sock.sendall( &burstRequest.rx_second, sizeof(burstRequest.rx_second)) < 0) { etiLog.level(info) << "DPD Feedback Server Client send rx_second failed"; break; } if (client_sock.sendall( &burstRequest.rx_pps, sizeof(burstRequest.rx_pps)) < 0) { etiLog.level(info) << "DPD Feedback Server Client send rx_pps failed"; break; } if (burstRequest.rx_samples.size() < frame_bytes) { throw logic_error("DPD Feedback burstRequest invalid: not enough RX samples"); } if (client_sock.sendall( &burstRequest.rx_samples[0], frame_bytes) < 0) { etiLog.level(info) << "DPD Feedback Server Client send rx_frame failed"; break; } } } void DPDFeedbackServer::ServeFeedbackThread() { set_thread_name("dpdfeedbackserver"); while (m_running) { try { ServeFeedback(); } catch (const runtime_error &e) { etiLog.level(error) << "DPD Feedback Server runtime error: " << e.what(); } catch (const std::exception &e) { etiLog.level(error) << "DPD Feedback Server exception: " << e.what(); } catch (...) { etiLog.level(error) << "DPD Feedback Server unknown exception!"; } if (m_running) { this_thread::sleep_for(chrono::seconds(5)); } } m_running.store(false); } } // namespace Output Opendigitalradio-ODR-DabMod-f7eedef/src/output/Feedback.h000066400000000000000000000070501475762153200234070ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: This presents a TCP socket to an external tool which calculates a Digital Predistortion model from a short sequence of transmit samples and corresponding receive samples. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include "Log.h" #include "TimestampDecoder.h" #include "output/SDRDevice.h" namespace Output { enum class BurstRequestState { None, // To pending request SaveTransmitFrame, // The TX thread has to save an outgoing frame SaveReceiveFrame, // The RX thread has to save an incoming frame Acquired, // Both TX and RX frames are ready }; struct FeedbackBurstRequest { // All fields in this struct are protected mutable std::mutex mutex; std::condition_variable mutex_notification; BurstRequestState state = BurstRequestState::None; // In the SaveTransmit states, num_samples complexf samples are saved into // the vectors size_t num_samples = 0; // The timestamp of the first sample of the TX buffers uint32_t tx_second = 0; uint32_t tx_pps = 0; // in units of 1/16384000s // Samples contain complexf, but since our internal representation is uint8_t // we keep it like that std::vector tx_samples; // The timestamp of the first sample of the RX buffers uint32_t rx_second = 0; uint32_t rx_pps = 0; std::vector rx_samples; // Also, actually complexf }; // Serve TX samples and RX feedback samples over a TCP connection class DPDFeedbackServer { public: DPDFeedbackServer( std::shared_ptr device, uint16_t port, // Set to 0 to disable the Feedbackserver uint32_t sampleRate); DPDFeedbackServer(const DPDFeedbackServer& other) = delete; DPDFeedbackServer& operator=(const DPDFeedbackServer& other) = delete; ~DPDFeedbackServer(); void set_tx_frame(const std::vector &buf, const frame_timestamp& ts); private: // Thread that reacts to burstRequests and receives from the SDR device void ReceiveBurstThread(void); // Thread that listens for requests over TCP to get TX and RX feedback void ServeFeedbackThread(void); void ServeFeedback(void); std::thread rx_burst_thread; std::thread burst_tcp_thread; FeedbackBurstRequest burstRequest; std::atomic_bool m_running; uint16_t m_port = 0; uint32_t m_sampleRate = 0; std::shared_ptr m_device; }; } // namespace Output Opendigitalradio-ODR-DabMod-f7eedef/src/output/Lime.cpp000066400000000000000000000344361475762153200231540ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Evariste F5OEO, evaristec@gmail.com Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver using the LimeSDR library. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "output/Lime.h" #ifdef HAVE_LIMESDR //#define LIMEDEBUG #include #include #include #include #include "Log.h" #include "Utils.h" #ifdef __ARM_NEON__ #include #endif using namespace std; namespace Output { static constexpr size_t FRAMES_MAX_SIZE = 2; static constexpr size_t FRAME_LENGTH = 196608; // at native sample rate! #ifdef __ARM_NEON__ void conv_s16_from_float(unsigned n, const float *a, short *b) { unsigned i; const float32x4_t plusone4 = vdupq_n_f32(1.0f); const float32x4_t minusone4 = vdupq_n_f32(-1.0f); const float32x4_t half4 = vdupq_n_f32(0.5f); const float32x4_t scale4 = vdupq_n_f32(32767.0f); const uint32x4_t mask4 = vdupq_n_u32(0x80000000); for (i = 0; i < n / 4; i++) { float32x4_t v4 = ((float32x4_t *)a)[i]; v4 = vmulq_f32(vmaxq_f32(vminq_f32(v4, plusone4), minusone4), scale4); const float32x4_t w4 = vreinterpretq_f32_u32(vorrq_u32(vandq_u32( vreinterpretq_u32_f32(v4), mask4), vreinterpretq_u32_f32(half4))); ((int16x4_t *)b)[i] = vmovn_s32(vcvtq_s32_f32(vaddq_f32(v4, w4))); } } #else void conv_s16_from_float(unsigned n, const float *a, short *b) { unsigned i; for (i = 0; i < n; i++) { b[i] = (short)(a[i] * 32767.0f); } } #endif Lime::Lime(SDRDeviceConfig &config) : SDRDevice(), m_conf(config) { m_interpolate = m_conf.upsample; etiLog.level(info) << "Lime:Creating the device with: " << m_conf.device; const int device_count = LMS_GetDeviceList(nullptr); if (device_count < 0) { etiLog.level(error) << "Error making LimeSDR device: " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot find LimeSDR output device"); } lms_info_str_t device_list[device_count]; if (LMS_GetDeviceList(device_list) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot find LimeSDR output device"); } size_t device_i = 0; // If several cards, need to get device by configuration if (LMS_Open(&m_device, device_list[device_i], nullptr) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot open LimeSDR output device"); } if (LMS_Reset(m_device) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot reset LimeSDR output device"); } if (LMS_Init(m_device) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot init LimeSDR output device"); } if (m_conf.masterClockRate != 0) { if (LMS_SetClockFreq(m_device, LMS_CLOCK_CGEN, m_conf.masterClockRate) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot set master clock rate (CGEN) for LimeSDR output device"); } float_type masterClockRate = 0; if (LMS_GetClockFreq(m_device, LMS_CLOCK_CGEN, &masterClockRate) < 0) { etiLog.level(error) << "Error reading CGEN clock LimeSDR device: %s " << LMS_GetLastErrorMessage(); } else { etiLog.level(info) << "LimeSDR master clock rate set to " << fixed << setprecision(4) << masterClockRate; } } if (LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, true) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot enable channel for LimeSDR output device"); } if (LMS_SetSampleRate(m_device, m_conf.sampleRate * m_interpolate, 0) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot set sample rate for LimeSDR output device"); } float_type host_sample_rate = 0.0; if (LMS_GetSampleRate(m_device, LMS_CH_TX, m_channel, &host_sample_rate, NULL) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot get samplerate for LimeSDR output device"); } etiLog.level(info) << "LimeSDR sample rate set to " << fixed << setprecision(4) << host_sample_rate / 1000.0 << " kHz"; tune(m_conf.lo_offset, m_conf.frequency); float_type cur_frequency = 0.0; if (LMS_GetLOFrequency(m_device, LMS_CH_TX, m_channel, &cur_frequency) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot get frequency for LimeSDR output device"); } etiLog.level(info) << "LimeSDR:Actual frequency: " << fixed << setprecision(3) << cur_frequency / 1000.0 << " kHz."; if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain / 100.0) < 0) { //value 0..100 -> Normalize etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot set TX gain for LimeSDR output device"); } if (LMS_SetAntenna(m_device, LMS_CH_TX, m_channel, LMS_PATH_TX2) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot set antenna for LimeSDR output device"); } double bandwidth_calibrating = 2.5e6; // Minimal bandwidth if (LMS_Calibrate(m_device, LMS_CH_TX, m_channel, bandwidth_calibrating, 0) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot calibrate LimeSDR output device"); } switch (m_interpolate) { case 1: { //design matlab static double coeff[61] = { -0.0008126748726, -0.0003874975955, 0.0007290032809, -0.0009636150789, 0.0007643355639, 3.123887291e-05, -0.001263667713, 0.002418729011, -0.002785810735, 0.001787990681, 0.0006407162873, -0.003821208142, 0.006409643684, -0.006850919221, 0.004091503099, 0.00172403187, -0.008917749859, 0.01456955727, -0.01547530293, 0.009518089704, 0.00304264226, -0.01893160492, 0.0322769247, -0.03613986075, 0.02477015182, 0.0041426518, -0.04805115238, 0.09958232939, -0.1481673121, 0.1828524768, 0.8045722842, 0.1828524768, -0.1481673121, 0.09958232939, -0.04805115238, 0.0041426518, 0.02477015182, -0.03613986075, 0.0322769247, -0.01893160492, 0.00304264226, 0.009518089704, -0.01547530293, 0.01456955727, -0.008917749859, 0.00172403187, 0.004091503099, -0.006850919221, 0.006409643684, -0.003821208142, 0.0006407162873, 0.001787990681, -0.002785810735, 0.002418729011, -0.001263667713, 3.123887291e-05, 0.0007643355639, -0.0009636150789, 0.0007290032809, -0.0003874975955, -0.0008126748726}; LMS_SetGFIRCoeff(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, coeff, 61); } break; default: throw runtime_error("Unsupported interpolate: " + to_string(m_interpolate)); } // Frame duration is 96ms const size_t samplerate_ratio = m_conf.sampleRate / 2048000; const size_t buffer_size = FRAME_LENGTH * m_interpolate * samplerate_ratio * 10; // We take 10 Frame buffer size Fifo // Fifo seems to be round to multiple of SampleRate m_tx_stream.channel = m_channel; m_tx_stream.fifoSize = buffer_size; m_tx_stream.throughputVsLatency = 2.0; // Should be {0..1} but could be extended m_tx_stream.isTx = LMS_CH_TX; m_tx_stream.dataFmt = lms_stream_t::LMS_FMT_I16; if (LMS_SetupStream(m_device, &m_tx_stream) < 0) { etiLog.level(error) << "Error making LimeSDR device: %s " << LMS_GetLastErrorMessage(); throw runtime_error("Cannot setup TX stream for LimeSDR output device"); } LMS_StartStream(&m_tx_stream); LMS_SetGFIR(m_device, LMS_CH_TX, m_channel, LMS_GFIR3, true); } Lime::~Lime() { if (m_device != nullptr) { LMS_StopStream(&m_tx_stream); LMS_DestroyStream(m_device, &m_tx_stream); LMS_EnableChannel(m_device, LMS_CH_TX, m_channel, false); LMS_Close(m_device); } } void Lime::tune(double lo_offset, double frequency) { if (not m_device) throw runtime_error("Lime device not set up"); if (LMS_SetLOFrequency(m_device, LMS_CH_TX, m_channel, m_conf.frequency) < 0) { etiLog.level(error) << "Error setting LimeSDR TX frequency: %s " << LMS_GetLastErrorMessage(); } } double Lime::get_tx_freq(void) const { if (not m_device) throw runtime_error("Lime device not set up"); float_type cur_frequency = 0.0; if (LMS_GetLOFrequency(m_device, LMS_CH_TX, m_channel, &cur_frequency) < 0) { etiLog.level(error) << "Error getting LimeSDR TX frequency: %s " << LMS_GetLastErrorMessage(); } return cur_frequency; } void Lime::set_txgain(double txgain) { m_conf.txgain = txgain; if (not m_device) throw runtime_error("Lime device not set up"); if (LMS_SetNormalizedGain(m_device, LMS_CH_TX, m_channel, m_conf.txgain / 100.0) < 0) { etiLog.level(error) << "Error setting LimeSDR TX gain: %s " << LMS_GetLastErrorMessage(); } } double Lime::get_txgain(void) const { if (not m_device) throw runtime_error("Lime device not set up"); float_type txgain = 0; if (LMS_GetNormalizedGain(m_device, LMS_CH_TX, m_channel, &txgain) < 0) { etiLog.level(error) << "Error getting LimeSDR TX gain: %s " << LMS_GetLastErrorMessage(); } return txgain; } void Lime::set_bandwidth(double bandwidth) { LMS_SetLPFBW(m_device, LMS_CH_TX, m_channel, bandwidth); } double Lime::get_bandwidth(void) const { double bw; LMS_GetLPFBW(m_device, LMS_CH_TX, m_channel, &bw); return bw; } SDRDevice::run_statistics_t Lime::get_run_statistics(void) const { run_statistics_t rs; rs["underruns"].v = underflows; rs["overruns"].v = overflows; rs["dropped_packets"].v = dropped_packets; rs["frames"].v = num_frames_modulated; rs["fifo_fill"].v = m_last_fifo_fill_percent * 100; return rs; } double Lime::get_real_secs(void) const { // TODO return 0.0; } void Lime::set_rxgain(double rxgain) { // TODO } double Lime::get_rxgain(void) const { // TODO return 0.0; } size_t Lime::receive_frame( complexf *buf, size_t num_samples, frame_timestamp &ts, double timeout_secs) { // TODO return 0; } bool Lime::is_clk_source_ok() { // TODO return true; } const char *Lime::device_name(void) const { return "Lime"; } std::optional Lime::get_temperature(void) const { if (not m_device) throw runtime_error("Lime device not set up"); float_type temp = 0; if (LMS_GetChipTemperature(m_device, 0, &temp) >= 0) { return temp; } else { etiLog.level(error) << "Error getting LimeSDR temperature: %s " << LMS_GetLastErrorMessage(); return std::nullopt; } } void Lime::transmit_frame(struct FrameData&& frame) { if (not m_device) throw runtime_error("Lime device not set up"); // The frame buffer contains bytes representing FC32 samples const complexf *buf = reinterpret_cast(frame.buf.data()); const size_t numSamples = frame.buf.size() / sizeof(complexf); m_i16samples.resize(numSamples * 2); short *buffi16 = &m_i16samples[0]; conv_s16_from_float(numSamples * 2, (const float *)buf, buffi16); if ((frame.buf.size() % sizeof(complexf)) != 0) { throw runtime_error("Lime: invalid buffer size"); } lms_stream_status_t LimeStatus; LMS_GetStreamStatus(&m_tx_stream, &LimeStatus); overflows += LimeStatus.overrun; underflows += LimeStatus.underrun; dropped_packets += LimeStatus.droppedPackets; #ifdef LIMEDEBUG etiLog.level(info) << LimeStatus.fifoFilledCount << "/" << LimeStatus.fifoSize << ":" << numSamples << "Rate" << LimeStatus.linkRate / (2 * 2.0); etiLog.level(info) << "overrun" << LimeStatus.overrun << "underun" << LimeStatus.underrun << "drop" << LimeStatus.droppedPackets; #endif m_last_fifo_fill_percent.store((float)LimeStatus.fifoFilledCount / (float)LimeStatus.fifoSize); /* if(LimeStatus.fifoFilledCount>=5*FRAME_LENGTH*m_interpolate) // Start if FIFO is half full { if(not m_tx_stream_active) { etiLog.level(info) << "Fifo OK : Normal running"; LMS_StartStream(&m_tx_stream); m_tx_stream_active = true; } } */ ssize_t num_sent = 0; lms_stream_meta_t meta; meta.flushPartialPacket = true; meta.timestamp = 0; meta.waitForTimestamp = false; if (m_interpolate == 1) { num_sent = LMS_SendStream(&m_tx_stream, buffi16, numSamples, &meta, 1000); } if (num_sent == 0) { etiLog.level(info) << "Lime: zero samples sent" << num_sent; } else if (num_sent == -1) { etiLog.level(error) << "Error sending LimeSDR stream: %s " << LMS_GetLastErrorMessage(); } num_frames_modulated++; } } // namespace Output #endif // HAVE_LIMESDR Opendigitalradio-ODR-DabMod-f7eedef/src/output/Lime.h000066400000000000000000000060531475762153200226130ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Evariste F5OEO, evaristec@gmail.com Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver using the LimeSDR library. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H #include #endif #ifdef HAVE_LIMESDR #include #include #include #include "output/SDR.h" #include "ModPlugin.h" #include "EtiReader.h" #include "RemoteControl.h" #include namespace Output { class Lime : public Output::SDRDevice { public: Lime(SDRDeviceConfig &config); Lime(const Lime &other) = delete; Lime &operator=(const Lime &other) = delete; ~Lime(); virtual void tune(double lo_offset, double frequency) override; virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; virtual double get_rxgain(void) const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, frame_timestamp &ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) override; virtual const char *device_name(void) const override; virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig &m_conf; lms_device_t *m_device = nullptr; size_t m_channel = 0; // Should be set by config lms_stream_t m_tx_stream; bool m_tx_stream_active = false; size_t m_interpolate = 1; std::vector interpolatebuf; std::vector m_i16samples; std::atomic m_last_fifo_fill_percent = ATOMIC_VAR_INIT(0); size_t underflows = 0; size_t overflows = 0; size_t dropped_packets = 0; size_t num_frames_modulated = 0; }; } // namespace Output #endif //HAVE_SOAPYSDR Opendigitalradio-ODR-DabMod-f7eedef/src/output/SDR.cpp000066400000000000000000000416561475762153200227200ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2024 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "output/SDR.h" #include "output/UHD.h" #include "output/Lime.h" #include "output/Dexter.h" #include "PcDebug.h" #include "Log.h" #include "RemoteControl.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include using namespace std; namespace Output { // Maximum number of frames that can wait in frames. // Keep it low when not using synchronised transmission, in order to reduce delay. // When using synchronised transmission, use a 6s buffer to give us enough margin. static constexpr size_t FRAMES_MAX_SIZE_UNSYNC = 8; static constexpr size_t FRAMES_MAX_SIZE_SYNC = 250; // If the timestamp is further in the future than // 100 seconds, abort static constexpr double TIMESTAMP_ABORT_FUTURE = 100; SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : ModOutput(), ModMetadata(), RemoteControllable("sdr"), m_config(config), m_device(device) { // muting is remote-controllable m_config.muting = false; m_running.store(true); m_device_thread = std::thread(&SDR::process_thread_entry, this); if (m_config.dpdFeedbackServerPort > 0) { m_dpd_feedback_server = make_shared( m_device, m_config.dpdFeedbackServerPort, m_config.sampleRate); } RC_ADD_PARAMETER(txgain, "TX gain"); RC_ADD_PARAMETER(rxgain, "RX gain for DPD feedback"); RC_ADD_PARAMETER(bandwidth, "Analog front-end bandwidth"); RC_ADD_PARAMETER(freq, "Transmission frequency in Hz"); RC_ADD_PARAMETER(channel, "Transmission frequency as channel"); RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter"); RC_ADD_PARAMETER(temp, "Temperature in degrees C of the device"); RC_ADD_PARAMETER(underruns, "Counter of number of underruns"); RC_ADD_PARAMETER(latepackets, "Counter of number of late packets"); RC_ADD_PARAMETER(frames, "Counter of number of frames modulated"); RC_ADD_PARAMETER(synchronous, "1 if configured for synchronous transmission"); RC_ADD_PARAMETER(max_gps_holdover_time, "Max holdover duration in seconds"); #ifdef HAVE_OUTPUT_UHD if (std::dynamic_pointer_cast(device)) { RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO"); RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss"); } #endif // HAVE_OUTPUT_UHD RC_ADD_PARAMETER(queued_frames_ms, "Number of frames queued, represented in milliseconds"); #ifdef HAVE_LIMESDR if (std::dynamic_pointer_cast(device)) { RC_ADD_PARAMETER(fifo_fill, "A value representing the Lime FIFO fullness [percent]"); } #endif // HAVE_LIMESDR #ifdef HAVE_DEXTER if (std::dynamic_pointer_cast(device)) { RC_ADD_PARAMETER(in_holdover_since, "DEXTER timestamp when holdover began"); RC_ADD_PARAMETER(remaining_holdover_s, "DEXTER remaining number of seconds in holdover"); RC_ADD_PARAMETER(clock_state, "DEXTER clock state: startup/normal/holdover"); } #endif // HAVE_DEXTER } SDR::~SDR() { m_running.store(false); m_queue.trigger_wakeup(); if (m_device_thread.joinable()) { m_device_thread.join(); } } void SDR::set_sample_size(size_t size) { m_size = size; } int SDR::process(Buffer *dataIn) { if (not m_running) { throw std::runtime_error("SDR thread failed"); } const uint8_t* pDataIn = (uint8_t*)dataIn->getData(); m_frame.resize(dataIn->getLength()); std::copy(pDataIn, pDataIn + dataIn->getLength(), m_frame.begin()); // We will effectively transmit the frame once we got the metadata. return dataIn->getLength(); } meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) { if (m_device and m_running) { FrameData frame; frame.buf = std::move(m_frame); frame.sampleSize = m_size; if (metadataIn.empty()) { etiLog.level(info) << "SDR output: dropping one frame with invalid FCT"; } else { /* In transmission modes where several ETI frames are needed to * build one transmission frame (like in TM 1), we will have * several entries in metadataIn. Take the first one, which * comes from the earliest ETI frame. * This behaviour is different to earlier versions of ODR-DabMod, * which took the timestamp from the latest ETI frame. */ frame.ts = metadataIn[0].ts; // TODO check device running try { if (m_dpd_feedback_server) { m_dpd_feedback_server->set_tx_frame(frame.buf, frame.ts); } } catch (const runtime_error& e) { etiLog.level(warn) << "SDR output: Feedback server failed, restarting..."; m_dpd_feedback_server = std::make_shared( m_device, m_config.dpdFeedbackServerPort, m_config.sampleRate); } const auto max_size = m_config.enableSync ? FRAMES_MAX_SIZE_SYNC : FRAMES_MAX_SIZE_UNSYNC; auto r = m_queue.push_overflow(std::move(frame), max_size); etiLog.log(trace, "SDR,push %d %zu", r.overflowed, r.new_size); num_queue_overflows += r.overflowed ? 1 : 0; } } else { // Ignore frame } return {}; } void SDR::process_thread_entry() { // Set thread priority to realtime if (int ret = set_realtime_prio(1)) { etiLog.level(error) << "Could not set priority for SDR device thread:" << ret; } set_thread_name("sdrdevice"); last_tx_time_initialised = false; try { while (m_running.load()) { struct FrameData frame; etiLog.log(trace, "SDR,wait"); m_queue.wait_and_pop(frame); etiLog.log(trace, "SDR,pop"); if (m_running.load() == false) { break; } if (m_device) { handle_frame(std::move(frame)); } } } catch (const ThreadsafeQueueWakeup& e) { } catch (const runtime_error& e) { etiLog.level(error) << "SDR output thread caught runtime error: " << e.what(); } m_running.store(false); } const char* SDR::name() { if (m_device) { m_name = "OutputSDR("; m_name += m_device->device_name(); m_name += ")"; } else { m_name = "OutputSDR()"; } return m_name.c_str(); } void SDR::handle_frame(struct FrameData&& frame) { // Assumes m_device is valid if (not m_device->is_clk_source_ok()) { return; } const auto& time_spec = frame.ts; if (m_config.enableSync and m_config.muteNoTimestamps and not time_spec.timestamp_valid) { etiLog.log(info, "OutputSDR: Muting sample %d : no timestamp\n", frame.ts.fct); return; } if (m_config.enableSync and time_spec.timestamp_valid) { // Tx time from MNSC and TIST const uint32_t tx_second = frame.ts.timestamp_sec; const uint32_t tx_pps = frame.ts.timestamp_pps; const double device_time = m_device->get_real_secs(); if (not frame.ts.timestamp_valid) { /* We have not received a full timestamp through * MNSC. We sleep through the frame. */ etiLog.level(info) << "OutputSDR: Throwing sample " << frame.ts.fct << " away: incomplete timestamp " << tx_second << " / " << tx_pps; return; } if (frame.ts.offset_changed) { etiLog.level(debug) << "TS offset changed"; m_device->require_timestamp_refresh(); } if (last_tx_time_initialised) { const size_t sizeIn = frame.buf.size() / frame.sampleSize; // Checking units for the increment calculation: // samps * ticks/s / (samps/s) // (samps * ticks * s) / (s * samps) // ticks const uint64_t increment = (uint64_t)sizeIn * 16384000ul / (uint64_t)m_config.sampleRate; uint32_t expected_sec = last_tx_second + increment / 16384000ul; uint32_t expected_pps = last_tx_pps + increment % 16384000ul; while (expected_pps >= 16384000) { expected_sec++; expected_pps -= 16384000; } if (expected_sec != tx_second or expected_pps != tx_pps) { etiLog.level(warn) << "OutputSDR: timestamp irregularity at FCT=" << frame.ts.fct << std::fixed << " Expected " << expected_sec << "+" << (double)expected_pps/16384000.0 << "(" << expected_pps << ")" << " Got " << tx_second << "+" << (double)tx_pps/16384000.0 << "(" << tx_pps << ")"; m_device->require_timestamp_refresh(); } } last_tx_second = tx_second; last_tx_pps = tx_pps; last_tx_time_initialised = true; const double pps_offset = tx_pps / 16384000.0; etiLog.log(trace, "SDR,tist %f", time_spec.get_real_secs()); if (time_spec.get_real_secs() < device_time) { etiLog.level(warn) << "OutputSDR: Timestamp in the past at FCT=" << frame.ts.fct << " offset: " << std::fixed << time_spec.get_real_secs() - device_time << " (" << device_time << ")" " frame " << frame.ts.fct << ", tx_second " << tx_second << ", pps " << pps_offset; m_device->require_timestamp_refresh(); return; } if (time_spec.get_real_secs() > device_time + TIMESTAMP_ABORT_FUTURE) { etiLog.level(error) << "OutputSDR: Timestamp way too far in the future at FCT=" << frame.ts.fct << " offset: " << std::fixed << time_spec.get_real_secs() - device_time; throw std::runtime_error("Timestamp error. Aborted."); } } if (m_config.muting) { etiLog.log(info, "OutputSDR: Muting FCT=%d requested", frame.ts.fct); m_device->require_timestamp_refresh(); return; } m_device->transmit_frame(std::move(frame)); } // ======================================= // Remote Control // ======================================= void SDR::set_parameter(const string& parameter, const string& value) { stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "txgain") { ss >> m_config.txgain; m_device->set_txgain(m_config.txgain); } else if (parameter == "rxgain") { ss >> m_config.rxgain; m_device->set_rxgain(m_config.rxgain); } else if (parameter == "bandwidth") { ss >> m_config.bandwidth; m_device->set_bandwidth(m_config.bandwidth); } else if (parameter == "freq") { ss >> m_config.frequency; m_device->tune(m_config.lo_offset, m_config.frequency); } else if (parameter == "channel") { try { const double frequency = parse_channel(value); m_config.frequency = frequency; m_device->tune(m_config.lo_offset, m_config.frequency); } catch (const std::out_of_range& e) { throw ParameterError("Cannot parse channel"); } } else if (parameter == "muting") { ss >> m_config.muting; } else if (parameter == "synchronous") { uint32_t enableSync = 0; ss >> enableSync; m_config.enableSync = enableSync > 0; } else if (parameter == "max_gps_holdover_time") { ss >> m_config.maxGPSHoldoverTime; } else { stringstream ss_err; ss_err << "Parameter '" << parameter << "' is read-only or not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } const string SDR::get_parameter(const string& parameter) const { stringstream ss; ss << std::fixed; if (parameter == "txgain") { ss << m_config.txgain; } else if (parameter == "rxgain") { ss << m_config.rxgain; } else if (parameter == "bandwidth") { ss << m_config.bandwidth; } else if (parameter == "freq") { ss << m_config.frequency; } else if (parameter == "channel") { const auto maybe_freq = convert_frequency_to_channel(m_config.frequency); if (maybe_freq.has_value()) { ss << *maybe_freq; } else { throw ParameterError("Frequency is outside list of channels"); } } else if (parameter == "muting") { ss << m_config.muting; } else if (parameter == "temp") { if (not m_device) { throw ParameterError("OutputSDR has no device"); } const std::optional temp = m_device->get_temperature(); if (temp) { ss << *temp; } else { throw ParameterError("Temperature not available"); } } else if (parameter == "queued_frames_ms") { ss << m_queue.size() * chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); } else if (parameter == "synchronous") { ss << m_config.enableSync; } else if (parameter == "max_gps_holdover_time") { ss << m_config.maxGPSHoldoverTime; } else { if (m_device) { const auto stat = m_device->get_run_statistics(); try { const auto& value = stat.at(parameter).v; if (std::holds_alternative(value)) { ss << std::get(value); } else if (std::holds_alternative(value)) { ss << std::get(value); } else if (std::holds_alternative(value)) { ss << std::get(value); } else if (std::holds_alternative(value)) { ss << std::get(value); } else if (std::holds_alternative(value)) { ss << (std::get(value) ? 1 : 0); } else if (std::holds_alternative(value)) { ss << ""; } else { throw std::logic_error("variant alternative not handled"); } return ss.str(); } catch (const std::out_of_range&) { } } ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t SDR::get_all_values() const { json::map_t stat = m_device->get_run_statistics(); stat["txgain"].v = m_config.txgain; stat["rxgain"].v = m_config.rxgain; stat["freq"].v = m_config.frequency; stat["muting"].v = m_config.muting; stat["temp"].v = std::nullopt; const auto maybe_freq = convert_frequency_to_channel(m_config.frequency); if (maybe_freq.has_value()) { stat["channel"].v = *maybe_freq; } else { stat["channel"].v = std::nullopt; } if (m_device) { const std::optional temp = m_device->get_temperature(); if (temp) { stat["temp"].v = *temp; } } stat["queued_frames_ms"].v = m_queue.size() * (size_t)chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); stat["synchronous"].v = m_config.enableSync; stat["max_gps_holdover_time"].v = (size_t)m_config.maxGPSHoldoverTime; return stat; } } // namespace Output Opendigitalradio-ODR-DabMod-f7eedef/src/output/SDR.h000066400000000000000000000053651475762153200223620ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: Common interface for all SDR outputs */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include "ModPlugin.h" #include "output/SDRDevice.h" #include "output/Feedback.h" namespace Output { class SDR : public ModOutput, public ModMetadata, public RemoteControllable { public: SDR(SDRDeviceConfig& config, std::shared_ptr device); SDR(const SDR& other) = delete; SDR operator=(const SDR& other) = delete; virtual ~SDR(); virtual void set_sample_size(size_t size); virtual int process(Buffer *dataIn) override; virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; virtual const char* name() override; /*********** REMOTE CONTROL ***************/ /* Base function to set parameters. */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; /* Getting a parameter always returns a string. */ virtual const std::string get_parameter( const std::string& parameter) const override; virtual const json::map_t get_all_values() const override; private: void process_thread_entry(void); void handle_frame(struct FrameData&& frame); SDRDeviceConfig& m_config; std::atomic m_running = ATOMIC_VAR_INIT(false); std::thread m_device_thread; size_t m_size = sizeof(complexf); std::vector m_frame; ThreadsafeQueue m_queue; std::shared_ptr m_device; std::string m_name; std::shared_ptr m_dpd_feedback_server; bool last_tx_time_initialised = false; uint32_t last_tx_second = 0; uint32_t last_tx_pps = 0; size_t num_queue_overflows = 0; }; } Opendigitalradio-ODR-DabMod-f7eedef/src/output/SDRDevice.h000066400000000000000000000106451475762153200234770ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: Common interface for all SDR outputs */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "TimestampDecoder.h" namespace Output { enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; using complexf = std::complex; /* This structure is used as initial configuration for all SDR devices. * It must also contain all remote-controllable settings, otherwise * they will get lost on a modulator restart. */ struct SDRDeviceConfig { std::string device; std::string subDevice; // For UHD std::string tx_antenna; std::string rx_antenna; bool fixedPoint = false; long masterClockRate = 32768000; unsigned sampleRate = 2048000; double frequency = 0.0; double lo_offset = 0.0; double txgain = 0.0; double rxgain = 0.0; bool enableSync = false; double bandwidth = 0.0; unsigned upsample = 1; // When working with timestamps, mute the frames that // do not have a timestamp bool muteNoTimestamps = false; unsigned dabMode = 0; unsigned maxGPSHoldoverTime = 0; /* allowed values for UHD : auto, int, sma, mimo */ /* allowed values for BladeRF : pps, 10mhz */ std::string refclk_src; /* allowed values for UHD : int, sma, mimo */ std::string pps_src; /* allowed values for UHD : pos, neg */ std::string pps_polarity; /* What to do when the reference clock PLL loses lock */ refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; // muting can only be changed using the remote control bool muting = false; // TCP port on which to serve TX and RX samples for the // digital pre distortion learning tool uint16_t dpdFeedbackServerPort = 0; }; // Each frame contains one OFDM frame, and its // associated timestamp struct FrameData { // Buffer holding frame data std::vector buf; size_t sampleSize = sizeof(complexf); // A full timestamp contains a TIST according to standard // and time information within MNSC with tx_second. frame_timestamp ts; }; // All SDR Devices must implement the SDRDevice interface class SDRDevice { public: using run_statistics_t = json::map_t; virtual void tune(double lo_offset, double frequency) = 0; virtual double get_tx_freq(void) const = 0; virtual void set_txgain(double txgain) = 0; virtual double get_txgain(void) const = 0; virtual void transmit_frame(struct FrameData&& frame) = 0; virtual run_statistics_t get_run_statistics(void) const = 0; virtual double get_real_secs(void) const = 0; virtual void set_rxgain(double rxgain) = 0; virtual double get_rxgain(void) const = 0; virtual void set_bandwidth(double bandwidth) = 0; virtual double get_bandwidth(void) const = 0; virtual size_t receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) = 0; // Returns device temperature in degrees C virtual std::optional get_temperature(void) const = 0; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) = 0; virtual const char* device_name(void) const = 0; virtual void require_timestamp_refresh() { m_require_timestamp_refresh = true; } protected: bool m_require_timestamp_refresh = false; }; } // namespace Output Opendigitalradio-ODR-DabMod-f7eedef/src/output/Soapy.cpp000066400000000000000000000246031475762153200233540ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver using the SoapySDR library that can output to many devices. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "output/Soapy.h" #ifdef HAVE_SOAPYSDR #include #include #include #include #include #include "Log.h" #include "Utils.h" using namespace std; namespace Output { static constexpr size_t FRAMES_MAX_SIZE = 2; Soapy::Soapy(SDRDeviceConfig& config) : SDRDevice(), m_conf(config) { etiLog.level(info) << "Soapy:Creating the device with: " << m_conf.device; try { m_device = SoapySDR::Device::make(m_conf.device); stringstream ss; ss << "SoapySDR driver=" << m_device->getDriverKey(); ss << " hardware=" << m_device->getHardwareKey(); for (const auto &it : m_device->getHardwareInfo()) { ss << " " << it.first << "=" << it.second; } } catch (const std::exception &ex) { etiLog.level(error) << "Error making SoapySDR device: " << ex.what(); throw std::runtime_error("Cannot create SoapySDR output"); } if (m_conf.masterClockRate != 0) { m_device->setMasterClockRate(m_conf.masterClockRate); } etiLog.level(info) << "SoapySDR:Actual master clock rate: " << std::fixed << std::setprecision(4) << m_device->getMasterClockRate()/1000.0 << " kHz"; m_device->setSampleRate(SOAPY_SDR_TX, 0, m_conf.sampleRate); m_device->setSampleRate(SOAPY_SDR_RX, 0, m_conf.sampleRate); etiLog.level(info) << "SoapySDR:Actual TX rate: " << std::fixed << std::setprecision(4) << m_device->getSampleRate(SOAPY_SDR_TX, 0) / 1000.0 << " ksps."; tune(m_conf.lo_offset, m_conf.frequency); etiLog.level(info) << "SoapySDR:Actual frequency: " << std::fixed << std::setprecision(3) << m_conf.frequency / 1000.0 << " kHz."; if (m_conf.bandwidth > 0) { m_device->setBandwidth(SOAPY_SDR_TX, 0, m_conf.bandwidth); m_device->setBandwidth(SOAPY_SDR_RX, 0, m_conf.bandwidth); etiLog.level(info) << "SoapySDR:Actual TX bandwidth: " << std::fixed << std::setprecision(2) << m_device->getBandwidth(SOAPY_SDR_TX, 0); } m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); etiLog.level(info) << "SoapySDR:Actual TX gain: " << std::fixed << std::setprecision(2) << m_device->getGain(SOAPY_SDR_TX, 0); if (not m_conf.tx_antenna.empty()) { m_device->setAntenna(SOAPY_SDR_TX, 0, m_conf.tx_antenna); } etiLog.level(info) << "SoapySDR:Actual TX antenna: " << m_device->getAntenna(SOAPY_SDR_TX, 0); if (m_device->hasHardwareTime()) { using namespace std::chrono; auto n = system_clock::now(); const long long ticks = duration_cast(n.time_since_epoch()).count(); m_device->setHardwareTime(ticks); } const std::vector channels({0}); m_tx_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); m_rx_stream = m_device->setupStream(SOAPY_SDR_RX, "CF32", channels); } Soapy::~Soapy() { if (m_device != nullptr) { if (m_tx_stream != nullptr) { m_device->closeStream(m_tx_stream); } if (m_rx_stream != nullptr) { m_device->closeStream(m_rx_stream); } SoapySDR::Device::unmake(m_device); } } void Soapy::tune(double lo_offset, double frequency) { if (not m_device) throw runtime_error("Soapy device not set up"); SoapySDR::Kwargs offset_arg; offset_arg["OFFSET"] = to_string(lo_offset); m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency, offset_arg); m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0); } double Soapy::get_tx_freq(void) const { if (not m_device) throw runtime_error("Soapy device not set up"); // TODO lo offset return m_device->getFrequency(SOAPY_SDR_TX, 0); } void Soapy::set_txgain(double txgain) { m_conf.txgain = txgain; if (not m_device) throw runtime_error("Soapy device not set up"); m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); } double Soapy::get_txgain(void) const { if (not m_device) throw runtime_error("Soapy device not set up"); return m_device->getGain(SOAPY_SDR_TX, 0); } void Soapy::set_bandwidth(double bandwidth) { m_conf.bandwidth = bandwidth; if (not m_device) throw runtime_error("Soapy device not set up"); m_device->setBandwidth(SOAPY_SDR_TX, 0, m_conf.bandwidth); m_device->setBandwidth(SOAPY_SDR_RX, 0, m_conf.bandwidth); } double Soapy::get_bandwidth(void) const { if (not m_device) throw runtime_error("Soapy device not set up"); return m_device->getBandwidth(SOAPY_SDR_TX, 0); } SDRDevice::run_statistics_t Soapy::get_run_statistics(void) const { run_statistics_t rs; rs["underruns"].v = underflows; rs["overruns"].v = overflows; rs["timeouts"].v = timeouts; rs["frames"].v = num_frames_modulated; return rs; } double Soapy::get_real_secs(void) const { if (m_device) { long long time_ns = m_device->getHardwareTime(); return time_ns / 1e9; } else { return 0.0; } } void Soapy::set_rxgain(double rxgain) { m_device->setGain(SOAPY_SDR_RX, 0, m_conf.rxgain); m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0); } double Soapy::get_rxgain(void) const { return m_device->getGain(SOAPY_SDR_RX, 0); } size_t Soapy::receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) { int flags = 0; long long timeNs = ts.get_ns(); const size_t numElems = num_samples; void *buffs[1]; buffs[0] = buf; int ret = m_device->activateStream(m_rx_stream, flags, timeNs, numElems); if (ret != 0) { throw std::runtime_error(string("Soapy activate RX stream failed: ") + SoapySDR::errToStr(ret)); } m_rx_stream_active = true; int n_read = m_device->readStream( m_rx_stream, buffs, num_samples, flags, timeNs); ret = m_device->deactivateStream(m_rx_stream); if (ret != 0) { throw std::runtime_error(string("Soapy deactivate RX stream failed: ") + SoapySDR::errToStr(ret)); } m_rx_stream_active = false; if (n_read < 0) { throw std::runtime_error(string("Soapy failed to read from RX stream : ") + SoapySDR::errToStr(ret)); } ts.set_ns(timeNs); return n_read; } bool Soapy::is_clk_source_ok() { // TODO return true; } const char* Soapy::device_name(void) const { return "Soapy"; } std::optional Soapy::get_temperature(void) const { // TODO Unimplemented // LimeSDR exports 'lms7_temp' return std::nullopt; } void Soapy::transmit_frame(struct FrameData&& frame) { if (not m_device) throw runtime_error("Soapy device not set up"); long long int timeNs = frame.ts.get_ns(); // muting and mutenotimestamp is handled by SDR const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid); if (not m_tx_stream_active) { int flags = has_time_spec ? SOAPY_SDR_HAS_TIME : 0; int ret = m_device->activateStream(m_tx_stream, flags, timeNs); if (ret != 0) { throw std::runtime_error(string("Soapy activate TX stream failed: ") + SoapySDR::errToStr(ret)); } m_tx_stream_active = true; } // The frame buffer contains bytes representing FC32 samples const complexf *buf = reinterpret_cast(frame.buf.data()); const size_t numSamples = frame.buf.size() / sizeof(complexf); if ((frame.buf.size() % sizeof(complexf)) != 0) { throw std::runtime_error("Soapy: invalid buffer size"); } // Stream MTU is in samples, not bytes. const size_t mtu = m_device->getStreamMTU(m_tx_stream); size_t num_acc_samps = 0; while (num_acc_samps < numSamples) { const void *buffs[1]; buffs[0] = buf + num_acc_samps; const size_t samps_to_send = std::min(numSamples - num_acc_samps, mtu); const bool eob_because_muting = m_conf.muting; const bool end_of_burst = eob_because_muting or ( frame.ts.timestamp_valid and m_require_timestamp_refresh and samps_to_send <= mtu ); int flags = 0; auto num_sent = m_device->writeStream( m_tx_stream, buffs, samps_to_send, flags, timeNs); if (num_sent == SOAPY_SDR_TIMEOUT) { timeouts++; continue; } else if (num_sent == SOAPY_SDR_OVERFLOW) { overflows++; continue; } else if (num_sent == SOAPY_SDR_UNDERFLOW) { underflows++; continue; } if (num_sent < 0) { etiLog.level(error) << "Unexpected stream error " << SoapySDR::errToStr(num_sent); throw std::runtime_error("Fault in Soapy"); } timeNs += 1e9 * num_sent/m_conf.sampleRate; num_acc_samps += num_sent; if (end_of_burst) { int ret_deact = m_device->deactivateStream(m_tx_stream); if (ret_deact != 0) { throw std::runtime_error( string("Soapy deactivate TX stream failed: ") + SoapySDR::errToStr(ret_deact)); } m_tx_stream_active = false; m_require_timestamp_refresh = false; } if (eob_because_muting) { break; } } num_frames_modulated++; } } // namespace Output #endif // HAVE_SOAPYSDR Opendigitalradio-ODR-DabMod-f7eedef/src/output/Soapy.h000066400000000000000000000062011475762153200230130ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver using the SoapySDR library that can output to many devices. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_SOAPYSDR #include #include #include #include #include #include #include "output/SDR.h" #include "ModPlugin.h" #include "EtiReader.h" #include "RemoteControl.h" namespace Output { class Soapy : public Output::SDRDevice { public: Soapy(SDRDeviceConfig& config); Soapy(const Soapy& other) = delete; Soapy& operator=(const Soapy& other) = delete; ~Soapy(); virtual void tune(double lo_offset, double frequency) override; virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; virtual double get_rxgain(void) const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) const override; virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig& m_conf; SoapySDR::Device *m_device = nullptr; SoapySDR::Stream *m_tx_stream = nullptr; bool m_tx_stream_active = false; SoapySDR::Stream *m_rx_stream = nullptr; bool m_rx_stream_active = false; size_t timeouts = 0; size_t underflows = 0; size_t overflows = 0; size_t num_frames_modulated = 0; }; } // namespace Output #endif //HAVE_SOAPYSDR Opendigitalradio-ODR-DabMod-f7eedef/src/output/UHD.cpp000066400000000000000000000422331475762153200227000ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "output/UHD.h" #ifdef HAVE_OUTPUT_UHD //#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) #define MDEBUG(fmt, args...) #include "Log.h" #include #include #include // 3.11.0.0 introduces the API breaking change, where // uhd::msg is replaced by the new log API #if UHD_VERSION >= 3110000 # define UHD_HAS_LOG_API 1 # include # include #else # define UHD_HAS_LOG_API 0 # include # include #endif #include #include #include #include #include #include #include #include using namespace std; namespace Output { // Maximum number of frames that can wait in frames static const size_t FRAMES_MAX_SIZE = 8; static std::string stringtrim(const std::string &s) { auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c){ return std::isspace(c);} ); return std::string(wsfront, std::find_if_not(s.rbegin(), std::string::const_reverse_iterator(wsfront), [](int c){ return std::isspace(c);} ).base()); } #if UHD_HAS_LOG_API == 1 static void uhd_log_handler(const uhd::log::logging_info& info) { // do not print very short U messages, nor those of // verbosity trace or debug if (info.verbosity >= uhd::log::info and stringtrim(info.message).size() != 1) { etiLog.level(debug) << "UHD Message (" << (int)info.verbosity << ") " << info.component << ": " << info.message; } } #else static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) { if (type == uhd::msg::warning) { etiLog.level(warn) << "UHD Warning: " << msg; } else if (type == uhd::msg::error) { etiLog.level(error) << "UHD Error: " << msg; } else { // do not print very short U messages and such if (stringtrim(msg).size() != 1) { etiLog.level(debug) << "UHD Message: " << msg; } } } #endif // UHD_HAS_LOG_API UHD::UHD(SDRDeviceConfig& config) : SDRDevice(), m_conf(config), m_running(false) { std::stringstream device; device << m_conf.device; if (m_conf.masterClockRate != 0) { if (device.str() != "") { device << ","; } device << "master_clock_rate=" << m_conf.masterClockRate; } MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n", device.str().c_str(), this); #if UHD_HAS_LOG_API == 1 uhd::log::add_logger("dabmod", uhd_log_handler); try { uhd::log::set_console_level(uhd::log::fatal); } catch (const uhd::key_error&) { etiLog.level(warn) << "OutputUHD: Could not set UHD console loglevel"; } #else uhd::msg::register_handler(uhd_msg_handler); #endif uhd::set_thread_priority_safe(); etiLog.log(info, "OutputUHD:Creating the usrp device with: %s...", device.str().c_str()); m_usrp = uhd::usrp::multi_usrp::make(device.str()); etiLog.log(info, "OutputUHD:Using device: %s...", m_usrp->get_pp_string().c_str()); if (m_conf.masterClockRate != 0.0) { double master_clk_rate = m_usrp->get_master_clock_rate(); etiLog.log(debug, "OutputUHD:Checking master clock rate: %f...", master_clk_rate); if (fabs(master_clk_rate - m_conf.masterClockRate) > (m_conf.masterClockRate * 1e-6)) { throw std::runtime_error("Cannot set USRP master_clock_rate. Aborted."); } } MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n"); if (m_conf.refclk_src == "gpsdo-ettus") { m_usrp->set_clock_source("gpsdo"); } else { m_usrp->set_clock_source(m_conf.refclk_src); } m_usrp->set_time_source(m_conf.pps_src); if (m_conf.subDevice != "") { m_usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(m_conf.subDevice), uhd::usrp::multi_usrp::ALL_MBOARDS); } etiLog.level(debug) << "UHD clock source is " << m_usrp->get_clock_source(0); etiLog.level(debug) << "UHD time source is " << m_usrp->get_time_source(0); m_device_time = std::make_shared(m_usrp, m_conf); m_usrp->set_tx_rate(m_conf.sampleRate); etiLog.log(debug, "OutputUHD:Set rate to %d. Actual TX Rate: %f sps...", m_conf.sampleRate, m_usrp->get_tx_rate()); if (fabs(m_usrp->get_tx_rate() / m_conf.sampleRate) > m_conf.sampleRate * 1e-6) { throw std::runtime_error("Cannot set USRP sample rate. Aborted."); } if (m_conf.bandwidth > 0) { m_usrp->set_tx_bandwidth(m_conf.bandwidth); m_usrp->set_rx_bandwidth(m_conf.bandwidth); etiLog.level(info) << "OutputUHD:Actual TX bandwidth: " << std::fixed << std::setprecision(2) << m_usrp->get_tx_bandwidth(); } tune(m_conf.lo_offset, m_conf.frequency); etiLog.level(debug) << std::fixed << std::setprecision(3) << "OutputUHD:Actual TX frequency: " << m_conf.frequency; etiLog.level(debug) << std::fixed << std::setprecision(3) << "OutputUHD:Actual RX frequency: " << m_usrp->get_tx_freq(); m_usrp->set_tx_gain(m_conf.txgain); m_conf.txgain = m_usrp->get_tx_gain(); etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", m_conf.txgain); etiLog.log(debug, "OutputUHD:Mute on missing timestamps: %s", m_conf.muteNoTimestamps ? "enabled" : "disabled"); m_usrp->set_rx_rate(m_conf.sampleRate); etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", m_usrp->get_rx_rate()); if (not m_conf.rx_antenna.empty()) { m_usrp->set_rx_antenna(m_conf.rx_antenna); } etiLog.log(debug, "OutputUHD:Actual RX Antenna: %s", m_usrp->get_rx_antenna().c_str()); if (not m_conf.tx_antenna.empty()) { m_usrp->set_tx_antenna(m_conf.tx_antenna); } etiLog.log(debug, "OutputUHD:Actual TX Antenna: %s", m_usrp->get_tx_antenna().c_str()); m_usrp->set_rx_gain(m_conf.rxgain); etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", m_usrp->get_rx_gain()); const uhd::stream_args_t stream_args( m_conf.fixedPoint ? "sc16" : "fc32"); m_rx_stream = m_usrp->get_rx_stream(stream_args); m_tx_stream = m_usrp->get_tx_stream(stream_args); m_running.store(true); m_async_rx_thread = std::thread(&UHD::print_async_thread, this); MDEBUG("OutputUHD:UHD ready.\n"); } UHD::~UHD() { stop_threads(); } void UHD::tune(double lo_offset, double frequency) { if (lo_offset != 0.0) { etiLog.level(info) << std::fixed << std::setprecision(3) << "OutputUHD:Setting freq to " << frequency << " with LO offset " << lo_offset << "..."; const auto tr = uhd::tune_request_t(frequency, lo_offset); uhd::tune_result_t result = m_usrp->set_tx_freq(tr); etiLog.level(debug) << "OutputUHD: TX freq" << std::fixed << std::setprecision(0) << " Target RF: " << result.target_rf_freq << " Actual RF: " << result.actual_rf_freq << " Target DSP: " << result.target_dsp_freq << " Actual DSP: " << result.actual_dsp_freq; uhd::tune_result_t result_rx = m_usrp->set_rx_freq(tr); etiLog.level(debug) << "OutputUHD: RX freq" << std::fixed << std::setprecision(0) << " Target RF: " << result_rx.target_rf_freq << " Actual RF: " << result_rx.actual_rf_freq << " Target DSP: " << result_rx.target_dsp_freq << " Actual DSP: " << result_rx.actual_dsp_freq; } else { //set the centre frequency etiLog.level(info) << std::fixed << std::setprecision(3) << "OutputUHD:Setting freq to " << frequency << "..."; m_usrp->set_tx_freq(frequency); m_usrp->set_rx_freq(frequency); } m_conf.frequency = m_usrp->get_tx_freq(); } double UHD::get_tx_freq(void) const { return m_usrp->get_tx_freq(); } void UHD::set_txgain(double txgain) { m_usrp->set_tx_gain(txgain); m_conf.txgain = m_usrp->get_tx_gain(); } double UHD::get_txgain(void) const { return m_usrp->get_tx_gain(); } void UHD::set_bandwidth(double bandwidth) { m_usrp->set_tx_bandwidth(bandwidth); m_usrp->set_rx_bandwidth(bandwidth); m_conf.bandwidth = m_usrp->get_tx_bandwidth(); } double UHD::get_bandwidth(void) const { return m_usrp->get_tx_bandwidth(); } void UHD::transmit_frame(struct FrameData&& frame) { const double tx_timeout = 20.0; const size_t sample_size = m_conf.fixedPoint ? (2 * sizeof(int16_t)) : sizeof(complexf); const size_t sizeIn = frame.buf.size() / sample_size; uhd::tx_metadata_t md_tx; bool tx_allowed = true; // muting and mutenotimestamp is handled by SDR if (m_conf.enableSync and frame.ts.timestamp_valid) { uhd::time_spec_t timespec( frame.ts.timestamp_sec, frame.ts.pps_offset()); md_tx.time_spec = timespec; md_tx.has_time_spec = true; } else { md_tx.has_time_spec = false; } size_t usrp_max_num_samps = m_tx_stream->get_max_num_samps(); size_t num_acc_samps = 0; //number of accumulated samples while (tx_allowed and m_running.load() and (num_acc_samps < sizeIn)) { size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps); const bool eob_because_muting = m_conf.muting; // ensure the the last packet has EOB set if the timestamps has been // refreshed and need to be reconsidered. If muting was set, set the // EOB and quit the loop afterwards, to avoid an underrun. md_tx.end_of_burst = eob_because_muting or ( frame.ts.timestamp_valid and m_require_timestamp_refresh and samps_to_send <= usrp_max_num_samps ); m_require_timestamp_refresh = false; // send a single packet size_t num_tx_samps = m_tx_stream->send( frame.buf.data() + sample_size * num_acc_samps, samps_to_send, md_tx, tx_timeout); etiLog.log(trace, "UHD,sent %zu of %zu", num_tx_samps, samps_to_send); num_acc_samps += num_tx_samps; md_tx.time_spec += uhd::time_spec_t::from_ticks(num_tx_samps, (double)m_conf.sampleRate); if (num_tx_samps == 0) { etiLog.log(warn, "OutputUHD unable to write to device, skipping frame!"); break; } if (eob_because_muting) { break; } } num_frames_modulated++; } SDRDevice::run_statistics_t UHD::get_run_statistics(void) const { run_statistics_t rs; rs["underruns"].v = num_underflows; rs["overruns"].v = num_overflows; rs["late_packets"].v = num_late_packets; rs["frames"].v = num_frames_modulated; if (m_device_time) { const auto gpsdo_stat = m_device_time->get_gnss_stats(); rs["gpsdo_holdover"].v = gpsdo_stat.holdover; rs["gpsdo_num_sv"].v = gpsdo_stat.num_sv; } else { rs["gpsdo_holdover"].v = true; rs["gpsdo_num_sv"].v = 0; } return rs; } double UHD::get_real_secs(void) const { return m_usrp->get_time_now().get_real_secs(); } void UHD::set_rxgain(double rxgain) { m_usrp->set_rx_gain(m_conf.rxgain); m_conf.rxgain = m_usrp->get_rx_gain(); } double UHD::get_rxgain() const { return m_usrp->get_rx_gain(); } size_t UHD::receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) { uhd::stream_cmd_t cmd( uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE); cmd.num_samps = num_samples; cmd.stream_now = false; cmd.time_spec = uhd::time_spec_t(ts.timestamp_sec, ts.pps_offset()); m_rx_stream->issue_stream_cmd(cmd); uhd::rx_metadata_t md_rx; constexpr double timeout = 60; size_t samples_read = m_rx_stream->recv(buf, num_samples, md_rx, timeout); // Update the ts with the effective receive TS ts.timestamp_sec = md_rx.time_spec.get_full_secs(); ts.timestamp_pps = md_rx.time_spec.get_frac_secs() * 16384000.0; return samples_read; } // Return true if GPS and reference clock inputs are ok bool UHD::is_clk_source_ok(void) { bool ok = true; if (refclk_loss_needs_check()) { try { if (not m_usrp->get_mboard_sensor("ref_locked", 0).to_bool()) { ok = false; etiLog.level(alert) << "OutputUHD: External reference clock lock lost !"; if (m_conf.refclk_lock_loss_behaviour == CRASH) { throw std::runtime_error( "OutputUHD: External reference clock lock lost."); } } } catch (const uhd::lookup_error &e) { suppress_refclk_loss_check = true; etiLog.log(warn, "OutputUHD: This USRP does not have mboard " "sensor for ext clock loss. Check disabled."); } } if (m_device_time) { ok &= m_device_time->verify_time(); } return ok; } const char* UHD::device_name(void) const { return "UHD"; } std::optional UHD::get_temperature(void) const { try { return std::round(m_usrp->get_tx_sensor("temp", 0).to_real()); } catch (const uhd::lookup_error &e) { return std::nullopt; } } bool UHD::refclk_loss_needs_check() const { if (suppress_refclk_loss_check) { return false; } return m_conf.refclk_src != "internal"; } void UHD::stop_threads() { m_running.store(false); if (m_async_rx_thread.joinable()) { m_async_rx_thread.join(); } } void UHD::print_async_thread() { while (m_running.load()) { uhd::async_metadata_t async_md; if (m_usrp->get_device()->recv_async_msg(async_md, 1)) { const char* uhd_async_message = ""; bool failure = false; switch (async_md.event_code) { case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: break; case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: uhd_async_message = "Underflow"; num_underflows++; break; case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: uhd_async_message = "Packet loss between host and device."; failure = true; break; case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: uhd_async_message = "Packet had time that was late."; num_late_packets++; break; case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: uhd_async_message = "Underflow occurred inside a packet."; failure = true; break; case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: uhd_async_message = "Packet loss within a burst."; failure = true; break; default: uhd_async_message = "unknown event code"; failure = true; break; } if (failure) { etiLog.level(alert) << "Received Async UHD Message '" << uhd_async_message << "' at time " << async_md.time_spec.get_real_secs(); } } auto time_now = std::chrono::steady_clock::now(); if (last_print_time + std::chrono::seconds(1) < time_now) { const double usrp_time = m_usrp->get_time_now().get_real_secs(); if ( (num_underflows > num_underflows_previous) or (num_late_packets > num_late_packets_previous)) { etiLog.log(info, "OutputUHD status (usrp time: %f): " "%d underruns and %d late packets since last status.\n", usrp_time, num_underflows - num_underflows_previous, num_late_packets - num_late_packets_previous); } num_underflows_previous = num_underflows; num_late_packets_previous = num_late_packets; last_print_time = time_now; } } } } // namespace Output #endif // HAVE_OUTPUT_UHD Opendigitalradio-ODR-DabMod-f7eedef/src/output/UHD.h000066400000000000000000000072521475762153200223470ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: It is an output driver for the USRP family of devices, and uses the UHD library. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_OUTPUT_UHD #include #include #include #include #include #include #include #include "output/SDR.h" #include "output/USRPTime.h" #include "TimestampDecoder.h" #include #include namespace Output { class UHD : public Output::SDRDevice { public: UHD(SDRDeviceConfig& config); UHD(const UHD& other) = delete; UHD& operator=(const UHD& other) = delete; ~UHD(); virtual void tune(double lo_offset, double frequency) override; virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; virtual double get_rxgain(void) const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) const override; virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig& m_conf; uhd::usrp::multi_usrp::sptr m_usrp; uhd::tx_streamer::sptr m_tx_stream; uhd::rx_streamer::sptr m_rx_stream; std::shared_ptr m_device_time; size_t num_underflows = 0; size_t num_overflows = 0; size_t num_late_packets = 0; size_t num_frames_modulated = 0; size_t num_underflows_previous = 0; size_t num_late_packets_previous = 0; // Used to print statistics once a second std::chrono::steady_clock::time_point last_print_time; // Returns true if we want to verify loss of refclk bool refclk_loss_needs_check(void) const; mutable bool suppress_refclk_loss_check = false; // Poll asynchronous metadata from UHD std::atomic m_running; std::thread m_async_rx_thread; void stop_threads(void); void print_async_thread(void); }; } // namespace Output #endif // HAVE_OUTPUT_UHD Opendigitalradio-ODR-DabMod-f7eedef/src/output/USRPTime.cpp000066400000000000000000000240351475762153200236700ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: The part of the UHD output that takes care of the GPSDO. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #include "output/USRPTime.h" #ifdef HAVE_OUTPUT_UHD //#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) #define MDEBUG(fmt, args...) namespace Output { using namespace std; USRPTime::USRPTime( uhd::usrp::multi_usrp::sptr usrp, SDRDeviceConfig& conf) : m_usrp(usrp), m_conf(conf), time_last_check(timepoint_t::clock::now()) { if (m_conf.refclk_src == "internal" and m_conf.pps_src != "none") { etiLog.level(warn) << "OutputUHD: Unusal refclk and pps source settings. Setting time once, no monitoring."; set_usrp_time_from_pps(); } else if (m_conf.pps_src == "none") { if (m_conf.enableSync) { etiLog.level(warn) << "OutputUHD: you are using synchronous transmission without PPS input!"; } set_usrp_time_from_localtime(); } else if (m_conf.pps_src == "pps") { // let verify_time handle time setup } else if (m_conf.pps_src == "gpsdo") { if (gpsdo_is_ettus() ? check_gps_locked() : check_gps_timelock()) { set_usrp_time_from_pps(); gps_state = gps_state_e::monitor_fix; num_checks_without_gps_fix = 0; } else { // let verify_time handle time setup } } else if (m_conf.pps_src == "external") { set_usrp_time_from_pps(); } else { throw std::runtime_error("USRPTime not implemented yet: " + m_conf.pps_src); } } bool USRPTime::verify_time() { if (not gpsfix_needs_check()) { return true; } /* During bootup, we say the gpsdo is not ok, and we poll the GPSDO until * we reach lock. Then we sync time. If we do not reach lock in time, we * crash. * * Once we are synced and we have lock, everything ok. If we lose lock for * a number of seconds, we switch to the lost_fix state. * * In the lost fix state, we return false to get the TX muted, and we monitor. * If the fix comes back, we unmute. If we reach the timeout, we crash. */ check_gps(); const auto duration_without_fix = gps_fix_check_interval * num_checks_without_gps_fix; switch (gps_state) { case gps_state_e::bootup: if (duration_without_fix > initial_gps_fix_wait) { throw runtime_error("GPS did not fix in " + to_string(initial_gps_fix_wait) + " seconds"); } if (num_checks_without_gps_fix == 0) { if (m_conf.pps_src != "none") { set_usrp_time_from_pps(); } gps_state = gps_state_e::monitor_fix; return true; } return false; case gps_state_e::monitor_fix: if (duration_without_fix > m_conf.maxGPSHoldoverTime) { throw runtime_error("Lost GPS Fix for " + to_string(duration_without_fix) + " seconds"); } return true; } throw logic_error("End of USRPTime::verify_time() reached"); } gnss_stats_t USRPTime::get_gnss_stats(void) const { return gnss_stats; } void USRPTime::check_gps() { timepoint_t time_now = timepoint_t::clock::now(); // Divide interval by two because we alternate between // launch and check const auto checkinterval = chrono::seconds(lrint(gps_fix_check_interval/2.0)); if (gpsfix_needs_check() and time_last_check + checkinterval < time_now) { time_last_check = time_now; // Alternate between launching task and checking the result. if (gps_fix_future.valid()) { if (not gps_fix_future.get()) { if (num_checks_without_gps_fix == 0) { etiLog.level(alert) << "OutputUHD: GPS Time Lock lost"; } num_checks_without_gps_fix++; } else { if (num_checks_without_gps_fix) { etiLog.level(info) << "OutputUHD: GPS Time Lock recovered"; } num_checks_without_gps_fix = 0; } } else { // Checking the sensor here takes too much // time, it has to be done in a separate thread. if (gpsdo_is_ettus()) { gps_fix_future = std::async(std::launch::async, std::bind(&USRPTime::check_gps_locked, this) ); } else { gps_fix_future = std::async(std::launch::async, std::bind(&USRPTime::check_gps_timelock, this) ); } } } } bool USRPTime::gpsfix_needs_check() const { if (m_conf.refclk_src == "internal") { return false; } else if (gps_state == gps_state_e::monitor_fix and (m_conf.refclk_src == "gpsdo" or m_conf.refclk_src == "gpsdo-ettus")) { return (m_conf.maxGPSHoldoverTime != 0); } else if (gps_state == gps_state_e::bootup and (m_conf.refclk_src == "gpsdo" or m_conf.refclk_src == "gpsdo-ettus")) { return true; } else { return false; } } bool USRPTime::gpsdo_is_ettus() const { return (m_conf.refclk_src == "gpsdo-ettus"); } /* Return a uhd:time_spec representing current system time * with 1ms granularity. */ static uhd::time_spec_t uhd_timespec_now(void) { using namespace std::chrono; auto n = system_clock::now(); const long long ticks = duration_cast(n.time_since_epoch()).count(); return uhd::time_spec_t::from_ticks(ticks, 1000); } void USRPTime::set_usrp_time_from_localtime() { const auto t = uhd_timespec_now(); m_usrp->set_time_now(t); etiLog.level(info) << "OutputUHD: Setting USRP time to " << std::fixed << t.get_real_secs(); } void USRPTime::set_usrp_time_from_pps() { using namespace std::chrono; /* handling time for synchronisation: wait until the next full * second, and set the USRP time at next PPS */ auto now = uhd_timespec_now(); const time_t secs_since_epoch = now.get_full_secs(); while (secs_since_epoch + 1 > now.get_full_secs()) { this_thread::sleep_for(milliseconds(1)); now = uhd_timespec_now(); } /* We are now shortly after the second change. * Wait 200ms to ensure the PPS comes later. */ this_thread::sleep_for(milliseconds(200)); const auto time_set = uhd::time_spec_t(secs_since_epoch + 3, 0.0); etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " << std::fixed << time_set.get_real_secs(); m_usrp->set_time_unknown_pps(time_set); // The UHD doc says we need to give the USRP one second to update // all the internal registers. this_thread::sleep_for(seconds(1)); const auto time_usrp = m_usrp->get_time_now(); etiLog.level(info) << "OutputUHD: USRP time " << std::fixed << time_usrp.get_real_secs(); if (std::abs(time_usrp.get_real_secs() - time_set.get_real_secs()) > 10.0) { throw runtime_error("OutputUHD: Unable to set USRP time!"); } } // Check functionality of GPS sensors applicable to the ODR LEA-M8F board GPSDO bool USRPTime::check_gps_timelock() { bool locked = false; try { const string sensor_value = m_usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string(); const string gngga = m_usrp->get_mboard_sensor("gps_gngga", 0).to_pp_string(); std::stringstream ss(gngga); std::string item; std::vector elems; while (std::getline(ss, item, ',')) { elems.push_back(item); } const auto num_svs = (elems.size() > 7) ? elems[7] : "0"; gnss_stats.num_sv = std::stoi(num_svs); locked = (sensor_value.find("TIME LOCKED") != string::npos); } catch (const uhd::exception &e) { etiLog.level(warn) << "OutputUHD: no gps_timelock sensor: " << e.what(); } // LEA-M8F-patched UHD 3.12.0 has this additional sensor, that can // be used to distinguish holdover operation. Previous versions // did a config reset at startup to ensure we would not startup while // in holdover. try { const string disc_src = m_usrp->get_mboard_sensor("gps_discsrc", 0).to_pp_string(); locked &= (disc_src.find("gnss") != string::npos); } catch (const uhd::exception &e) { etiLog.level(warn) << "OutputUHD: no gps_timelock sensor: " << e.what(); } gnss_stats.holdover = not locked; return locked; } // Check function for GPS LOCKED sensor from the Ettus GPSDO bool USRPTime::check_gps_locked() { try { const uhd::sensor_value_t sensor_value( m_usrp->get_mboard_sensor("gps_locked", 0)); if (not sensor_value.to_bool()) { etiLog.level(warn) << "OutputUHD: gps_locked " << sensor_value.to_pp_string(); return false; } return true; } catch (const uhd::exception &e) { etiLog.level(warn) << "OutputUHD: no gps_locked sensor" << e.what(); return false; } } } // namespace Output #endif // HAVE_OUTPUT_UHD Opendigitalradio-ODR-DabMod-f7eedef/src/output/USRPTime.h000066400000000000000000000066701475762153200233420ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org DESCRIPTION: The part of the UHD output that takes care of the GPSDO and setting device time. */ /* This file is part of ODR-DabMod. ODR-DabMod 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. ODR-DabMod 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 ODR-DabMod. If not, see . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_OUTPUT_UHD #include #include #include #include #include #include #include "Log.h" #include "output/SDR.h" #include "TimestampDecoder.h" #include "RemoteControl.h" #include #include namespace Output { struct gnss_stats_t { int num_sv = 0; // Number of Satellite Vehicles used bool holdover = false; // True if LEA-M8F uses its internal time reference }; class USRPTime { public: USRPTime( uhd::usrp::multi_usrp::sptr usrp, SDRDeviceConfig& conf); // Verifies the GPSDO state, that the device time is ok. // Returns true if all ok. Needs to be called so the device // time gets properly set. // Should be called more often than the gps_fix_check_interval bool verify_time(void); gnss_stats_t get_gnss_stats(void) const; // Wait time in seconds to get fix static const int initial_gps_fix_wait = 180; // Interval for checking the GPS at runtime static constexpr double gps_fix_check_interval = 10.0; // seconds private: enum class gps_state_e { /* In the bootup state, we wait for correct time lock and * the first PPS, and then sync time. */ bootup, /* Once the system is up, we check lock every now and then. If the * fix is lost for too long, we crash. */ monitor_fix, }; void check_gps(); uhd::usrp::multi_usrp::sptr m_usrp; SDRDeviceConfig& m_conf; gps_state_e gps_state = gps_state_e::bootup; int num_checks_without_gps_fix = 1; gnss_stats_t gnss_stats; using timepoint_t = std::chrono::time_point; timepoint_t time_last_check; std::future gps_fix_future; // Returns true if we want to check for the gps_timelock sensor bool gpsfix_needs_check(void) const; // Return true if the gpsdo is from ettus, false if it is the ODR // LEA-M8F board is used bool gpsdo_is_ettus(void) const; void set_usrp_time_from_localtime(void); void set_usrp_time_from_pps(void); bool check_gps_timelock(void); bool check_gps_locked(void); }; } // namespace Output #endif // HAVE_OUTPUT_UHD Opendigitalradio-ODR-DabMod-f7eedef/src/testremotecontrol/000077500000000000000000000000001475762153200240045ustar00rootroot00000000000000Opendigitalradio-ODR-DabMod-f7eedef/src/testremotecontrol/Makefile000066400000000000000000000001661475762153200254470ustar00rootroot00000000000000CXXFLAGS=-Wall -g -lboost_system -lboost_thread -I.. all: test test: test.cpp ../RemoteControl.cpp clean: rm test Opendigitalradio-ODR-DabMod-f7eedef/src/testremotecontrol/README000066400000000000000000000001621475762153200246630ustar00rootroot00000000000000This folder contains a non-essential small demo + test program for the remote control 2012, Matthias P. Braendli Opendigitalradio-ODR-DabMod-f7eedef/src/testremotecontrol/test.cpp000066400000000000000000000040301475762153200254640ustar00rootroot00000000000000#include #include #include #include "RemoteControl.h" using namespace std; class TestControllable : public RemoteControllable { public: TestControllable(string name) : RemoteControllable(name) { RC_ADD_PARAMETER(foo, "That's the foo"); RC_ADD_PARAMETER(bar, "That's the bar"); RC_ADD_PARAMETER(baz, "That's the baz"); } void set_parameter(string parameter, string value) { stringstream ss(value); ss.exceptions ( stringstream::failbit | stringstream::badbit ); if (parameter == "foo") { ss >> foo_; } else if (parameter == "bar") { bar_ = value; } else if (parameter == "baz") { ss >> baz_; } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } string get_parameter(string parameter) { stringstream ss; if (parameter == "foo") { ss << foo_; } else if (parameter == "bar") { ss << bar_; } else if (parameter == "baz") { ss << baz_; } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } private: long foo_; std::string bar_; double baz_; }; int main() { RemoteControllerTelnet rc (2121); TestControllable t("test1"); TestControllable t2("test2"); t.enrol_at(rc); t2.enrol_at(rc); rc.start(); std::cerr << "Thread has been launched" << std::endl; sleep(100); std::cerr << "Stop" << std::endl; rc.stop(); return 0; }