Opendigitalradio-ODR-DabMux-29c710c/000077500000000000000000000000001476627344300171425ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/.gitignore000066400000000000000000000005421476627344300211330ustar00rootroot00000000000000*.o *.Po .*.swp .directory odr-dabmux odr-zmq2farsync odr-zmq2edi zmqinput-keygen src/dabOutput/.deps/ src/dabOutput/.dirstamp aclocal.m4 build-aux configure Makefile.in Makefile autom4te.cache config.h config.log config.status lib/Makefile src/Makefile stamp-h1 config.h.in~ config.h.in .deps .dirstamp *.plist cscope.out ctags tags .clang_complete Opendigitalradio-ODR-DabMux-29c710c/.travis.yml000066400000000000000000000023471476627344300212610ustar00rootroot00000000000000language: cpp dist: focal jobs: include: # Clang on OSX - 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-output-edi" os: linux arch: amd64 compiler: gcc addons: &linuxaddons apt: packages: &packages - libzmq3-dev - libzmq5 - automake - libtool - libboost-all-dev - libcurl4-openssl-dev - g++-10 - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="--enable-output-raw" arch: amd64 compiler: gcc addons: *linuxaddons - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="" arch: amd64 compiler: gcc addons: *linuxaddons - env: MATRIX_EVAL="CC=gcc-10 CXX=g++-10" CONF="" arch: arm64 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 boost || true brew install curl || true fi script: - | ./bootstrap.sh ./configure $CONF make Opendigitalradio-ODR-DabMux-29c710c/AUTHORS000066400000000000000000000023051476627344300202120ustar00rootroot00000000000000Jean-Daniel Langlois <> for the Communications Research Centre, Ottawa, Canada, 2002: First version of DabMux.cpp (a.k.a. createETI.cpp) Pascal Charest for the Communications Research Centre, Ottawa, Canada, 2002-2009: Principal developer and maintainer of CRC-DABMUX Jean-Michel Bouffard for the Communications Research Centre, Ottawa, Canada, 2004: Minor changes for crc.c, DabMux.cpp Martin-Pierre Lussier for the Communications Research Centre, Ottawa, Canada, 2004, 2006: Minor changes for PcDebug.h, bridge.c, bridge.h, dabOutput.cpp FarSite Communications Ltd. All files included in lib/farsite/ Robin Alexander Improvements on the Web GUI Matthias P. Braendli for Opendigitalradio, since 2012: Configuration file Time encoding into necessary for SFN using ODR-DabMod and third-party modulators Service linking, announcements, frequency information EDI inputs and outputs Improved logging (syslog) and monitoring Remote Control ZeroMQ ETI output and contribution inputs STI/D UDP input Opendigitalradio-ODR-DabMux-29c710c/COPYING000066400000000000000000001045131476627344300202010ustar00rootroot00000000000000 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-DabMux-29c710c/ChangeLog000066400000000000000000000433411476627344300207210ustar00rootroot00000000000000This file contains information about the changes done to ODR-DabMux in this repository 2025-03-18: Matthias P. Braendli (v5.1.0): Fix startup value of DLFC and FCT. Add statistics for EDI/TCP outputs. 2024-10-03: Matthias P. Braendli (v5.0.0): Remove odr-zmq2edi. Make compatible with easydab again. Fix timestamp issue with EDI streams that have seconds=0. Fix receiving multicast streams, when several multicast groups are on the same port. 2024-05-05: Matthias P. Braendli (v4.5.0): Switch project to C++17. Update common code, with ClockTAI improvements among others. Add uatype journaline to config. Add ZMQ output to odr-zmq2edi. Increase repetition rate for labels. Add showjson to remote control. 2024-03-23: Matthias P. Braendli (v2.3.2): This is a patch version for v2.3.1 Fix compilation issues with recent boost and compiler versions 2023-08-12: Matthias P. Braendli (v4.4.1): Fix some compilation issues. 2023-05-09: Matthias P. Braendli (v4.4.0): Ensure CIF count continuity on startup, and make CIF count consistent with timestamp across restarts. Ensure EDI output inserts ESTn tags in the order given in the config file. 2022-10-18: Matthias P. Braendli (v4.3.0): Add "load_entire_file" configuration setting for file inputs. Add EDI/TCP output pre-roll burst to reduce downtime on new connections. Support reading the mux config in JSON format. Fix a 32-bit SId parse issue on 32-bit systems. Improve installation instructions in INSTALL.md. 2022-04-19: Matthias P. Braendli (v4.2.1): Fix `make dist` 2022-04-19: Matthias P. Braendli (v4.2.0): Add FIG 0/14 FEC scheme defintion, for enhancedpacket mode subchannels. Remove useless List Terminator for FIG0/13, and add support for Broadcast WebSite. Add timestamps to log output. Update manpages. 2022-03-15: Matthias P. Braendli (v4.1.0): Improve EDI input. Fix crash when FIG0/7 doesn't have enough space. Fix FIG0/6 (service linking) database continuation flag. Simplify compilation on ARM. Rework web GUI (Thanks to Robin) 2021-06-03: Matthias P. Braendli (v4.0.0): Incompatible change: Replace EDI packet interleaving by packet spreading (changes configuration file format for ODR-DabMux and command line options for zmq2edi). Add configurability for EDI UDP multicast input. Simplify FIG 0/9, avoid sending an empty subfield list. FIG 0/13 improvements for packet services, deprecate 'figtype' component setting which is replaced by 'user-application'. Improvements for timestamped input. Add --version command line option to all tools. EDI output: make transport addressing optional. 2020-05-26: Matthias P. Braendli (v3.1.1): Fix FIG2 segment count issue. Handle disconnects better in EDI output. Fix buffering issue with timestamped EDI input. 2020-04-15: Matthias P. Braendli (v3.1.0): Initialise TIST on startup rounded to 24ms, as it was done before v2.4.0 (see '5b1dea7'). Refresh TAI bulletin before it expires. Set SO_REUSEADDR for TCP listening sockets (avoids some startup failures). Add EDI input TIST delay setting, and improve EDI input stats and error handling. Add FIG 0/7 and Alarm announcement support (Thanks to Mathias Kuntze). Add startupcheck functionality to ODR-DabMux and ODR-ZMQ2EDI. ZMQ2EDI will not quit anymore in case of input issues. Handle FCT discontinuity in ZMQ2EDI, and display better statistics which are usable by the new munin stats script doc/stats_zmq2edi_munin.py. 2019-10-30: Matthias P. Braendli (v3.0.1): Fix compilation on older systems (like debian jessie) 2019-10-29: Matthias P. Braendli (v3.0.0): Add EDI input with support for timestamps, which requires a change in the configuration file format for the input specification. This marks the beginning of a transition away from the ZMQ contribution protocol towards the standard EDI, including EDI-over-TCP. Move code shared with other mmbTools into a separate `common` repository. Change default TAI clock cache storage location to `/var/tmp/odr-leap-seconds.cache`. Add encoder version and uptime to stats if available (carried over EDI, not ZMQ). Fix nonblocking file input. Modify ODR-zmq2edi and ODR-zmq2farsync timeout behaviour. 2019-12-20: Matthias P. Braendli (v2.4.2): Backport the file input nonblock fix to v2.4.1. 2019-06-25: Matthias P. Braendli (v2.4.1): Fix bug when odr-zmq2edi is used with more than one destination. 2019-06-07: Matthias P. Braendli (v2.4.0): Add timestamp offset setting, and fix bug due to UTCO offset not being properly applied. See doc/TIMESTAMPS.rst for more details. Implement FIG2 labels. Fix bug for DAB MP2 in EEP_A. Fix FIG0/24 for DAB+ services. ODR-ZMQ2EDI: fix MNSC endianness swap bug. Pull some RC changes from ODR-DabMod. Implement EDI-over-TCP output. 2019-01-07: Matthias P. Braendli (v2.3.1): Happy new year! Fix TAI bulletin parsing on systems where long is 32 bit. Add configurable URLs to download TAI clock bulletin and rework the download logic. Do not read TAI clock cache file from /tmp anymore, only use /var/tmp. 2018-10-30: Matthias P. Braendli (v2.3.0): Correct FIG0/6 output for services with different ECC. Add support for transmitting empty Linkage Sets. Save leap-second cache to /var/tmp, which doesn't get cleared across reboots. Rework audio level measurement in the statistics server to alleviate some timing-related issues. odr-zmq2edi: Add option to drop late packets. 2018-07-31: Matthias P. Braendli (v2.2.0): Fix delays and FIG repetition burst on announcement switching. Add support for services with different ECC (Signalled in FIG0/9). Improve ODR-ZMQ2EDI statistics. 2018-05-29: Matthias P. Braendli (v2.1.2): A couple of internal changes and fixes for compilation warnings. 2018-03-27: Matthias P. Braendli (v2.1.1): Fix segmentation fault in peak audio statistics calculation. 2018-03-23: Matthias P. Braendli (v2.1.0): Add version string to management server. Introduce slow audio peak statistic. Avoid doing useless copies in the RAW output. 2018-03-01: Matthias P. Braendli (v2.0.0): Configuration file syntax changed for Frequency Information and Other Services settings. Statistics server changes: audio-level peak information is measured over the last second, and not anymore since the last request. Same for buffers, where min/max are measured over thirty seconds. Make nonblock available again for file inputs. 2018-02-09: Matthias P. Braendli (v1.3.3): Add dynamic/static PTy setting to configuration. Add UTF-8 to EBU Latin character set converter for labels. Show TAI bulletin expiration in RC. Stop using SUBDIRS in automake, and build the executable in ./ and not in ./src anymore. 2018-01-23: Matthias P. Braendli (v1.3.2): Add clock selection to RAW output. zmq2edi: intentionnally crash after timeout. Add command "state" to the monitoring interface and add to Munin script. Fix TAI bulletin download failure handling. Improve portability for non-Linux POSIX systems, and improve autoconf checks. Default international-table to 1. 2017-08-23: Matthias P. Braendli (v1.3.1): Add retords.pl script for Xymon monitoring tool. Add odr-zmq2edi tool, and add necessary metadata to the ZMQ output of ODR-DabMux. The configuration file was extended in a backward-compatible way. Handling of the TAI clock subsystem has been adapted too. 2017-06-30: Matthias P. Braendli (v1.3.0): Add support for FIG0/24 to announce that a service is present in another ensemble. This completes the implementation of all service linking FIGs according to ETSI TS 103 176 Figure 1. Allow EDI packet lengths up to 1400 bytes. Add local cache for the leap second information downloaded from IETF, remove download from USNO servers, as they don't give validity information. 2017-06-05: Matthias P. Braendli (v1.2.2): Add support for FIG0/21 "Frequency Information". Improve EDI fragment interleaver. Avoid stalling the multiplexer when the TCP output is used. 2017-01-29: Matthias P. Braendli (v1.2.1): Bugfix for illegal memory access in EDI PFT layer. Add experimental STI-D(PI, X)/RTP input. 2017-01-24: Matthias P. Braendli (v1.2.0): Remove the libfec dependency. Adapt several FIGs to EN 300 401 V2.1.1. Add FIG0/6 for service linking information. Big overhaul of all inputs. TCP output improvements, stability and handling of multiple connections. Fix ZeroMQ input encryption for MPEG inputs. Rework PRBS source and describe it better. Remove unused bridge, SLIP, Test and nonfunctional DMB input. Add fragment interleaver for the EDI output. Fix important issue when both EDI and TIST are enabled. Enable syslog logger earlier. 2016-09-02: Matthias P. Braendli (v1.1.0): Switch FIG0/10 to long form, the latest draft spec marks the short form as legacy. Add RC support for deferred triggering of announcements, see doc/remote_control.txt 2016-07-30: Matthias P. Braendli (v1.0.0): Remove old carousel. Fix FIGs 0/2 and 0/8 when data services present. Fix parsing of TAI-UTC bulletin for EDI TIST. ZeroMQ is now a mandatory dependency because of the ManagementServer. Minor bugfixes and improvements. 2016-03-26: Matthias P. Braendli (v0.9.0): Enable new carousel by default. Integrate munin script improvements. EDI bugfix when many CU are used. EDI: add support for PFT with FEC=0 (only fragmentation). EDI: backward-incompatible configuration change. EDI: transmit timestamp with TAI vs UTC correction (experimental). Get compilation working under FreeBSD and OSX. Add ability to specify source and TTL for UDP output. Split example.mux into a simple and a more advanced example. 2015-11-27: Matthias P. Braendli (v0.8.1): Fix compilation with test input. Add PTy to remote control. Add EDI options to configuration file for multicast settings. 2015-09-13: Matthias P. Braendli (v0.8.0): Pad labels with spaces instead of terminating them with NUL. Modify real-time priority. Refactor big mux loop into separate function. Add ability to read out full configuration tree in JSON. Remove old command-line interface. Fix EDI errors. Replace TCP socket for Statistics and Management server by ZMQ REP/REQ. Start activities on a simple web GUI for monitoring and configuration. Add new FIG carousel, not enabled by default. Add utility to drive a FarSync card from a ZMQ ETI stream. Improve FIG0/13 signalling (DG flag, CAOrg removal) Add announcement support (FIG0/18 and 0/19) 2015-07-27: Matthias P. Braendli (v0.7.5): Fix segmentation fault in short label check logic. 2015-07-03: Matthias P. Braendli (v0.7.4): Allow empty component labels Fix compilation with ZeroMQ 4.1.x 2015-04-10: Matthias P. Braendli (v0.7.3): Security: RC only listens on localhost. Change FIG signalisation handling for User Application Type, which has an impact on Slideshow. -e parameter becomes optional. Improve error messages and documentation. Start some activities for a web-based GUI. 2014-09-26: Matthias P. Braendli (v0.7.2): Fix a memory leak with the ZMQ input. Fix handling of timestamps when using ZMQ input. Add the experimental EDI output. Support new ZeroMQ frame format also for toolame-dab. Update FarSync driver to latest version. When used without configuration file, LTO defaults to auto, TM defaults to 1. Add protection profile option to configuration. 2014-05-20: Matthias P. Braendli (v0.7.1): This is a bugfix release, with a minor update to the input state server. * odr-dabmux: Fix -r command line option. Correct internal allocation/deallocation mistakes. Add "Silent" state to input states. Little changes in munin graph script. 2014-04-25: Matthias P. Braendli (v0.7.0): This version supports a new ZeroMQ frame format. The old format is still used by toolame-dab, and by older versions of fdk-aac-dabplus, and will stay supported. * odr-dabmux: Add local-time-offset to remote control, and support 'auto' setting in configuration file. Update the munin script. Support the new ZeroMQ framing format. Support ZeroMQ CURVE authentication. Make ZeroMQ buffering better configurable. Include peak audio level in munin statistics. 2014-04-04: Matthias P. Braendli (v0.6.0): Adds support for MOT Slideshow and DLS, compatible with mot-encoder from fdk-aac-dabplus * odr-dabmux: Add FIG0/13 signalling for MOT slideshow Add local-time-offset and intl table options Fix some unseen compilation warnings Change ZMQ output format to guarantee frame alignment (not backward compatible) 2014-02-14: Matthias P. Braendli (v0.5.0): * odr-dabmux: Service and component labels can now be changed on the fly using the remote control. ZMQ input buffer size can be changed using the RC. Modify the RC 'list' command to simplify usage. Clean up the startup configuration dump. Added fault checking logic to RC that can restart it in case of a failure. Add doc/remote_control.txt 2014-02-12: Matthias P. Braendli (v0.4.3): * odr-dabmux: Add support for hexadecimal IDs in configuration file. Add ZMQ input for toolame-dab. 2014-02-11: Matthias P. Braendli (v0.4.2) * odr-dabmux: Actually add zmq.hpp to Makefile.am (v0.4.1) * odr-dabmux: Include zmq.hpp locally and prefer it over the system one. Fix wrong usage of zmq::socket_t.recv 2014-02-07: Matthias P. Braendli (v0.4.0) * odr-dabmux: CRC-DabMux renamed to ODR-DabMux Version bump to 0.4.0 2014-01-31: Matthias P. Braendli (tag r12): * crc-dabmux: Create new object-oriented abstraction for the inputs, with a wrapper for all existing inputs Adapt inputZMQ to the new internal interface Add a telnet Remote Control interface 2014-01-31: Matthias P. Braendli (tag r11): * crc-dabmux: Replace TCPLog by Logger that supports syslog Add TCP statistics server Make ZMQ input publish buffer statistics Add Munin helper script to graph ZMQ input stats Fix ZMQ include when inputzmq disabled 2014-01-05: Matthias P. Braendli (tag r10): * crc-dabmux: Add ZeroMQ input, compatible with fdk-aac-dabplus-zmq Replace CRC-DabMux-cfg with -e option 2013-12-14: Matthias P. Braendli (tag r9): * autotools: Remove autogenerated files and add bootstrap Fix faulty logic for --enable-output-zeromq Update version generation for git 2013-11-10: Matthias P. Braendli (tag r8): * crc-dabmux: Some configuration parsing and logging fixes. Replaced the C-style function dispatching for the outputs by C++ objects with inheritance. Added support for ZeroMQ ETI output. 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-dabmux/src/*: Added shortlabel support to configuration file Added Boost version check into autoconf 2012-08-26: Matthias P. Braendli (tag r6): * crc-dabmux/src/DabMux.h: Added missing file DabMux.h 2012-08-23: Matthias P. Braendli (tag r5): * crc-dabmux/*: Refactoring command line handling for crc-dabmux, added configuration file parser including example. When the program is called as CRC-DabMux-cfg, it reads the Ensemble definition from a file rather than from the command line. An example file is in doc/ DabMux now depends on Boost. 2012-08-17: Matthias P. Braendli (tags r4, r3, r2 and r1 were relevant to crc-dabmod in the old repository) 2011-05-24 Pascal Charest * src/DabMux.cpp: Changed for more precise messages. * src/DabMux.cpp: Added ECC support. * src/dabInputFile.cpp: Removed unused macros, which solved unitialised bug. * src/dabInputPacketFile.cpp: Removed unused macros, which solved unitialised bug. 2010-08-23 Pascal Charest * src/DabMux.cpp: Added UDP input for data subchannel (bug). 2010-06-24 Pascal Charest * src/DabMux.cpp: Added support for timestamp. 2010-06-17 Pascal Charest * src/DabMux.cpp: Solved FIC date bug. Copyright (C) 2010,2011 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-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 . Opendigitalradio-ODR-DabMux-29c710c/INSTALL.md000066400000000000000000000064111476627344300205740ustar00rootroot00000000000000You have 3 ways to install odr-dabmux on your host: # Using your linux distribution packaging system `odr-dabmux` 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 debian package does not include the Mux Web Management GUI # Using installation scripts If your linux distribution is debian-based, you can install odr-dabmux 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-dabmux 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 ## ZeroMQ sudo apt-get install --yes libzmq3-dev libzmq5 ## Boost 1.48 or later sudo apt-get install --yes libboost-system-dev # optional packages ## cURL to download the TAI-UTC bulletin, needed for timestamps in EDI output sudo apt-get install --yes libcurl4-openssl-dev ``` ### Dependencies on other linux distributions For CentOS, in addition to the packages needed to install a compiler, install the packages: boost-devel libcurl-devel zeromq-devel Third-party RPM packages are maintained by RaBe, and are built by the [openSUSE Build Service](https://build.opensuse.org/project/show/home:radiorabe:dab). For questions regarding these packages, please get in touch with the maintainer of the [radio RaBe repository](https://github.com/radiorabe/). For openSUSE, mnhauke is maintaining packages, also built using [OBS](https://build.opensuse.org/project/show/home:mnhauke:ODR-mmbTools). ## Compilation The *master* branch in the repository always points to the latest release. If you are looking for a new feature or bug-fix that did not yet make its way into a release, you can clone the *next* branch from the repository. 1. Clone this repository: ``` # stable version: git clone https://github.com/Opendigitalradio/ODR-DabMux.git # or development version (at your own risk): git clone https://github.com/Opendigitalradio/ODR-DabMux.git -b next ``` 1. Configure the project ``` cd ODR-DabMux ./bootstrap ./configure ``` 1. Compile and install: ``` make sudo make install ``` Notes: - It is advised to run the bootstrap and configure steps again every time you pull updates from the repository. - The configure script can be launched with a variety of options. Run `./configure --help` to display a complete list # Develop on OSX and FreeBSD If you want to develop on OSX platform install the necessary build tools and dependencies with brew brew install boost zeromq automake curl On FreeBSD, pkg installs all dependencies to /usr/local, but the build tools will not search there by default. Set the following environment variables before calling ./configure LDFLAGS="-L/usr/local/lib" CFLAGS="-I/usr/local/include" CXXFLAGS="-I/usr/local/include" On both systems, RAW output is not available. Note that these systems are not tested regularly. Opendigitalradio-ODR-DabMux-29c710c/LICENCE000066400000000000000000000024571476627344300201370ustar00rootroot00000000000000LICENSING ========= Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, http://www.opendigitalradio.org This file is part of ODR-DabMux. ODR-DabMux is a fork of CRC-DabMux, which was developed by the Communications Research Center Canada. 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 . KA9Q FEC routines ----------------- lib/fec/ contains code from KA9Q's fec library. Please see lib/fec/README.md and lib/fec/LICENSING Other parts ----------- lib/edi/PFT.{hpp,cpp} are APACHE 2.0 licensed. lib/charset contains a BSD-licensed UTF-8 library. lib/zmq.hpp is BSD-licensed. The farsync driver in lib/farsync is GPLv2+ licensed. Opendigitalradio-ODR-DabMux-29c710c/Makefile.am000066400000000000000000000146161476627344300212060ustar00rootroot00000000000000# Copyright (C) 2008, 2009 Her Majesty the Queen in Right of Canada # (Communications Research Center Canada) # # Copyright (C) 2024 Matthias P. Braendli # http://opendigitalradio.org # 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 . if IS_GIT_REPO GITVERSION_FLAGS = -DGITVERSION="\"`git describe --dirty`\"" else GITVERSION_FLAGS = endif bin_PROGRAMS=odr-dabmux zmqinput-keygen if HAVE_OUTPUT_RAW_TEST bin_PROGRAMS+=odr-zmq2farsync endif FARSYNC_DIR=lib/farsync/linux INCLUDE=-I$(FARSYNC_DIR) -Ilib/charset -Ilib -Isrc lib_fec_sources = lib/fec/char.h \ lib/fec/rs-common.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_charset_sources = lib/charset/charset.cpp \ lib/charset/charset.h \ lib/charset/utf8/checked.h \ lib/charset/utf8/core.h \ lib/charset/utf8/unchecked.h \ lib/charset/utf8/cpp11.h \ lib/charset/utf8/cpp17.h \ lib/charset/utf8/cpp20.h \ lib/charset/utf8.h odr_dabmux_CFLAGS =-Wall $(INCLUDE) $(PTHREAD_CFLAGS) $(GITVERSION_FLAGS) odr_dabmux_CXXFLAGS =-Wall $(PTHREAD_CXXFLAGS) $(INCLUDE) $(GITVERSION_FLAGS) $(BOOST_CPPFLAGS) $(ZMQ_CPPFLAGS) odr_dabmux_LDADD =$(ZMQ_LIBS) $(BOOST_LDFLAGS) \ $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) $(BOOST_SYSTEM_LIB) odr_dabmux_SOURCES =src/DabMux.cpp \ src/DabMux.h \ src/DabMultiplexer.cpp \ src/DabMultiplexer.h \ src/input/inputs.h \ src/input/Prbs.cpp \ src/input/Prbs.h \ src/input/Zmq.cpp \ src/input/Zmq.h \ src/input/File.cpp \ src/input/File.h \ src/input/Udp.cpp \ src/input/Udp.h \ src/input/Edi.cpp \ src/input/Edi.h \ src/dabOutput/dabOutput.h \ src/dabOutput/dabOutputFile.cpp \ src/dabOutput/dabOutputFifo.cpp \ src/dabOutput/dabOutputRaw.cpp \ src/dabOutput/dabOutputSimul.cpp \ src/dabOutput/dabOutputTcp.cpp \ src/dabOutput/dabOutputUdp.cpp \ src/dabOutput/dabOutputZMQ.cpp \ src/dabOutput/metadata.h \ src/dabOutput/metadata.cpp \ src/ConfigParser.cpp \ src/ConfigParser.h \ src/Eti.h \ src/Eti.cpp \ src/ManagementServer.h \ src/ManagementServer.cpp \ src/MuxElements.cpp \ src/MuxElements.h \ src/PcDebug.h \ src/fig/FIG.h \ src/fig/FIG.cpp \ src/fig/FIG0.h \ src/fig/FIG0structs.h \ src/fig/FIG0_0.cpp \ src/fig/FIG0_0.h \ src/fig/FIG0_1.cpp \ src/fig/FIG0_1.h \ src/fig/FIG0_2.cpp \ src/fig/FIG0_2.h \ src/fig/FIG0_3.cpp \ src/fig/FIG0_3.h \ src/fig/FIG0_5.cpp \ src/fig/FIG0_5.h \ src/fig/FIG0_6.cpp \ src/fig/FIG0_6.h \ src/fig/FIG0_7.cpp \ src/fig/FIG0_7.h \ src/fig/FIG0_8.cpp \ src/fig/FIG0_8.h \ src/fig/FIG0_9.cpp \ src/fig/FIG0_9.h \ src/fig/FIG0_10.cpp \ src/fig/FIG0_10.h \ src/fig/FIG0_13.cpp \ src/fig/FIG0_13.h \ src/fig/FIG0_14.cpp \ src/fig/FIG0_14.h \ src/fig/FIG0_17.cpp \ src/fig/FIG0_17.h \ src/fig/FIG0_18.cpp \ src/fig/FIG0_18.h \ src/fig/FIG0_19.cpp \ src/fig/FIG0_19.h \ src/fig/FIG0_21.cpp \ src/fig/FIG0_21.h \ src/fig/FIG0_24.cpp \ src/fig/FIG0_24.h \ src/fig/FIG1.cpp \ src/fig/FIG1.h \ src/fig/FIG2.cpp \ src/fig/FIG2.h \ src/fig/FIGCarousel.cpp \ src/fig/FIGCarousel.h \ src/fig/TransitionHandler.h \ src/mpeg.h \ src/mpeg.c \ src/PrbsGenerator.cpp \ src/PrbsGenerator.h \ src/utils.cpp \ src/utils.h \ lib/crc.h \ lib/crc.c \ lib/ClockTAI.h \ lib/ClockTAI.cpp \ lib/Globals.cpp \ lib/Log.h \ lib/Log.cpp \ lib/RemoteControl.cpp \ lib/RemoteControl.h \ lib/Json.h \ lib/Json.cpp \ lib/edi/STIDecoder.cpp \ lib/edi/STIDecoder.hpp \ lib/edi/STIWriter.cpp \ lib/edi/STIWriter.hpp \ lib/edi/PFT.cpp \ lib/edi/PFT.hpp \ lib/edi/common.cpp \ lib/edi/common.hpp \ lib/edi/buffer_unpack.hpp \ lib/edioutput/AFPacket.cpp \ lib/edioutput/AFPacket.h \ lib/edioutput/EDIConfig.h \ lib/edioutput/PFT.cpp \ lib/edioutput/PFT.h \ lib/edioutput/TagItems.cpp \ lib/edioutput/TagItems.h \ lib/edioutput/TagPacket.cpp \ lib/edioutput/TagPacket.h \ lib/edioutput/Transport.cpp \ lib/edioutput/Transport.h \ lib/ReedSolomon.h \ lib/ReedSolomon.cpp \ lib/Socket.h \ lib/Socket.cpp \ lib/ThreadsafeQueue.h \ lib/zmq.hpp \ $(lib_fec_sources) \ $(lib_charset_sources) zmqinput_keygen_SOURCES = src/zmqinput-keygen.c zmqinput_keygen_LDADD = $(ZMQ_LIBS) zmqinput_keygen_CFLAGS = -Wall $(GITVERSION_FLAGS) $(ZMQ_CPPFLAGS) odr_zmq2farsync_SOURCES = src/zmq2farsync/zmq2farsync.cpp \ src/dabOutput/dabOutput.h \ src/dabOutput/dabOutputRaw.cpp \ lib/Globals.cpp \ lib/Log.h \ lib/Log.cpp \ lib/RemoteControl.cpp \ lib/RemoteControl.h \ lib/Json.h \ lib/Json.cpp \ lib/Socket.h \ lib/Socket.cpp \ lib/zmq.hpp odr_zmq2farsync_LDADD = $(ZMQ_LIBS) odr_zmq2farsync_CFLAGS = -Wall $(ZMQ_CPPFLAGS) $(PTHREAD_CFLAGS) $(GITVERSION_FLAGS) $(INCLUDE) odr_zmq2farsync_CXXFLAGS = -Wall $(PTHREAD_CFLAGS) $(PTHREAD_LIBS) $(ZMQ_CPPFLAGS) $(GITVERSION_FLAGS) $(INCLUDE) man_MANS = man/odr-dabmux.1 EXTRA_DIST = COPYING NEWS README.md INSTALL.md LICENCE AUTHORS ChangeLog TODO.md doc \ lib/fec/README.md lib/fec/LICENSE \ lib/farsync/linux lib/farsync/windows \ lib/charset/README Opendigitalradio-ODR-DabMux-29c710c/NEWS000066400000000000000000000000001476627344300176270ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/README.md000066400000000000000000000053661476627344300204330ustar00rootroot00000000000000Overview ======== ODR-DabMux is a *DAB (Digital Audio Broadcasting) multiplexer* compliant to ETSI EN 300 401. It is the continuation of the work started by the Communications Research Center Canada on CRC-DabMux, and is now pursued in the [Opendigitalradio project](http://opendigitalradio.org). ODR-DabMux 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 of ODR-DabMux: - Standards-compliant DAB multiplexer - Configuration file, see doc/example.mux and doc/example.json - Timestamping support required for SFN - Logging to syslog - Monitoring using munin tool - Includes a Telnet and ZMQ Remote Control for setting/getting parameters - EDI input and output, both over UDP and TCP - Support for FarSync TE1 and TE1e cards (G.703) - Something that will (with your help?) one day become a nice GUI for configuration, see `gui/README.md` - Experimental STI-D(PI, X)/RTP input intended to be compatible with compliant encoders. - ZeroMQ and TCP ETI outputs that can be used with ODR-DabMod - ZeroMQ input that can be used with ODR-AudioEnc which supports CURVE authentication Additional tools: `odr-zmq2farsync`, a tool that can drive a FarSync card from a ZeroMQ ETI stream. The `src/` directory contains the source code of ODR-DabMux and the additional tools. The `doc/` directory contains the ODR-DabMux documentation, a few example configuration files, and the munin and xymon scripts for the statistics server. The `lib/` directory contains source code of libraries needed to build ODR-DabMux. Up to v4.5, this repository also contained `odr-zmq2edi`, a tool that can convert a ZeroMQ ETI stream to an EDI or ZMQ stream. This was superseded by `digris-zmq-converter` in the [digris-edi-zmq-bridge](https://github.com/digris/digris-edi-zmq-bridge) repository. Install ======= See `INSTALL.md` file for installation instructions. Licence ======= See the files `LICENCE` and `COPYING` Contributions and Contact ========================= Contributions to this tool are welcome, you can reach users and developers through the [ODR-mmbTools group](https://groups.io/g/odr-mmbtools) or any other channels mentioned on the ODR website. There is a list of ideas and thoughts about new possible features and improvements in the `TODO.md` file. Developed by: Matthias P. Braendli *matthias [at] mpb [dot] li* Pascal Charest *pascal [dot] charest [at] crc [dot] ca* Acknowledgements ================ David Lutton, Yoann Queret, Stefan Pöschel and Maik for bug-fix patches, Wim Nelis for the Xymon monitoring scripts, and many more for feedback and bug reports. - [http://opendigitalradio.org/](http://opendigitalradio.org/) Opendigitalradio-ODR-DabMux-29c710c/TODO.md000066400000000000000000000031171476627344300202330ustar00rootroot00000000000000This TODO file lists ideas and features for future developments. They are more or less ordered according to their benefit, but that is subjective to some degree. Unless written, no activity has been started on the topics. Explicit Service Linking ------------------------ It is impossible to activate/deactive linkage sets. Commit 5c3c6d7 added some code to transmit a FIG0/6 CEI, but this was subsequently reverted because it was not tested enough. Inputs for packet data ---------------------- It is currently unclear what input formats and sources work for packet data, and which ones would make sense to add. Also, there is no documentation on the possibilites of packet data. Improvements for inputs ----------------------- Add statistics to UDP input, in a similar way that ZeroMQ offers statistics. This would mean we have to move the packet buffer from the operating system into our own buffer, so that we can actually get the statistics. Fix DMB input ------------- The code that does interleaving and reed-solomon encoding for DMB is not used anymore, and is untested. The relevant parts are `src/dabInputDmb*` and `src/Dmb.cpp` Communicate Leap Seconds ------------------------ Actually, we're supposed to say in FIG0/10 when there is a UTC leap second upcoming, but since that's not trivial to find out because the POSIX time concept is totally unaware of that, this is not done. We need to know for EDI TIST, and the ClockTAI class can get the information from the Internet, but it is not used in FIG0/10. Implement FIG0/20 Service List ------------------------------ See ETSI TS 103 176 Opendigitalradio-ODR-DabMux-29c710c/bootstrap.sh000077500000000000000000000001161476627344300215140ustar00rootroot00000000000000#! /bin/sh autoreconf --install && \ echo "You can call ./configure now" Opendigitalradio-ODR-DabMux-29c710c/configure.ac000066400000000000000000000143521476627344300214350ustar00rootroot00000000000000# Copyright (C) 2008, 2009 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-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 . AC_PREREQ([2.69]) AC_INIT([ODR-DabMux],[5.1.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/DabMux.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) # Checks for libraries. AX_PTHREAD([], AC_MSG_ERROR([requires pthread])) AX_BOOST_BASE([1.48.0], [], AC_MSG_ERROR([BOOST 1.48 or later is required])) AX_BOOST_SYSTEM # Checks for header files. AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h sys/ioctl.h sys/socket.h sys/time.h unistd.h]) # Options # Outputs # FILE AC_ARG_ENABLE([output_file], [AS_HELP_STRING([--disable-output-file], [Disable FILE output])], [], [enable_output_file=yes]) AS_IF([test "x$enable_output_file" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_FILE, [1], [Define if FILE output is enabled])]) # FIFO AC_ARG_ENABLE([output_fifo], [AS_HELP_STRING([--disable-output-fifo], [Disable FIFO output])], [], [enable_output_fifo=yes]) AS_IF([test "x$enable_output_fifo" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_FIFO, [1], [Define if FIFO output is enabled])]) # UDP AC_ARG_ENABLE([output_udp], [AS_HELP_STRING([--disable-output-udp], [Disable UDP output])], [], [enable_output_udp=yes]) AS_IF([test "x$enable_output_udp" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_UDP, [1], [Define if UDP output is enabled])]) # TCP AC_ARG_ENABLE([output_tcp], [AS_HELP_STRING([--disable-output-tcp], [Disable TCP output])], [], [enable_output_tcp=yes]) AS_IF([test "x$enable_output_tcp" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_TCP, [1], [Define if TCP output is enabled])]) # RAW AC_ARG_ENABLE([output_raw], AS_HELP_STRING([--enable-output-raw], [Enable RAW output])) AS_IF([test "x$enable_output_raw" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_RAW, [1], [Define if RAW output is enabled])]) # SIMUL AC_ARG_ENABLE([output_simul], [AS_HELP_STRING([--disable-output-simul], [Disable SIMUL output])], [], [enable_output_simul=yes]) AS_IF([test "x$enable_output_simul" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_SIMUL, [1], [Define if SIMUL output is enabled])]) # EDI and ZMQ output metadata require TAI-UTC offset, which requires downloading the IETF TAI bulletin AC_CHECK_LIB(curl, curl_easy_init) have_curl=$ac_cv_lib_curl_curl_easy_init AS_IF([test "x$have_curl" = "xyes"], [AC_DEFINE(HAVE_CURL, [1], [Define if cURL is available])]) AS_IF([test "x$have_curl" = "xno"], [AC_MSG_WARN([cURL not found, timestamps will not work])]) AC_LANG_PUSH([C++]) AX_CHECK_COMPILE_FLAG([-Wno-maybe-uninitialized], [CXXFLAGS="$CXXFLAGS -Wno-maybe-uninitialized"], [], ["-Werror"]) 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([-Wdouble-promotion], [CXXFLAGS="$CXXFLAGS -Wdouble-promotion"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG(["-Wformat=2"], [CXXFLAGS="$CXXFLAGS -Wformat=2"], [], ["-Werror"]) # 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) ]) AC_LANG_POP([C++]) # ZeroMQ AX_ZMQ([4.0.0], [], AC_MSG_ERROR(ZeroMQ 4.0.0 is required)) AC_DEFINE([HAVE_INPUT_ZEROMQ], [1], [Define if ZeroMQ input is enabled]) AC_DEFINE([HAVE_OUTPUT_ZEROMQ], [1], [Define if ZeroMQ output is enabled]) AC_DEFINE([HAVE_ZEROMQ], [1], [Define if ZeroMQ enabled for rc]) # Do not build odr-zmq2farsync if no RAW output AM_CONDITIONAL([HAVE_OUTPUT_RAW_TEST], [test "x$enable_output_raw" = "xyes"]) AM_CONDITIONAL([IS_GIT_REPO], [test -d '.git']) AC_CONFIG_FILES([Makefile]) AC_OUTPUT echo echo "***********************************************" echo echo "ZeroMQ management server enabled." echo echo "Inputs: prbs udp zmq fifo file" echo echo "Formats: raw mpeg packet dabplus dmb epm" echo echo "Outputs:" enabled="" disabled="" for output in file fifo udp tcp raw simul do eval var=\$enable_output_$output AS_IF([test "x$var" = "xyes"], [enabled="$enabled $output"], [disabled="$disabled $output"]) done echo " Enabled: $enabled zmq" echo " Disabled: $disabled" if test "$have_curl" = "no" ; then echo echo "WARNING! cURL not found: ODR-DabMux will not support timestamps" echo fi echo echo "***********************************************" echo Opendigitalradio-ODR-DabMux-29c710c/doc/000077500000000000000000000000001476627344300177075ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/doc/README.md000066400000000000000000000061621476627344300211730ustar00rootroot00000000000000Description =========== ODR-DabMux is a software multiplexer that generates an ETI stream from audio and data streams. Because of its software based architecture, many typical DAB services can be generated and multiplexed on a single PC platform with live or pre-recorded sources. A DAB multiplex configuration is composed of one ensemble. An ensemble is the entity that receivers tune to and process. An ensemble contains several services. A service is the listener-selectable output. Each service contains one mandatory service component which is called primary component. An audio primary component define a program service while a data primary component define a data service. Service can contain additional components which are called secondary components. Maximum total number of components is 12 for program services and 11 for data services. A service component is a link to one subchannel (or Fast Information Data Channel). A subchannel is the physical space used within the common interleaved frame. __________________________________________________ ENSEMBLE | ODR-Ensemble | |__________________________________________________| | | | | | | _______V______ _______V______ _______V______ SERVICES | ODR-Service1 | | ODR-Service2 | | ODR-Service3 | |______________| |______________| |______________| | | | | |______ | | | | | | | __V__ __V__ __V__ __V__ __V__ __V__ SERVICE | SC1 | | SC2 | | SC3 | | SC4 | | SC5 | | SC6 | COMPONENTS |_____| |_____| |_____| |_____| |_____| |_____| | | _____| | | ____| | | | | | | __________ __V________V__V______________V________V___V_______ | MCI | SI | | SubCh1 | SubCh9 | ... | SubCh3 | SubCh60 | ... | |_____|____| |________|________|_______|________|_________|_____| Fast Information Ch. Main Service Channel COMMON INTERLEAVED FRAME Files in this folder ==================== The configuration is given in a file, this folder contains examples. A basic example is in the file *example.mux*, a more complete view of the settings is available in *advanced.mux* and configuration settings related to service linking are shown in *servicelinking.mux* An explanation on how to use the remote control is in *remote_control.txt*, and *zmq_remote.py* illustrates how to control ODR-DabMux using the ZMQ remote control interface. Two scripts are used for monitoring systems: *stats_dabmux_munin.py* for Munin, and *retodrs.pl* for Xymon. You can use *show_dabmux_stats.py* to print the statistics to console. The values are described in *STATS.md* *DabMux.1* is an old manpage that describes the command line options that existed in past versions. It is kept for archive. Opendigitalradio-ODR-DabMux-29c710c/doc/STATS.md000066400000000000000000000024471476627344300211360ustar00rootroot00000000000000Stats available through Management Server ========================================= Interface --------- The management server makes statistics about the inputs and EDI/TCP outputs available through a ZMQ request/reply socket. The `show_dabmux_stats.py` illustrates how to access this information. Meaning of values for inputs ---------------------------- `max` and `min` indicate input buffer fullness in bytes. `under` and `over` count the number of buffer underruns and overruns. `audio L` and `audio R` show the maximum audio level in dBFS over the last 500ms. `peak L` and `audio R` show the max audio level in dBFS over the last 5 minutes. The audio levels are measured in the audio encoder and carried in the EDI `ODRa` TAG, or in the ZMQ metadata. Otherwise ODR-DabMux would have to decode all audio contributions to measure the audio level. `State` is either NoData, Unstable, Silence, Streaming. Unstable means that underruns or overruns have occurred in the previous 30 minutes. Silence means the stream is working, but audio levels are always below -50dBFS. `version` and `uptime` are fields directly coming from the contribution source, and are only supported for the EDI input. These are carried over EDI using custom TAG `ODRv` (see function `parse_odr_version_data` in `lib/edi/common.cpp`). Opendigitalradio-ODR-DabMux-29c710c/doc/TIMESTAMPS.rst000066400000000000000000000045311476627344300221320ustar00rootroot00000000000000Some knowledge accumulated about timestamping ============================================= The meaning of the timestamps changed between v2.3.1 and v3.0.0, this document gives some guidance about the interaction between different settings. The following table tries to summarise the differences. +-----------------------------+----------------------------------------------+-------------------------------------+-----------------------------------------------+ | ODR-DabMux version | Meaning of timestamp inside EDI | ODR-ZMQ2EDI wait time w | Offset that should be set in the mod | +=============================+==============================================+=====================================+===============================================+ | Up to and including v2.3.1 | t_frame = t_mux (No offset in mux available) | positive, meaning delay after t_mux | Something larger than w + mod processing time | +-----------------------------+----------------------------------------------+-------------------------------------+-----------------------------------------------+ | Later than v2.3.1 | t_frame = t_tx = t_mux + tist_offset | negative, meaning delay before t_tx | Something larger than mod processing time | +-----------------------------+----------------------------------------------+-------------------------------------+-----------------------------------------------+ The edilib tool decodes both EDI timestamp and MNSC, and can be used to verify both are identical. Issues in ODR-DabMux v2.3.1 --------------------------- Running ODR-DabMux against the absolute timestamp firmware has uncovered a few issues: * At startup, the UTCO was not properly applied to the EDI seconds. This offset was 5 seconds (TAI-UTC offset - 32s, see EDI spec); * odr-zmq2edi did not compensate for UTCO, hiding the above issue; * ODR-DabMux needs a configurable offset; * (minor) MNSC and EDI timestamps did not use the same internal representation, making it difficult to prove that they encode the same value; * (minor) odr-zmq2edi swapped endianness when regenerating EDI from ETI (minor because only ODR-DabMod considers MNSC, and usually isn't used with EDI); **Important** Do not combine odr-zmq2edi with odr-dabmux of a different version! Do not combine digris-zmq-converter with odr-dabmux older than v4! Opendigitalradio-ODR-DabMux-29c710c/doc/advanced.mux000066400000000000000000000476041476627344300222220ustar00rootroot00000000000000; This is an advanced configuration example for ODR-DabMux, ; that documents more options that the simple example.mux ; More information about the usage of the tools is available ; in the guide, which can be found on the ; www.opendigitalradio.org website. ; ; The format is called INFO format, and defined by boost property_tree: ; http://www.boost.org/doc/libs/1_41_0/doc/html/boost_propertytree/parsers.html#boost_propertytree.parsers.info_parser ; It consists of six mandatory sections, whose relative order in this ; file are of no importance. ; The general section defines global multiplex parameters. general { ; the DAB Transmission mode (values 1-4 accepted) dabmode 1 ; the number of ETI frames to generate (set to 0 to get an unlimited number) nbframes 10 ; boolean fileds can accept either false or true as values: ; Enable logging to syslog syslog false ; Write the SCCA field useful for the Factum ETI analyser writescca false ; Enable timestamp definition necessary for SFN ; This also enables time encoding using the MNSC and in EDI. tist false ; On startup, the timestamp is initialised to system time. If you want ; to add an offset, uncomment the following line and give a positive ; number in seconds. Granularity: 24ms ; tist_offset 0.480 ; Specify the TIST value for the frame with FCT==0, in microseconds ; tist_at_fct0 768000 ; The management server is a simple TCP server that can present ; statistics data (buffers, overruns, underruns, etc) ; which can then be graphed a tool like Munin ; The doc/stats_dabmux_multi.py tool is a suitable ; plugin for that. The data fields are documented in doc/STATS.md ; If the port is zero, or the line commented, the server ; is not started. managementport 12720 } remotecontrol { ; enable the telnet remote control server on the given port ; This server allows you to read and define parameters that ; some features export. It is only accessible from localhost. ; Set the port to 0 to disable the server telnetport 12721 ; the remote control server makes use of the unique identifiers ; for the subchannels, services and components. Make sure you ; chose them so that you can identify them. } ; Some ensemble parameters ensemble { ; Example for Switzerland, with country id=4 and ECC=E1 id 0x4fff ; you can also use decimal if you want ecc 0xe1 ; Extended Country Code local-time-offset auto ; autmatically calculate from system local time ; or ;local-time-offset 1 ; in hours, supports half-hour offsets international-table 1 ; See TS 101 756 clause 5.7 ; 1 corresponds to the PTy used in RDS ; 2 corresponds to program types used in north america ; Enable FIG0/7, which specifies that the ensemble is compliant to EN 300 401 version 2. ; For more options see doc/advanced.mux reconfig-counter hash ; all labels are maximum 16 characters in length label "OpenDigitalRadio" ; The short label is built from the label by erasing letters, and cannot ; be longer than 8 characters. If omitted, it will be truncated from the ; label shortlabel "ODR" ; Announcement settings for FIG0/19. announcements { test_announcement { cluster 1 flags { Traffic true } subchannel sub-fu } } } ; Definition of DAB services services { ; Each service has it's own unique identifier, that is ; used throughout the configuration file and for the RC. srv-fu { id 0x4daa label "Funk" shortlabel "Fu" ; Programme Type, according to the chosen international-table above. pty 0 ; chose between static and dynamic PTy ; static means the PTy represents to overall genre of the programme. ; dynamic means the PTy follows the various items within a programme. ; Use the RC interface to modify at runtime. ; See EN 300 401 Clause 8.1.5 pty-sd static language 0 ; also supports id ; List of announcement switching flags signalled in FIG 0/18 ; This lists all possible announcements. If one is left out, it is disabled. announcements { Alarm false Traffic true Travel false Warning false News false Weather false Event false Special false ProgrammeInfo false Sports false Finance false ; a comma separated list of clusters in which the service belongs to ; cluster id 255 is not specified here and is ignored (for FIG 0/18) clusters "1,2" } } srv-ri { ; If your ensemble contains a service from another country, ; specify its ECC here. Example is for Italy, country id=5, ECC=E0 id 0x5dab ecc 0xe0 label "rick" } srv-lu { id 0x4dac label "Lu" ; pty, language, shortlabel and id can be omitted, and will take default values } } ; The subchannels are defined in the corresponding section. ; supported types are : audio, data, enhancedpacket, ; dabplus, packet ; ; Type 'packet' expects to receive data in the format described ; in EN 300 401 Clause 5.3.2. ; ; 'enhancedpacket' mode will calculate FEC for MSC packet mode ; as described in EN 300 401 Clause 5.3.5. ; ; 'data' will read from the source and write it unmodified into ; the MSC. subchannels { sub-fu { type audio bitrate 128 id 10 ; type audio subchannels automatically use ; UEP, unless the bitrate is 8, 16, 24, 40 or 144kbit/s ; (EN 300 401 Clause 6.2.1) ; this can be overridden with the option protection-profile protection-profile EEP_A ; supported options: UEP (use only for type audio!) ; EEP_A (for all bitrates) ; EEP_B (bitrates multiple of 32kbit/s) ; Set the protection level, possible values depend ; on the protection profile: ; UEP profile: 1 to 5; EEP profiles: 1 to 4 protection 4 ; example file input inputproto file inputuri "funk.mp2" nonblock false } sub-lu { type dabplus bitrate 96 id 3 protection 3 ; EXPERIMENTAL! ; Receive STI-D(LI) carried in STI(PI, X) inside RTP using UDP. ; This is intended to be compatible with AVT audio encoders. ; EXPERIMENTAL! inputproto sti inputuri "rtp://127.0.0.1:32010" } sub-udp { type dabplus bitrate 96 id 1 protection 3 inputproto edi ; Receive EDI/UDP unicast on port 32010 inputuri "udp://:32010" ; Receive EDI/UDP multicast stream on group 239.10.11.12 port 32010 ;inputuri "udp://@239.10.11.12:32010" ; Same, but specify local interface address 192.168.0.1, to select which local interface to use ;inputuri "udp://192.168.0.10@239.10.11.12:32010" } sub-ri { type dabplus bitrate 96 id 1 protection 1 ; example file input ;inputuri "rick.dabp" ; example zmq input: ; Accepts connections to port 9000 from any interface. inputproto zmq inputuri "tcp://*:9000" ; ZMQ specific options, mandatory: ; Maximum size of input buffer, in AAC frames (24ms) ; when this buffer size is reached, some frames will be ; discarded to get the size again below this value. ; As the present implementation discards entire AAC superframes, ; (5 frames = 120ms) the effect will clearly be audible. zmq-buffer 40 ; At startup or after an underrun, the buffer is filled to this ; amount of AAC frames before streaming starts. zmq-prebuffering 20 ; In an ideal scenario, where the input rate exactly corresponds ; to the rate at which the frames are consumed by dabmux, you ; see the buffer level staying around the zmq-prebuffering value. ; Network latency jitter can make it temporarily go lower or higher. ; Encoder clock drift will make the buffer either slowly fill or ; empty, which will create intermittent glitches. ; the ZMQ inputs support encryption using the CURVE method. ; The multiplexer must have a public and a private key, which ; can be shared among several zmq inputs. ; ; each encoder also has a public and private key, and the ; encoder *public* key has to be known to the multiplexer. ; Using this system, the multiplexer can be sure that ; only the encoder possessing the right secret key can ; connect here. This inhibits third parties to hijack the ; input. ; by default, it is disabled, set encryption to 1 to enable encryption true ; the multiplexer key pair. Keep these secret. secret-key "keys/mux.sec" public-key "keys/mux.pub" ; The public key from the encoder. Only the encoder you want ; to accept must know the corresponding secret key. encoder-key "keys/encoder1.pub" ; key pairs can be generated using the zmqinput-keygen tool. } ; 'prbs' will generate a pseudorandom bit sequence according to ; ETS 300 799 Clause G.2.1. This is useful for testing purposes and ; measurement of bit error rate. sub-prbs { type data bitrate 16 id 5 protection 3 ; Use the default PRBS polynomial. inputproto prbs inputuri "prbs://" ; To use another polynomial, set it in the url as hexadecimal ; The default polynomial is G(x) = x^20 + x^17 + 1, represented as ; (1 << 20) + (1 << 17) + (1 << 0) = 0x120001 ;inputuri "prbs://:0x120001 } ; An example using 'enhancedpacket' to send out epg data ; See http://wiki.opendigitalradio.org/How_to_configure_SPI_(Service_and_Programme_Information)_for_ODR-DabMux sub-data { id 6 type enhancedpacket ; Settings this flag to true makes ODR-DabMux preload the entire file contents into memory ; This allows you to replace the file contents while the current file data still gets transmitted to the ; end. load_entire_file true inputfile "./epg.dat" protection 1 bitrate 32 } } ; For now, each component links one service to one subchannel components { ; the component unique identifiers are used for the RC. comp-fu { ; specifies audio -or- packet type, defaults to zero when not given ; audio: foreground=0, background=1, multi-channel=2 ; data: unspecified=0, TMC=1, EWS=2, ITTS=3, paging=4, TDC=5, IP=59, MOT=60, proprietary=61 type 0 ; According to specification, you should not define component labels if ; the service is only used in one component. The service label is sufficient ; in that case. ;label "funk" ;shortlabel "fu" service srv-fu subchannel sub-fu ; FIG 0/13 user application was previously configured using the figtype setting, which ; allowed only a single user application. ; Now more than one user application can be defined per component, using the ; Using the 'user-applications' section, several can be defined per component. ; Do not use both figtype and user-applications. ; If both slideshow (TS 101 499) and SPI (TS 102 818) are carried in PAD, the following ; needs to be set for FIG 0/13 to be transmitted correctly. ; The same section is also applicable to packet services. user-applications { ; Add uaType 0x2 with X-PAD App Type = 12 and empty user application data userapp "slideshow" ; Add uaType 0x7 with X-PAD App Type = 16 and user application data = "Basic profile" userapp "spi" ; Broadcast website, add uaType 0x3 and user application data ; 0x01 "basic integrated receiver profile" and ; 0xFF "unrestricted profile" ; Only for packet-mode, not X-PAD. userapp "website" ; Journaline, adds uaType 0x44a and user application data 0x00 0x00 (TS 102 979 Clause 8.1.2) userapp "journaline" } ; Deprecated figtype setting: figtype 0x2 ; defines the User Application Type according to TS 101 756 Table 16: ; 0x2 : MOT Slideshow ; 0x3 : MOT Broadcast Web Site ; 0x4 : TPEG ; 0x5 : DGPS ; 0x6 : TMC ; 0x7 : EPG ; 0x8 : DAB Java ; 0x44a : Journaline ; If not defined, the FIG 0/13 is not transmitted for this component ; for packet components, set the packet address (mandatory) ;address ; Whether to use data groups ;datagroup false ; (defaults to false) ; You should normally set 'datagroup true' ; if your packet mode subchannel is transferring an MOT application such ; as SPI/EPG or Slideshow. ; If you specify the user-application "spi", FIG0/13 will set the user application data to "basic profile" } ; If a service is used in more than one component, the primary component has to ; be placed above the secondary component(s) to ensure that the SCIdS field of FIG0/8 ; is zero for the primary service component. (New in EN 300 401 V2.1.1) comp-lu { service srv-lu subchannel sub-lu user-applications { userapp "slideshow" } } comp-ri { service srv-ri subchannel sub-ri user-applications { userapp "slideshow" } } } outputs { ; The unique-id can be used by the remote control or the statistics server ; to identify the output ; Important! For real-time operation, you need to have exactly one ; output that applies back-pressure to ODR-DabMux, otherwise it will run ; at the highest possible rate on your system! ; ; For an output to a pipe, the data consumer at the other end of the pipe ; will dictate the multiplexing rate to ODR-DabMux. ; ; If you use the zmq or EDI outputs, you must also enable a simul:// output! ;supported output types for file and fifo outputs are ; raw, framed and streamed ; ; Please see doc/dab_output_formats.txt ;stdout "fifo:///dev/stdout?type=raw" ; Throttle output to real-time (one ETI frame every 24ms) throttle "simul://" edi { ; If TIST is enabled, requires leap-second information (see example.mux) destinations { ; The names you give to the destinations have no meaning, ; but have to be unique. You can give them meaningful names to help ; you identify the outputs. example_unicast { ; example for unicast EDI over UDP ; for unicast EDI, do not set source protocol udp sourceport 13000 destination "192.168.23.23" port 12000 ; For compatibility: if port is not specified in the destination itself, ; it is taken from the parent 'destinations' block. } example_multicast { ; example for multicast EDI, the source IP is required ; so that the data is sent on the correct ethernet interface protocol udp source "192.168.0.50" sourceport 13000 destination "232.20.10.1" port 12000 ; The multicast TTL has to be adapted according to your network ttl 1 } example_tcp { ; example for EDI TCP server. TCP is reliable, so it is counterproductive to ; use FEC. Using PFT also brings no benefit. protocol tcp listenport 13000 ; For every connected endpoint, a queue is created. If the queue overflows, we ; assume the endpoint has a problem, and we close the connection. This sets ; the max queue size in number of frames. With PFT disabled, one frame is generated ; every 24ms. With PFT enabled, it depends on fragmentation and FEC settings. ; ; default value: 500 frames, without PFT: 12s worth of EDI data ;max_frames_queued 500 } } ; The settings below apply to all destinations ; Enable the PFT subsystem. If false, AFPackets are sent. ; PFT is not necessary when using TCP. enable_pft false ; How many lost fragments can be recovered by Reed-Solomon. ; Requires enable_pft true. ; ; If set to 0, the PFT subsystem will only do Fragmentation and ; Transport, but no Reed Solomon. ; See ETSI TS 102 821, Clause 7 "PFT Layer", Figure 10. ODR-DabMux ; supports "Fragmentation and Transportation" and "Reed-Solomon and ; Transportation". fec 2 ; Spread and interleave fragments from several EDI frames so as to reduce the ; probability of errors when several UDP packets are lost in bursts. ; This comes at the cost of larger overall latency between multiplexing ; and modulation. ; ; Configure the packet spreader/interleaver through a percentage: ; 0% send all fragments at once, ; 100% spread over 24ms, ; >100% spread and interleave. Default 95% packet_spread 95 ; Length of a RS chunk, can be overridden ;default=207 ;chunk_len 207 ; Save the packets sent over Ethernet to the file ./edi.debug dump false ; show more debugging info verbose false ; (optional) set the kind of alignment to use in TAG Packets ; 0: no padding ; 8: pad to eight bytes (default) ; above 8: insert *dmy TAG Item to pad to given size in bytes ;tagpacket_alignment 8 } ; Other outputs: ; ZeroMQ output example, new configuration format. Several ; zeromq blocks can be added here. ; This output does not back-pressure the multiplexer. ;zeromq { ; Listen on all interfaces, on port 9100 ;endpoint "tcp://*:9100" ; Transmit backward compatible metadata containing ; EDI time and UTC offset when TIST is enabled. ; ; If TIST is enabled, requires leap-second information (see example.mux) ; ; WARNING! requires ODR-DabMux to be compiled with ; cURL support, and this will enable leap second download ; as for the EDI output! ;allowmetadata true ;} ; Legacy format for ZeroMQ output example, equivalent to `allowmetadata false`. ; See above for newer format. ;zmq "zmq+tcp://*:9100" ; Output ETI-over-TCP. This is like piping a RAW ETI NI data stream ; into a TCP socket, except that the output can handle simultaneous ; connections. Not suitable for SFN use because timestamps are incomplete. ; 0.0.0.0 means "listen on all interfaces" ; This output does not back-pressure the multiplexer. ;tcp "tcp://0.0.0.0:9200" ; UDP send to host:port, simple example for unicast ;net_udp "udp://host:port" ; example with source and TTL specification for multicast ;net_udp "udp://237.10.0.230:7000?src=10.0.1.125&ttl=1" ; RAW for farsync ETI card ;farsync "raw://sync0" ; the output also supports two parameters: ; clocking=master and clocking=slave ; ; and extsyncclock which enables external clock sync. Its value is the ; external clock frequency in Hz. ; Example: ;farsync "raw://sync0?clocking=master&extsyncclock=10000000" } Opendigitalradio-ODR-DabMux-29c710c/doc/dab_output_formats.txt000066400000000000000000000021031476627344300243450ustar00rootroot00000000000000ODR-DabMux supports three output formats for the ETI stream. The three formats are called 'framed', 'streamed' and 'raw'. The framed format is used for saving a finite ETI stream into a file. Each frame does not contain any padding, and the format can be described as follows: uint32_t nbFrames // for each frame uint16_t frameSize uint8_t data[ frameSize ] When streaming data, in which case the number of frames is not known in advance, the streamed format can be used. This format is identical to the first one except for the missing nbFrames. // for each frame uint16_t frameSize uint8_t data[ frameSize ] The raw format corresponds to ETI(NI), where each frame has a constant size of 6144 Bytes. The padding in this case is necessary. // for each frame uint8_t data [6144] In order to select the format, the following syntax for the -O option or the output setting in the configuration file is: file://filename?type=format where format is one of framed, streamed or raw, e.g. file:///tmp/mux.eti?type=raw saves a raw ETI file to /tmp/mux.eti Opendigitalradio-ODR-DabMux-29c710c/doc/example.json000066400000000000000000000045171476627344300222440ustar00rootroot00000000000000{ "_comment": "This is the same as example.mux, but in JSON format. JSON doesn't really support comments, so please refer to the example.mux and advanced.mux for documentation of the settings", "general": { "dabmode": 1, "nbframes": 0, "syslog": false, "tist": false, "tist_offset": 0, "managementport": 12720 }, "remotecontrol": { "telnetport": 12721, "zmqendpoint": "tcp://lo:12722" }, "ensemble": { "id": "0x4fff", "ecc": "0xe1", "local-time-offset": "auto", "reconfig-counter": "hash", "label": "OpenDigitalRadio", "shortlabel": "ODR" }, "services": { "srv-fu": { "id": "0x4daa", "label": "Fünk" }, "srv-ri": { "id": "0x5dab", "ecc": "0xe0", "label": "Rick" } }, "subchannels": { "sub-fu": { "type": "audio", "bitrate": 128, "id": 10, "protection": 3, "inputfile": "funk.mp2" }, "sub-bla": { "type": "audio", "bitrate": 96, "id": 1, "protection": 1, "inputproto": "edi", "inputuri": "tcp://0.0.0.0:9001", "buffer-management": "prebuffering", "buffer": 40, "prebuffering": 20 }, "sub-ri": { "type": "dabplus", "bitrate": 96, "id": 1, "protection": 3, "inputproto": "edi", "inputuri": "tcp://127.0.0.1:9000", "buffer-management": "timestamped", "buffer": 500, "tist-delay": 10 } }, "components": { "comp-fu": { "service": "srv-fu", "subchannel": "sub-fu" }, "comp-ri": { "service": "srv-ri", "subchannel": "sub-ri", "user-applications": { "userapp": "slideshow" } } }, "outputs": { "throttle": "simul://", "stdout": "fifo:///dev/stdout?type=raw", "edi": { "destinations": { "example_tcp": { "protocol": "tcp", "listenport": 13000 } } } } } Opendigitalradio-ODR-DabMux-29c710c/doc/example.mux000066400000000000000000000307761476627344300221120ustar00rootroot00000000000000; This is an example configuration file that illustrates ; the structure of the configuration. ; It doesn't show all possible options. A more detailed example ; is available in doc/advanced.mux ; ; The configuration file can also be given in JSON format, an ; example is given in doc/example.json ; ; It contains two services, one DAB and one DAB+, and also shows ; both the file input useful for offline processing, and the ; EDI input useful in a 24/7 scenario. ; More information about the usage of the tools is available ; in the guide, which can be found on the ; www.opendigitalradio.org website. ; ; As you can see, comments are defined by semicolons. ; ; It consists of six mandatory sections, whose relative order in this ; file are of no importance. ; The general section defines global multiplex parameters. general { ; the DAB Transmission mode (values 1-4 accepted) dabmode 1 ; the number of ETI frames to generate (set to 0 to get an unlimited number) nbframes 10 ; boolean fields can accept either false or true as values: ; Set to true to enable logging to syslog syslog false ; Enable timestamp definition necessary for SFN ; This also enables time encoding using the MNSC. ; ; When TIST is enabled, and either EDI or a ZMQ output with metadata is used, ; ODR-DabMux will download the leap-second information bulletin, ; and cache it locally in /var/tmp. ; It will refresh the bulletin by itself before it expires. If that fails, ; ODR-DabMux will continue running with the current TAI-UTC clock offset. ; ; If it cannot load this information, ODR-DabMux cannot start up! ; ; If your system doesn't have access to internet, you have to take care ; to create the file before ODR-DabMux startup. Get it from ; https://raw.githubusercontent.com/eggert/tz/master/leap-seconds.list ; and save it to ; /var/tmp/odr-dabmux-leap-seconds.cache ; ; ODR-DabMux will start up even with an expired bulletin in cache, but will ; output a warning. ; ; Use the RC interface 'get clocktai expiry' command to check how long ; your file is still valid. tist false ; On startup, the timestamp is initialised to system time. If you want ; to add an offset, uncomment the following line and give a positive ; number in seconds. Granularity: 24ms ; tist_offset 0.480 ; The URLs used to fetch the TAI bulletin can be overridden if needed. ; URLs are given as a pipe-separated list, and the default value is: ;tai_clock_bulletins "https://raw.githubusercontent.com/eggert/tz/master/leap-seconds.list" ; ; Through RC, use `set clocktai url ` to change the URLs used to download the bulletin ; during runtime. ; You may also override the bulletin using `set clocktai tai_utc_offset 37` to set a TAI-UTC offset manually. ; ; You may also use a file:// URL if you take care of updating the file ; yourself and store it locally. ; The management server is a simple TCP server that can present ; statistics data (buffers, overruns, underruns, etc) ; which can then be graphed a tool like Munin ; The doc/stats_dabmux_multi.py tool is a suitable ; plugin for that. The data fields are documented in doc/STATS.md ; If the port is zero, or the line commented, the server ; is not started. managementport 12720 ; At startup, run the command and abort if is it not returning 0. ; This may be a script. Useful for checking if the NTP client on your ; system has had time to setup the clock. ;startupcheck "chronyc waitsync 10 0.01" ;startupcheck "ntp-wait -fv" } remotecontrol { ; enable the telnet remote control server on the given port ; This server allows you to read and define parameters that ; some features export. It is only accessible from localhost. ; Set the port to 0 to disable the server telnetport 12721 ; The remote control is also accessible through a ZMQ REQ/REP socket, ; and is useful for machine-triggered interactions. It supports the ; same commands as the telnet RC. ; The example code in doc/zmq_remote.py illustrates how to use this rc. ; To disable the zeromq endpoint, remove the zmqendpoint line. ; By specifying "lo" in the URL, we make the server only accessible ; from localhost. You can write tcp://*:12722 to make it accessible ; on all interfaces. zmqendpoint tcp://lo:12722 ; the remote control server makes use of the unique identifiers ; for the subchannels, services and components. Make sure you ; chose them so that you can identify them. } ; Some ensemble parameters ensemble { ; A unique 16-bit id is allocated to the ensemble and allows unambiguous ; identification of the ensemble when associated with the ensemble ECC. ; The id normally starts with the coutry id. (See ETSI TS 101 756) ; Example for Switzerland, with country id=4 and ECC=E1 id 0x4fff ; you can also use decimal if you want ecc 0xe1 ; Extended Country Code local-time-offset auto ; autmatically calculate from system local time ; or ;local-time-offset 1 ; in hours, supports half-hour offsets ; The presence of reconfig-counter enables FIG0/7, which specifies that ; the ensemble is compliant to EN 300 401 version 2. ; You can either set a number which will be used for the Count field in FIG0/7, ;reconfig-counter 23 ; or set reconfig-counter hash ; to let ODR-DabMux calculate a hash that depends on your multiplex configuration, ; ensuring that when you change the configuration, the FIG 0/7 Count also changes ; ; Leave the option commented-out if you do not wish to transmit FIG 0/7. ; If you want to run your machine in UTC time, but still take advantage of the ; automatic calculation of the local time offset, set the environment variable TZ ; to your timezone (e.g. TZ=Europe/Rome) before you launch ODR-DabMux ; FIG1 labels are given with the 'label' and 'shortlabel' keys. ; ; All labels are maximum 16 characters in length. ; Labels that are valid utf-8 will be converted to EBU Latin Character set ; as defined in ETSI TS 101 756, in Annex C. If it's not valid utf-8, the ; label is taken as-is, byte per byte. Characters that cannot be ; represented in EBU Latin will be replaced by a space character. label "OpenDigitalRadio" ; The short label is built from the label by erasing letters, and cannot ; be longer than 8 characters. If omitted, it will be truncated from the ; label shortlabel "ODR" ; The FIG2 label can be up to 16 characters long, and is in UTF-8. ;fig2_label "ÓpêñÐigıtålRadiō" ; FIG2 labels can either be sent with a character field (old spec) ; or with a text control (new draftETSI TS 103 176 v2.2.1). ; If unspecified, defaults to setting the text control with the values ; shown in the example below. ; ;fig2_label_character_flag "0xFF00" ; ;fig2_label_text_control { ; bidi false ; base_direction "LTR" ; contextual false ; combining false ;} } ; Definition of DAB services services { ; Each service has it's own unique identifier, that is ; used throughout the configuration file and for the RC. srv-fu { id 0x4daa label "Fünk" ; You can define a shortlabel and a fig2_label too. } srv-ri { ; If your ensemble contains a service from another country, ; specify its ECC here. Example is for Italy, country id=5, ECC=E0 id 0x5dab ecc 0xe0 label "Rick" } } subchannels { sub-fu { ; This is our DAB programme, using a file input type audio bitrate 128 id 10 protection 3 inputfile "funk.mp2" } sub-bla { type audio bitrate 96 id 1 protection 1 ; for audio and dabplus, EDI input is available. It supports TCP server and UDP inputproto edi ; Accepts connection to port 9001 from any interface. Prefer disabling PFT when using TCP. inputuri "tcp://0.0.0.0:9001" ; For UDP, PFT should be enabled at the sender. ; Unicast UDP input: ;inputuri "udp://:9001" ; Multicast UDP input: ;inputuri "udp://@239.10.0.1:9001" ; Multicast UDP input with local interface (192.168.0.10) specification ;inputuri "udp://192.168.0.10@239.10.0.1:9001" ; Two buffer-management types are available: prebuffering and timestamped. ; prebuffering will accumulate a few frames before it starts streaming, and each ; time there is a buffer underrun (similar to how the ZMQ input works) ; ; timestamped takes into account the TIST inside EDI and inserts the encoded ; audio frame into the ETI frame with the same timestamp buffer-management prebuffering ; In an ideal scenario, where the input rate exactly corresponds ; to the rate at which the frames are consumed by dabmux, you ; see the buffer level staying around the prebuffering value. ; Network latency jitter can make it temporarily go lower or higher. ; Encoder clock drift will make the buffer either slowly fill or ; empty, which will create intermittent glitches. ; Maximum size of input buffer, in frames (24ms) ; when this buffer size is reached, some frames will be ; discarded to get the size again below this value. buffer 40 ; At startup or after an underrun, the buffer is filled to this ; amount of frames before streaming starts. prebuffering 20 } sub-ri { ; This is our DAB+ programme, using a ZeroMQ input type dabplus bitrate 96 id 1 protection 3 ; Accepts connections to port 9000 from any interface. ; Use ODR-AudioEnc as encoder, accepts only connection ; from the local machine. inputproto edi inputuri "tcp://127.0.0.1:9000" buffer-management timestamped ; When using timestamped, the prebuffering is without effect. ; The buffer setting however still dictates the maximum buffer size, to ; avoid runaway memory usage in case of issues. buffer 500 ; 500 * 24ms = 12 seconds ; Specify the additional delay in milliseconds to add to the TIST. Positive values ; mean the content will be inserted later. tist-delay 10 } } ; In our simple example, each component links one service to one subchannel components { ; the component unique identifiers are used for the RC. comp-fu { ; According to specification, you should not define component labels if ; the service is only used in one component. The service label is sufficient ; in that case. service srv-fu subchannel sub-fu } comp-ri { service srv-ri subchannel sub-ri ; If the programme contains slideshow, please also specify the user-application: user-applications { userapp "slideshow" } } } ; A list of outputs outputs { ; The unique-id can be used by the remote control or the statistics server ; to identify the output ; Output RAW ETI NI to standard output stdout "fifo:///dev/stdout?type=raw" edi { ; Example EDI-over-TCP output ; If TIST is enabled, requires leap-second information destinations { example_tcp { protocol tcp listenport 13000 ; (Optional) When a new client connects, it will receive a pre-roll burst of EDI data, so that it can quickly fill ; its buffers. The value given is the duration of the pre-roll in seconds. ; It makes sense to have a value slightly larger than tist-offset to ensure the destination will receive ; data that will allow it to start transmitting immediately. ;preroll-burst 2.0 } } } ; Throttle output to real-time (one ETI frame every 24ms) throttle "simul://" ; Important! For real-time operation, you need to have exactly one ; output that applies back-pressure to ODR-DabMux, otherwise it will run ; at the highest possible rate on your system! ; ; For an output to a pipe, the data consumer at the other end of the pipe ; will dictate the multiplexing rate to ODR-DabMux. ; ; If you use the EDI output, you must also enable a simul:// output! ; More options are given in doc/advanced.mux } Opendigitalradio-ODR-DabMux-29c710c/doc/remote_control.txt000066400000000000000000000106141476627344300235050ustar00rootroot00000000000000Telnet Remote Control ===================== ODR-DabMux can be configured to set up a simple telnet remote control that can be used to modify some parameters without restarting the multiplexer. The same functionality available through telnet is also available over a ZeroMQ REQ/REP socket, to make automation easier. The server only listens on localhost for security reasons. Remote access should be done using a VPN or SSH port forwarding. The principle is that parts of ODR-DabMux which have modifiable parameters register themselves as remote-controllable modules, and also register parameters. Each parameter has a value, that can be read/written using the get/set commands. The interface is quite simple, and supports the following commands: > help list * Lists the modules that are loaded and their parameters show MODULE * Lists all parameters and their values from module MODULE get MODULE PARAMETER * Gets the value for the specified PARAMETER from module MODULE set MODULE PARAMETER VALUE * Sets the value for the PARAMETER ofr module MODULE quit * Terminate this session Example ======= In this example, there is one service whose unique id (set in the configuration file) is 'srv-fb', one subchannel with uid 'sub-fb' and one component 'comp-fb'. The command 'list' will show all exported parameters, and a small description: > list srv-fb label : Label and shortlabel [label,short] sub-fb buffer : Size of the input buffer [aac superframes] enable : If the input is enabled. Set to zero to empty the buffer. comp-fb label : Label and shortlabel [label,short] The indication in square brackets can help you understand the format of the values, or the units used. e.g. for AAC subchannels, the 'buffer' parameter has values that are counted in number of AAC superframes. It is implicit that a number is meant. In contrast to this, the 'label' parameters of both services and components take a "label,short-label" pair, separated by a comma. Binary values accept the value 0 as false, and 1 as true. Remarks concerning specific modules =================================== ZMQ input --------- The ZMQ input (both for MPEG and AAC+) export a 'buffer' parameter which defines how many frames must be in the buffer *before* the input starts streaming. If you increase the size of the buffer, it will not fill up by itself (unless there is a clock drift between the mux and the encoder). In order to force the buffer to fill up, disable the input by setting 'enable' to 0, and, once the buffer is empty, re-enable it. It will fill to the desired value. Labels (Components and Services) -------------------------------- The restrictions on short-labels, namely that they can only consist of letters appearing in the labels, and that they must be maximum 8 characters long, are verified by the 'label' parameters. If you try to set an invalid label/short-label combination, you will get an error, and the label is unchanged: example: > set comp-fb label Programme1,prog1 comp-fb short label prog1 is not subset of label 'Programme1' example: > set comp-fb label Programme1,Programme comp-fb short label Programme is too long (max 8 characters) Announcements ------------- Announcements can be triggered by the remote control in two ways. For a specific announcement, its active parameter can be toggled which will immediately signal it accordingly. Or the start time or stop time can be set to trigger signalling changes in the future. Direct setting: > set my_announcement active 1 ok Deferred setting: The "start_in" and a "stop_in" parameters both accept a value in milliseconds. They can either be set, and when you read them back you will see the timeout go down to zero; or they can be "not set" if you never set them or if the timeout expired. It is also possible to set both "start_in" and "stop_in" to trigger both a start and stop in the future. The timeout expiry will then influence the "active" parameter internally, ensuring that the "active" parameter always represents the current state of the signalling. > set my_announcement start_in 10000 ok > show my_announcement active: 0 start_in: 7313 stop_in: Not set > show my_announcement active: 0 start_in: 1244 stop_in: Not set > show my_announcement active: 1 start_in: Not set stop_in: Not set Opendigitalradio-ODR-DabMux-29c710c/doc/retodrs.pl000066400000000000000000000504631476627344300217360ustar00rootroot00000000000000#!/usr/bin/perl -w # # RETrieve_Open_Digital_Radio_Status, retodrs: # Retrieve the status and statistics of an Opendigitalradio service and report # the results to Xymon. The information is retrieved from the management server # within ODR-DabMux. # # NOTE: This script MUST be run on the same machine as DabMux is running! The # management server within DabMux is only accessible from localhost. # Moreover, the check on availability only works for localhost. # # Written by W.J.M. Nelis, wim.nelis@ziggo.nl, 2016.12 # use strict ; use Time::Piece ; # Format time use ZMQ::LibZMQ3 ; # Message passing use ZMQ::Constants qw(ZMQ_REQ) ; use JSON::PP ; # Decode server message # # Installation constants. # ----------------------- # my $XyDisp = $ENV{XYMSRV} ; # Name of monitor server my $XySend = $ENV{XYMON} ; # Monitor interface program my $FmtDate = "%Y.%m.%d %H:%M:%S" ; # Default date format $FmtDate = $ENV{XYMONDATEFORMAT} if exists $ENV{XYMONDATEFORMAT} ; my $HostName= 'OzoNop' ; # 'Source' of this test my $TestName= 'odr_mux' ; # Test name my $XyInfo = "hostinfo host=$HostName" ; # Extract host info from xymon my @ColourOf= ( 'red', 'yellow', 'clear', 'green' ) ; # # Define the URL to access the management server in DabMux. The access is # limited to access from localhost! # my $ODRMgmtSrvr= 'tcp://127.0.0.1:12720' ; # URL of server # # Define the parameters to show in the table and how to enter them in an RRD. # From this definition a list of counter-like variables is compiled in hash # %Counters. The values of these variables need to be saved from one pass of # this script to the next. # my @Params= ( # OdrName TableName RrdDefinition [ 'state' , 'State' , '' ], [ 'peak_left' , 'Peak left [dB]' , 'DS:PeakLeft:GAUGE:600:-100:100' ], [ 'peak_right' , 'Peak right [dB]', 'DS:PeakRight:GAUGE:600:-100:100' ], [ 'num_underruns', '' , 'DS:Underrun:DERIVE:600:0:U' ], [ 'num_overruns' , '' , 'DS:Overrun:DERIVE:600:0:U' ], [ 'rate_underruns', 'Underrun [/s]' , '' ], [ 'rate_overruns' , 'Overrun [/s]' , '' ], [ 'min_fill' , '' , 'DS:BufferMin:GAUGE:600:-1:U' ], [ 'max_fill' , '' , 'DS:BufferMax:GAUGE:600:0:U' ] ) ; my %Counters= () ; foreach ( @Params ) { next unless $$_[2] =~ m/DERIVE/ ; $Counters{$$_[0]}= $$_[0] ; # Save name of counter-like variable $Counters{$$_[0]}=~ s/^num_/rate_/ ; # Build name of derived variable } # of foreach # # Define the thresholds for the various DabMux statistics and any derived # value. # my %Thresholds= ( state => { red => qr/^(?:NoData)$/ }, rate_underruns => { red => '20.0'}, rate_overruns => { red => '20.0'}, # peak_left => { yellow => ['< -80', '80'] }, # peak_right => { yellow => ['< -80', '80'] } ) ; # # Define the name of the file to hold the values of the counter-type variables. # my $SaveFile= '/usr/lib/xymon/client/ext/retodrs.sav' ; # # Global variables. # ----------------- # my $Now= localtime ; # Timestamp of tests $Now= $Now->strftime( $FmtDate ) ; my $Colour= $#ColourOf ; # Test status my $Result= '' ; # Message to sent to Xymon my %HostInfo ; # Host information from xymon my %Table0= () ; # Tables with results my %Table1= () ; my @SubChannel= () ; # Subchannel assignment my %SubChannel= () ; # in both directions my %ErrMsg ; # Error messages $ErrMsg{$_}= [] foreach ( @ColourOf ) ; my ($CurTime,$PrvTime) ; # Times of measurement my %Prev= () ; # Variables in previous pass # # Save an error message in intermediate list %ErrMsg. Function InformXymon will # move these messages to the start of the xymon status message. # sub LogError($$) { my $clr= shift ; # Status/colour of message my $msg= shift ; # Error message return unless defined $msg ; return unless $msg ; chomp $msg ; # Clean up message, just to be sure $msg=~ s/^\s+// ; $msg=~ s/\s+$// ; if ( exists $ErrMsg{$clr} ) { push @{$ErrMsg{$clr}}, $msg ; } else { push @{$ErrMsg{clear}}, $msg ; } # of else } # of LogError # # Issue a message the the logfile. As this script is run periodically by Xymon, # StdOut will be redirected to the logfile. # sub LogMessage { my $Msg= shift ; my @Time= (localtime())[0..5] ; $Time[4]++ ; $Time[5]+= 1900 ; chomp $Msg ; printf "%4d%02d%02d %02d%02d%02d %s\n", reverse(@Time), $Msg ; } # of LogMessage sub max($$) { return $_[0] > $_[1] ? $_[0] : $_[1] ; } sub min($$) { return $_[0] < $_[1] ? $_[0] : $_[1] ; } # # Function AnError is given a short description and a boolean value, whose value # is false if the operation associated with the description failed. The result # of this function is the opposite of the boolean value supplied. If failed, the # description is entered in the error message list %ErrMsg, including the # content of $!, if the latter is not empty. # sub AnError($$) { if ( $_[1] ) { return 0 ; # Return a false value } else { my $msg= $! ; # Retrieve any error message if ( $msg eq '' ) { LogError( 'clear', "$_[0] failed" ) ; } else { LogError( 'clear', "$_[0] failed:" ) ; LogError( 'clear', " $msg" ) ; } # of else return 1 ; # Return a true value } # of else } # of AnError # # Function ApplyThresholds determines for which channels threshold checks should # be performed. Then it checks the collected statistics against their # thresholds, and sets the status of those statistics accordingly. The status of # the statistics which are not checked against a threshold are set to 'clear'. # sub ApplyThresholds() { my $hr ; # Reference in a multi-level hash # # Set flag ThresholdCheck at each subchannel. It is set to true if threshold # checks should be performed. # if ( exists $HostInfo{select}{list} ) { $hr= $HostInfo{select}{list} ; $Table1{$_}{ThresholdCheck}= exists $$hr{$_} ? 1 : 0 foreach ( keys %Table1 ) ; } else { $Table1{$_}{ThresholdCheck}= 1 foreach ( keys %Table1 ) ; } # of else # # Invoke function CheckValue for each pair {subchannel,statistic} for which a # threshold check should and can be performed. # foreach my $sub ( keys %Table1 ) { next unless $Table1{$sub}{ThresholdCheck} ; $hr= $Table1{$sub} ; # Ref to subchannel info foreach my $var ( keys %Thresholds ) { next unless exists $$hr{$var} ; CheckValue( $$hr{$var}, $Thresholds{$var} ) ; } # of foreach } # of foreach } # of ApplyThresholds # # Function BuildMessage takes the the status and statistics in hash %Table and # builds a message for Xymon. # sub BuildMessage() { my $RrdMsg ; # RRD message my $sub ; # Name of a sub channel my ($Value,$Status) ; # Value and status of one statistic my @Values ; # Values of one subchannel my $hr ; # Reference into a hash # # Check the subchannel assignment against the list of named subchannels. They # should match. # for ( my $i= 0 ; $i<= $#SubChannel ; $i++ ) { $hr= $SubChannel[$i] ; next unless defined $hr ; next if exists $Table1{$hr} ; $SubChannel[$i]= undef ; delete $SubChannel{$hr} ; } # of for foreach my $sub ( sort keys %Table1 ) { next if exists $SubChannel{$sub} ; $hr= $#SubChannel + 1 ; $SubChannel[$hr]= $sub ; } # of foreach # # Build a table showing the services. # $Result = "\n" ; foreach ( sort keys %Table0 ) { $Result.= " \n" ; } # of foreach $Result.= "
$_ $Table0{$_}
\n\n" ; # # Build the first part of the table to enter the statistics into RRD's and # ultimately into graphs. # $RrdMsg = "\n" ; $RrdMsg.= "" ; $Result.= $RrdMsg ; } # of BuildMessage # # Function CheckPortStatus checks if the TCP port to access the management # server is available in listen mode. The function result is true if the TCP # port is found in the listen state, false otherwise. # sub CheckPortStatus($) { my $url= shift ; # The url to check my $Found= 0 ; # Function result my @F ; # Fields of a line image my @netstat= `netstat -ln4` ; # Retrieve port status info foreach ( @netstat ) { chomp ; @F= split ; # next unless @F == 6 ; # next unless $F[5] eq 'LISTEN' ; next unless "$F[0]://$F[3]" eq $url ; $Found= 1 ; # Port in listen state found last ; # Terminate search } # of foreach return $Found ; } # of CheckPortStatus # # Function CheckValue checks the value of a statistic against its threshold(s). # A reference to the value and a reference to the threshold definition are # passed. # sub CheckValue($$) { my $vr= shift ; # Reference to the variable my $tr= shift ; # Reference to the threshold descriptor my $clr ; $$vr{Status}= $#ColourOf ; # Default result return if $$vr{Value} eq 'wait' ; for ( my $i= $#ColourOf ; $i >= 0 ; $i-- ) { $clr= $ColourOf[$i] ; next unless exists $$tr{$clr} ; if ( ref($$tr{$clr}) eq 'ARRAY' ) { foreach ( @{$$tr{$clr}} ) { if ( ref($_) eq 'Regexp' ) { # Text check $$vr{Status}= $i if $$vr{Value} =~ m/$_/ ; } elsif ( m/^[-+\d\.]+$/ ) { # Numeric upperbound $$vr{Status}= $i if $$vr{Value} > $_ ; } elsif ( m/^<\s*([-+\d\.]+)$/ ) { # Numeric lowerbound $$vr{Status}= $i if $$vr{Value} < $1 ; } # of elsif } # of foreach } else { if ( ref($$tr{$clr}) eq 'Regexp' ) { # Text check $$vr{Status}= $i if $$vr{Value} =~ m/$$tr{$clr}/ ; } elsif ( $$tr{$clr} =~ m/^[-+\d\.]+$/ ) { # Numeric upperbound $$vr{Status}= $i if $$vr{Value} > $$tr{$clr} ; } elsif ( $$tr{$clr} =~ m/^<\s*([-+\d\.]+)$/ ) { # Numeric lowerbound $$vr{Status}= $i if $$vr{Value} < $1 ; } # of elsif } # of else } # of for } # of CheckValue # # Function ComputeRates computes the the rate of change of the counter-like # variables. # sub ComputeRates() { my $hr ; my $val ; foreach my $sub ( keys %Table1 ) { $hr= $Table1{$sub} ; # Ref into hash foreach my $var ( keys %Counters ) { $$hr{$Counters{$var}}{Value}= 'wait' ; $$hr{$Counters{$var}}{State}= undef ; if ( exists $Prev{$sub}{$var} and defined $$hr{$var}{Value} and defined $PrvTime ) { if ( $$hr{$var}{Value} >= $Prev{$sub}{$var} ) { $val= ( $$hr{$var}{Value} - $Prev{$sub}{$var} ) / ( $CurTime - $PrvTime ) ; $$hr{$Counters{$var}}{Value}= sprintf( '%.2f', $val ) ; } # of if } # of if } # of foreach } # of foreach } # of ComputeRates # # Function GetOneReply sends the supplied request to the management server and # returns the result as a reference to a hash. If something went wrong, the # result will be undef and an (appropiate?) error message is entered in %ErrMsg. # sub GetOneReply($$) { my $socket = shift ; # Socket object my $request= shift ; # Request string my $reqlng= length( $request ) ; # Length of request string my $rc= zmq_send( $socket, $request, $reqlng ) ; return undef if AnError( "Request \"$request\"", $rc == $reqlng ) ; my $reply= zmq_recvmsg( $socket ) ; return undef if AnError( "Reply on \"$request\"", defined $reply ) ; $reply= decode_json( zmq_msg_data($reply) ) ; # Convert to Perl structure return $reply ; } # of GetOneReply # # Function GetStatistics retrieves both the status and the statistics from the # server within the DabMux. The results are collected in hash %Table. Subhash # %{$Table{0}} will contain the service information, %{$Table{1}} will contain # the subchannel status and statistics. # sub GetStatistics() { my ($ctxt,$socket) ; # Connection variables my ($reply,$rv) ; # Request/reply variables my ($hr,$vr) ; # Refs into multi-level hash $CurTime= undef ; # No data collected yet # # Build a connection to the DabMux server. # $ctxt= zmq_ctx_new ; return undef if AnError( 'Building context object', defined $ctxt ) ; $socket= zmq_socket( $ctxt, ZMQ_REQ ) ; return undef if AnError( 'Creating socket', defined $socket ) ; $rv= zmq_connect( $socket, $ODRMgmtSrvr ) ; return undef if AnError( 'Connecting to DabMux', $rv == 0 ) ; $reply= GetOneReply( $socket, 'info' ) ; return undef unless defined $reply ; %Table0= %$reply ; # Save overview of services my $Once= 1 ; # Loop control variable while ( $Once ) { $Once= 0 ; # Only one iteration. # # Retrieve the subchannel assignment. # $reply= GetOneReply( $socket, 'getptree' ) ; if ( defined $reply ) { foreach my $sub ( keys %{$$reply{subchannels}} ) { $hr= $$reply{subchannels}{$sub} ; next unless exists $$hr{id} ; next unless $$hr{id} =~ m/^\d+$/ ; $SubChannel[$$hr{id}]= $sub ; $SubChannel{$sub}= $$hr{id} ; } # of foreach } else { next ; # Skip rest of retrievals } # of else # # Retrieve the status and the statistics. # $reply= GetOneReply( $socket, 'state' ) ; if ( defined $reply ) { foreach my $sub ( keys %$reply ) { $Table1{$sub}= {} ; # Preset result area $hr= $Table1{$sub} ; foreach ( keys %{$$reply{$sub}} ) { $$hr{$_}{Value} = $$reply{$sub}{$_} ; $$hr{$_}{Status}= undef ; } # of foreach } # of foreach } else { next ; # Skip retrieval of statistics } # of else $reply= GetOneReply( $socket, 'values' ) ; if ( defined $reply and exists $$reply{values} ) { $CurTime= time ; # Save time of retrieval foreach my $sub ( keys %{$$reply{values}} ) { next unless exists $Table1{$sub} ; next unless exists $$reply{values}{$sub}{inputstat} ; $hr= $Table1{$sub} ; # Ref to destination $vr= $$reply{values}{$sub}{inputstat} ; # Ref to source foreach ( keys %$vr ) { $$hr{$_}{Value} = $$vr{$_} ; $$hr{$_}{Status}= undef ; } # of foreach } # of foreach # } else { # next ; } # of else # # Terminate the connection to the DabMux server. # } continue { $rv= zmq_close( $socket ) ; AnError( 'Closing socket', $rv == 0 ) ; return 0 ; # Return a defined value } # of continue / while } # of GetStatistics # # Function GetXymonHostInfo retrieves the configuration of host $HostName from # the xymon configuration file hosts.cfg. If tag ODR is present, it is handled. # sub GetXymonHostInfo() { %HostInfo= ( select => { default => '^.+$' } ) ; # Default result my @Lines= `$XySend $XyDisp \"$XyInfo\"` ; # Retrieve host info if ( @Lines != 1 ) { # Handle error condition LogError( 'clear', 'Retrieval of host information from Xymon failed' ) ; return ; } # of if my ($Tag)= $Lines[0] =~ m/\b(ODR[^\s\|]+)/ ; # Extract tag ODR return unless defined $Tag ; # return if $Tag eq 'ODR' ; $Tag=~ s/^ODR\:// ; # Remove tag header foreach my $sub ( split( /,/, $Tag ) ) { if ( $sub =~ m/select\((.+)\)$/ ) { $HostInfo{select}{list}{$_}= 0 foreach ( split(/;/,$1) ) ; delete $HostInfo{select}{default} ; } # of if } # of foreach } # of GetXymonHostInfo # # Function InformXymon sends the message, in global variable $Result, to the # Xymon server. Any error messages in %ErrMsg are prepended to the message and # the status (colour) of the message is adapted accordingly. # sub InformXymon() { my $ErrMsg= '' ; my $Clr ; # Colour of one sub-test for ( my $i= 0 ; $i < @ColourOf ; $i++ ) { $Clr= $ColourOf[$i] ; next unless @{$ErrMsg{$Clr}} ; $Colour= min( $Colour, $i ) ; $ErrMsg.= "&$Clr $_\n" foreach ( @{$ErrMsg{$Clr}} ) ; } # of foreach $ErrMsg.= "\n" if $ErrMsg ; $Colour= $ColourOf[$Colour] ; $Result= "\"status $HostName.$TestName $Colour $Now\n" . "Open Digital Radio DabMux status\n\n" . "$ErrMsg$Result\"\n" ; `$XySend $XyDisp $Result` ; # Inform Xymon $Result= '' ; # Reset message parameters $Colour= $#ColourOf ; $ErrMsg{$_}= [] foreach ( @ColourOf ) ; } # of InformXymon # # Function RestoreCounters restores the values of counter-like variables, # collected in the previous pass of this script, in hash %Prev. However, if the # information is too old, nothing is restored. # sub RestoreCounters() { my @F ; # Fields in a line image %Prev= () ; # Clear save area $PrvTime= undef ; unless ( open( FH, '<', $SaveFile ) ) { LogError( 'yellow', "Can't read file $SaveFile : $!" ) ; LogMessage( "Can't read file $SaveFile : $!" ) ; return ; } # of unless while ( ) { chomp ; @F= split ; if ( $F[0] eq 'Time' ) { last unless ( time - $F[1] < 1000 ) ; $PrvTime= $F[1] ; } elsif ( $F[0] eq 'Counter' ) { $Prev{$F[1]}{$F[2]}= $F[3] ; } # of elsif } # of while close( FH ) ; } # of RestoreCounters # Function SaveCounters saves the counter-type variables in a file. They are # retrieved in the next pass of this script, and will be used to calculate the # rate in which these variables increase. # sub SaveCounters() { # # If the retrieval of the statistics failed, nothing should be saved. Perhaps # the information saved at the (a?) previous pass is usable in the next pass. # return unless defined $CurTime ; unless ( open( FH, '>', $SaveFile ) ) { LogError( 'yellow', "Can't write file $SaveFile : $!" ) ; LogMessage( "Can't write file $SaveFile : $!" ) ; return ; } # of unless print FH "Time $CurTime\n" ; foreach my $sub ( sort keys %Table1 ) { foreach my $var ( sort keys %{$Table1{$sub}} ) { next unless exists $Counters{$var} ; next unless defined $Table1{$sub}{$var}{Value} ; print FH "Counter $sub $var $Table1{$sub}{$var}{Value}\n" ; } # of foreach } # of foreach close( FH ) ; } # of SaveCounters # # MAIN PROGRAM. # ============= # unless ( CheckPortStatus($ODRMgmtSrvr) ) { # If server down, LogMessage( "URL \"$ODRMgmtSrvr\" is not available" ) ; LogError( 'red', 'Server is not available' ) ; InformXymon ; # report error via xymon exit ; # and stop } # of unless unless ( defined GetStatistics ) { # If retrieval fails, InformXymon ; # report error via xymon exit ; # and stop } # of unless GetXymonHostInfo ; # Retrieve additional host info if ( keys %Counters ) { RestoreCounters ; # Get counters from previous pass ComputeRates ; # Compute their rate of change SaveCounters ; # Save counters for next pass } # of if ApplyThresholds ; # Check for out-of-range values BuildMessage ; # Build xymon message InformXymon ; # Send message to xymon Opendigitalradio-ODR-DabMux-29c710c/doc/servicelinking.mux000066400000000000000000000161331476627344300234620ustar00rootroot00000000000000; This is an example configuration file that illustrates ; how to define service linking ; More information about the usage of the tools is available ; in the guide, which can be found on the ; www.opendigitalradio.org website. ; general { dabmode 1 nbframes 20000 syslog false tist false managementport 12720 } remotecontrol { telnetport 12721 } ; Service linking sets linking { ; Every child section declares one linkage sets according to ; TS 103 176 Clause 5.2.3 "Linkage sets". This information will ; be encoded in FIG 0/6 set-fu { ; Linkage Set Number is a 12-bit number that identifies the linkage set ; in a country (requires coordination between multiplex operators in a country) ; (mandatory) lsn 0xabc ; Hard link means that all services carry the same programme, soft links means ; that the programmes are related in some way. (default true) hard true ; Linkage actuator flag. Set to false to disable a link active true ; Whether this linkage set affects only one country or several. Linkage sets that ; include AMSS or DRM services need to have this set to true. (default false) ; Linkage sets whose key service has a different ECC than the ensemble ECC need to ; set this to true. international false ; If the keyservice and list are absent, a FIG with an empty list will be transmitted for ; this linkage set. This instructs receivers to delete their corresponding database entry ; (Change Event Indicator for FIG0/6) ; Every linkage set has to contain a service from the current ensemble. keyservice may be omitted. keyservice srv-fu ; List of services to be included (mandatory if keyservice given) list { ; Every service has a uid that can be used as a human-readable description ; The first example is a link to a DAB service on another ensemble. fu-on-my-friends-mux { ; Possible options: dab, fm, drm, amss (mandatory) type dab ; if type is dab, the id is a DAB service ID (mandatory) id 0x8daf ; Since this link set has international false, we do not need to specify ; the ECC. With internation true, the following would be needed ; (mandatory if internation true) ;ecc 0xec } ; The second example is a link to an FM transmission fu-on-fm { ; Possible options: dab, fm, drm, amss type fm ; if type is fm, the id is a PI-code id 0x1A2B ; Also here, ECC declaration is not required } } } ; And now an international true to test more options set-ri { lsn 0xdef hard soft international true keyservice srv-ri list { ri-on-drm { type drm id 0x1298 ecc 0xec } ri-on-amss { type amss id 0x1A2B ecc 0xea } ri-on-fm { type fm id 0x4C5D ecc 0x4f } } } } ; According to ETSI TR 101 496-2 Clause 3.6.10. ; Each entry corresponds to one frequence information ; database entry. The multiplexer then transmits this ; information inside FIG 0/21 defined in ; ETSI EN 300 401 Clause 8.1.8 frequency_information { fi_dab_4fff { ; The database key comprises oe, range_modulation and ; either eid, pi_code, drm_id or amss_id. ; The database key must be unique among all the fi entries. ; RegionId and signalling FI for data services are not implemented. oe false range_modulation dab eid 0x4fff continuity true frequencies { freq_a { signal_mode_1 true adjacent true frequency 234.208 } freq_b { signal_mode_1 true adjacent true frequency 188.928 } freq_c { signal_mode_1 true adjacent false frequency 230.784 } } } fi_fm_1234 { oe false range_modulation fm pi_code 0x1234 continuity true frequencies "87.6 105.2" } fi_dab_4fee { oe true range_modulation dab eid 0x4fee continuity true frequencies { freq_a { signal_mode_1 true adjacent false frequency 230.784 } } } fi_drm_12ab45 { oe false range_modulation drm drm_id 0x12ab45 continuity true frequencies "15.21 22.4" } fi_amss_33cc88 { range_modulation amss amss_id 0x33cc88 continuity true frequencies "14.8" } } ; We can announce the presence of a service in another ensemble using FIG0/24, ; both for services we carry in this ensemble (OE=0) and for services that ; only exist in another ensemble (OE=1) other-services { ; you can freely chose the unique id srv-fu { ; If this ensemble contains a service with this id, OE will be set to 0. ; Otherwise, OE=1 id 0x8daa ; If this service is present in other ensembles, it can be announced ; through FIG0/24. other_ensembles is a comma separated list of ; ensemble IDs (decimal or hexadecimal with 0x prefix). ; Add the current ensemble Id to the list if you carry this service too. other_ensembles "0x4ffe,0x4ffd,0x4fff" } ; For a more efficient usage of the FIC capacity, it is better to first enumerate ; the services that we carry in the ensemble (OE=0), followed by the foreign services. ; This avoids having to send FIG0 headers every time the OE flag switches. srv-foreign { id 0x8daf other_ensembles "0x4ffd" ; Only Audio type services are supported } } ; For information about the ensemble, service, subchannels, components and outputs, ; please see doc/example.mux and doc/advanced.mux ensemble { id 0x4fff ecc 0xec local-time-offset auto label "OpenDigitalRadio" shortlabel "ODR" } services { srv-fu { id 0x8daa label "Funk" } srv-ri { id 0x8dab label "Rick" } } subchannels { sub-fu { type dabplus inputfile "tcp://*:9000" bitrate 96 id 1 protection 3 zmq-buffer 40 zmq-prebuffering 20 } sub-ri { type dabplus inputfile "tcp://*:9001" bitrate 96 id 2 protection 3 zmq-buffer 40 zmq-prebuffering 20 } } components { comp-fu { service srv-fu subchannel sub-fu } comp-ri { service srv-ri subchannel sub-ri } } outputs { file "file://./test.eti?type=raw" ;throttle "simul://" } Opendigitalradio-ODR-DabMux-29c710c/doc/show_dabmux_stats.py000077500000000000000000000071031476627344300240230ustar00rootroot00000000000000#!/usr/bin/env python # # present statistics from dabmux Stats Server # to standard output. # # If you are looking for munin integration, use # ODR-DabMux/doc/stats_dabmux_multi.py import sys import json import zmq import os ctx = zmq.Context() def connect(): """Create a connection to the dabmux stats server returns: the socket""" sock = zmq.Socket(ctx, zmq.REQ) sock.connect("tcp://localhost:12720") sock.send(b"info") infojson = json.loads(sock.recv().decode("utf-8")) sys.stderr.write("Statistics from ODR-DabMux {}\n".format(infojson['version'])) if not infojson['service'].startswith("ODR-DabMux"): sys.stderr.write("This is not ODR-DabMux: {}\n".format(infojson['service'])) sys.exit(1) return sock if len(sys.argv) == 1: sock = connect() sock.send(b"values") poller = zmq.Poller() poller.register(sock, zmq.POLLIN) socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: data = sock.recv().decode("utf-8") values = json.loads(data)['values'] print("## INPUT STATS") tmpl = "{ident:20}{maxfill:>8}{minfill:>8}{under:>8}{over:>8}{audioleft:>8}{audioright:>8}{peakleft:>8}{peakright:>8}{state:>16}{version:>48}{uptime:>8}{offset:>8}" print(tmpl.format( ident="id", maxfill="max", minfill="min", under="under", over="over", audioleft="audio L", audioright="audio R", peakleft="peak L", peakright="peak R", state="state", version="version", uptime="uptime", offset="offset")) for ident in sorted(values): v = values[ident]['inputstat'] if 'state' not in v: v['state'] = None if 'version' not in v: v['version'] = "Unknown" if 'uptime' not in v: v['uptime'] = "?" print(tmpl.format( ident=ident, maxfill=v['max_fill'], minfill=v['min_fill'], under=v['num_underruns'], over=v['num_overruns'], audioleft=v['peak_left'], audioright=v['peak_right'], peakleft=v['peak_left_slow'], peakright=v['peak_right_slow'], state=v['state'], version=v['version'], uptime=v['uptime'], offset=v['last_tist_offset'])) sock.send(b"output_values") poller = zmq.Poller() poller.register(sock, zmq.POLLIN) socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: print() print("## OUTPUT STATS") data = sock.recv().decode("utf-8") values = json.loads(data)['output_values'] for identifier in values: if identifier.startswith("edi_tcp_"): listen_port = identifier.rsplit("_", 1)[-1] num_connections = values[identifier]["num_connections"] print(f"EDI TCP on port {listen_port}: {num_connections} connections") else: print(f"Unknown output type: {identifier}") elif len(sys.argv) == 2 and sys.argv[1] == "config": sock = connect() sock.send(b"config") config = json.loads(sock.recv().decode("utf-8")) print(config['config']) Opendigitalradio-ODR-DabMux-29c710c/doc/stats_dabmux_munin.py000077500000000000000000000202731476627344300241740ustar00rootroot00000000000000#!/usr/bin/env python # # present statistics from dabmux Stats Server and ZeroMQ RC # to munin. Expects Stats server on port 12720 and ZeroMQ RC # on port 12722. # # Copy this to /etc/munin/plugins/stats_dabmux_munin # and make it executable (chmod +x) import sys import json import zmq import os import re config_top = """ multigraph clocktai_expiry graph_title Time to expiry for TAI bulletin graph_order expiry graph_args --base 1000 graph_vlabel Number of seconds until expiry graph_category dabmux graph_info This graph shows the number of remaining seconds this bulletin is valid expiry.info Seconds until expiry expiry.label Seconds until expiry expiry.min 0 expiry.warning {onemonth}: """.format(onemonth=3600*24*30) #default data type is GAUGE config_template = """ multigraph buffers_{ident} graph_title Contribution {ident} buffer graph_order high low graph_args --base 1000 graph_vlabel max/min buffer size bytes during last ${{graph_period}} graph_category dabmux graph_info This graph shows the high and low buffer sizes for the {ident} ZMQ input high.info Max buffer size high.label Max Buffer Bytes high.min 0 high.warning 1: low.info Min buffer size low.label Min Buffer Bytes low.min 0 low.warning 1: multigraph over_underruns_{ident} graph_title Contribution {ident} over/underruns graph_order underruns overruns graph_args --base 1000 --logarithmic graph_vlabel number of underruns/overruns during last ${{graph_period}} graph_category dabmux graph_info This graph shows the number of under/overruns for the {ident} ZMQ input underruns.info Number of underruns underruns.label Number of underruns underruns.min 0 underruns.warning 0:0 underruns.type COUNTER overruns.info Number of overruns overruns.label Number of overruns overruns.min 0 overruns.warning 0:0 overruns.type COUNTER multigraph audio_levels_{ident} graph_title Contribution {ident} audio level (peak) graph_order left left_slow right right_slow graph_args --base 1000 graph_vlabel peak audio level during last ${{graph_period}} graph_category encoders graph_info This graph shows the audio level and peak of both channels of the {ident} ZMQ input left.info Left channel audio level left.label Left level left.min -90 left.max 0 left.warning -40:0 left.critical -80:0 left_slow.info Left channel audio peak over last 5 minutes left_slow.label Left peak left_slow.min -90 left_slow.max 0 left_slow.warning -40:0 left_slow.critical -80:0 right.info Right channel audio level right.label Right level right.min -90 right.max 0 right.warning -40:0 right.critical -80:0 right_slow.info Right channel audio peak over last 5 minutes right_slow.label Right peak right_slow.min -90 right_slow.max 0 right_slow.warning -40:0 right_slow.critical -80:0 multigraph state_{ident} graph_title State of contribution {ident} graph_order state graph_args --base 1000 --lower-limit 0 --upper-limit 5 graph_vlabel Current state of the input graph_category dabmux graph_info This graph shows the state for the {ident} ZMQ input state.info Input state state.label 0 Unknown, 1 NoData, 2 Unstable, 3 Silent, 4 Streaming state.warning 4:4 state.critical 2:4 """ ctx = zmq.Context() class RCException(Exception): pass def do_transaction(command, sock): """To a send + receive transaction, quit whole program on timeout""" sock.send(command.encode("utf-8")) poller = zmq.Poller() poller.register(sock, zmq.POLLIN) socks = dict(poller.poll(1000)) if socks: if socks.get(sock) == zmq.POLLIN: return sock.recv().decode("utf-8") sys.stderr.write("Could not receive data for command '{}'\n".format(command)) sys.exit(1) def do_multipart_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 get_rc_value(module, name, sock): try: parts = do_multipart_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 connect_to_stats(): """Create a connection to the dabmux stats server returns: the socket""" sock = zmq.Socket(ctx, zmq.REQ) sock.set(zmq.LINGER, 5) sock.connect("tcp://localhost:12720") version = json.loads(do_transaction("info", sock)) if not version['service'].startswith("ODR-DabMux"): sys.stderr.write("Wrong version\n") sys.exit(1) return sock def connect_to_rc(): """Create a connection to the dabmux RC returns: the socket""" sock = zmq.Socket(ctx, zmq.REQ) sock.set(zmq.LINGER, 5) sock.connect("tcp://localhost:12722") try: ping_answer = do_multipart_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 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) if len(sys.argv) == 1: munin_values = "" sock_rc = connect_to_rc() clocktai_expiry = get_rc_value("clocktai", "expiry", sock_rc) re_clocktai_expiry = re.compile(r"(\d+)", re.X) munin_values += "multigraph clocktai_expiry\n" munin_values += handle_re("expiry", re_clocktai_expiry, clocktai_expiry) sock_stats = connect_to_stats() values = json.loads(do_transaction("values", sock_stats))['values'] for ident in values: v = values[ident]['inputstat'] ident_ = ident.replace('-', '_') munin_values += "multigraph buffers_{ident}\n".format(ident=ident_) munin_values += "high.value {}\n".format(v['max_fill']) munin_values += "low.value {}\n".format(v['min_fill']) munin_values += "multigraph over_underruns_{ident}\n".format(ident=ident_) munin_values += "underruns.value {}\n".format(v['num_underruns']) munin_values += "overruns.value {}\n".format(v['num_overruns']) munin_values += "multigraph audio_levels_{ident}\n".format(ident=ident_) munin_values += "left.value {}\n".format(v['peak_left']) munin_values += "right.value {}\n".format(v['peak_right']) if 'peak_left_slow' in v: # If ODR-DabMux is v2.0.0-3 or older, it doesn't export the slow peaks munin_values += "left_slow.value {}\n".format(v['peak_left_slow']) munin_values += "right_slow.value {}\n".format(v['peak_right_slow']) if 'state' in v: # If ODR-DabMux is v1.3.1-3 or older, it doesn't export state re_state = re.compile(r"\w+ \((\d+)\)") match = re_state.match(v['state']) if match: munin_values += "multigraph state_{ident}\n".format(ident=ident_) munin_values += "state.value {}\n".format(match.group(1)) else: sys.stderr.write("Cannot parse state '{}'\n".format(v['state'])) print(munin_values) elif len(sys.argv) == 2 and sys.argv[1] == "config": sock_stats = connect_to_stats() config = json.loads(do_transaction("config", sock_stats)) munin_config = config_top for conf in config['config']: munin_config += config_template.format(ident=conf.replace('-', '_')) print(munin_config) else: sys.stderr.write("Invalid command line arguments") sys.exit(1) Opendigitalradio-ODR-DabMux-29c710c/doc/stats_zmq2edi_munin.py000066400000000000000000000116731476627344300242700ustar00rootroot00000000000000#!/usr/bin/env python3 # # A munin plugin for ODR-ZMQ2EDI # # Reads the logfile, and the previously rotated logfile (suffixed by .1) and # analyses the output. Generates a graph with percentage of frames late, and a # graph with min/max wait time. # # Copy this to /etc/munin/plugins/stats_zmq2edi_munin # and make it executable (chmod +x) # # Then make sure that zmq2edi log output gets written to LOGFILE below, # and setup up a logrotate script to rotate the log. The rotated log # filename must be appended with .1 # Every six seconds a line is output. We are polled in 5 min = 300s intervals NUM_LINES = int(300 / 6) LOGFILE = "/var/log/supervisor/zmq2edi.log" import time import sys import os import re munin_config = """ multigraph wait_time_zmq2edi graph_title zmq2edi wait_time graph_order high low graph_args --base 1000 graph_vlabel max/min wait times during last ${graph_period} graph_category zmq2edi graph_info This graph shows the min and max wait times high.info Max wait time high.label Max wait time ms high.min 0 high.warning 1: low.info Min wait time low.label Min wait time ms low.min -6000 low.warning 1: multigraph late_packets_zmq2edi graph_title EDI packets delivered too late graph_order late graph_args --base 1000 graph_vlabel late packets during last ${graph_period} graph_category zmq2edi graph_info This graph shows the number late EDI packets (250 packets = 6 seconds) late.info Number of late packets late.label Number of late packets late.min 0 late.max %s late.warning 0:0 """ % (NUM_LINES * 250,) def parse_logs(): # example lines: # Buffering time statistics [milliseconds]: min: 907.799 max: 981.409 mean: 944.335 stdev: 26.827 late: 0 of 250 (0%) # Values might also be in scientific form, e.g. -1.80938e+07 re_logline = re.compile(r"""Buffering time statistics.* min: (.+) max: (.+) mean: (.+) stdev: (.+) late: (.+) of 250""", flags=re.ASCII) # The following lines are output at startup and during a reset respectively: startup_pattern = "starting up" backoff_pattern = "Backoff" lines = [] # Check that the file exists and was last written to in the previous 2* 6s, # otherwise assume the tool isn't running if not os.path.exists(LOGFILE) or (time.time() - os.stat(LOGFILE).st_mtime) > 12: num_late = None t_min_period = None t_max_period = None else: # Keep only the last NUM_LINES # Read the previously rotated logfile too to make sure we have enough data for fname in [LOGFILE+ ".1", LOGFILE]: if os.path.exists(fname): with open(fname, "r") as fd: for line in fd: lines.append(line) if len(lines) > NUM_LINES: del lines[0] # Calculate min, max over the whole period, and sum the number of late num_late = 0 t_min_period = None t_max_period = None num_statistics = 0 for line in lines: if startup_pattern in line: num_late += 250 elif backoff_pattern in line: num_late += 250 else: match = re_logline.search(line) if match: num_statistics += 1 t_min = float(match.group(1)) t_max = float(match.group(2)) t_mean = float(match.group(3)) stdev = float(match.group(4)) late = int(match.group(5)) if t_min_period is None or t_min < t_min_period: t_min_period = t_min if t_max_period is None or t_max > t_max_period: t_max_period = t_max if num_late is None: num_late = 0 num_late += late # The min can take extremely low values, we clamp it here to -6 seconds # to keep the graph readable if t_min_period is not None and t_min_period < -6000: t_min_period = -6000 return num_late, round(t_min_period) if t_min_period is not None else None, round(t_max_period) if t_max_period is not None else None def muninify(value): """ According to http://guide.munin-monitoring.org/en/latest/develop/plugins/plugin-concise.html#plugin-concise "If the plugin - for any reason - has no value to report, then it may send the value U for undefined." """ return 'U' if value is None else value # No arguments means that munin wants values if len(sys.argv) == 1: num_late, t_min, t_max = parse_logs() munin_values = "multigraph wait_time_zmq2edi\n" munin_values += "high.value {}\n".format(muninify(t_max)) munin_values += "low.value {}\n".format(muninify(t_min)) munin_values += "multigraph late_packets_zmq2edi\n" munin_values += "late.value {}\n".format(muninify(num_late)) print(munin_values) elif len(sys.argv) == 2 and sys.argv[1] == "config": print(munin_config) else: sys.exit(1) Opendigitalradio-ODR-DabMux-29c710c/doc/zmq_remote.py000077500000000000000000000054351476627344300224550ustar00rootroot00000000000000#!/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-DabMux-29c710c/lib/000077500000000000000000000000001476627344300177105ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/ClockTAI.cpp000066400000000000000000000554601476627344300220170ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 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 . */ /* This file downloads the TAI-UTC bulletins from the from IETF and parses them * so that correct time can be communicated in EDI timestamps. * * This file contains self-test code that can be executed by running * g++ -g -Wall -DTAI_TEST -DHAVE_CURL -std=c++11 -lcurl -pthread \ * ClockTAI.cpp Log.cpp RemoteControl.cpp -lboost_system -o taitest && ./taitest */ #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "ClockTAI.h" #include "Log.h" #include #include #include #include #if SUPPORT_SETTING_CLOCK_TAI # include #endif #ifdef HAVE_CURL # include #endif #include #include #include #include #include #include #include #include using namespace std; constexpr int refresh_retry_interval_hours = 1; // Offset between NTP time and POSIX time: // timestamp_unix = timestamp_ntp - NTP_UNIX_OFFSET constexpr int64_t NTP_UNIX_OFFSET = 2208988800L; // leap seconds insertion bulletin was previously available from the IETF and in the TZ // distribution, but in late 2023 IETF stopped serving the file. static array default_tai_urls = { "https://raw.githubusercontent.com/eggert/tz/master/leap-seconds.list", }; // According to the Filesystem Hierarchy Standard, the data in // /var/tmp "must not be deleted when the system is booted." static const char *tai_cache_location = "/var/tmp/odr-leap-seconds.cache"; static string join_string_with_pipe(const vector& vec) { stringstream ss; for (auto it = vec.cbegin(); it != vec.cend(); ++it) { ss << *it; if (it + 1 != vec.cend()) { ss << "|"; } } return ss.str(); } static vector split_pipe_separated_string(const string& s) { stringstream ss; ss << s; string elem; vector components; while (getline(ss, elem, '|')) { components.push_back(elem); } return components; } // read TAI offset from a valid bulletin in IETF format static int parse_ietf_bulletin(const std::string& bulletin) { // Example Line: // 3692217600 37 # 1 Jan 2017 // // NTP timestampleap seconds# some comment // The NTP timestamp starts at epoch 1.1.1900. // The difference between NTP timestamps and unix epoch is 70 // years i.e. 2208988800 seconds std::regex regex_bulletin(R"(([0-9]+)\s+([0-9]+)\s+#.*)"); time_t now = time(nullptr); int tai_utc_offset = 0; int tai_utc_offset_valid = false; stringstream ss(bulletin); /* We cannot just take the last line, because it might * be in the future, announcing an upcoming leap second. * * So we need to look at the current date, and compare it * with the date of the leap second. */ for (string line; getline(ss, line); ) { std::smatch bulletin_entry; bool is_match = std::regex_search(line, bulletin_entry, regex_bulletin); if (is_match) { if (bulletin_entry.size() != 3) { throw runtime_error( "Incorrect number of matched TAI IETF bulletin entries"); } const string bulletin_ntp_timestamp(bulletin_entry[1]); const string bulletin_offset(bulletin_entry[2]); const int64_t timestamp_unix = std::stoll(bulletin_ntp_timestamp) - NTP_UNIX_OFFSET; const int offset = std::stoi(bulletin_offset.c_str()); // Ignore entries announcing leap seconds in the future if (timestamp_unix < now) { tai_utc_offset = offset; tai_utc_offset_valid = true; } #if TAI_TEST else { cerr << "IETF Ignoring offset " << bulletin_offset << " at TS " << bulletin_ntp_timestamp << " in the future" << endl; } #endif } } if (not tai_utc_offset_valid) { throw runtime_error("No data in TAI bulletin"); } // With the current evolution of the offset, we're probably going // to reach 500 long after DAB gets replaced by another standard. // Or maybe leap seconds get abolished first... if (tai_utc_offset < 0 or tai_utc_offset > 500) { throw runtime_error("Unreasonable TAI-UTC offset calculated"); } return tai_utc_offset; } int64_t BulletinState::expires_in() const { time_t now = time(nullptr); return expires_at - now; } bool BulletinState::usable() const { return valid and expires_in() > 0; } bool BulletinState::expires_soon() const { constexpr int64_t MONTH = 3600 * 24 * 30; return usable() and expires_in() < 1 * MONTH; } BulletinState Bulletin::state() const { // The bulletin contains one line that specifies an expiration date // in NTP time. If that point in time is in the future, we consider // the bulletin valid. // // The entry looks like this: //#@ 3707596800 BulletinState ret; if (std::holds_alternative(bulletin_or_override)) { const auto& od = std::get(bulletin_or_override); ret.offset = od.offset; ret.expires_at = od.expires_at; ret.valid = true; #ifdef TAI_TEST etiLog.level(debug) << "state() from Override!"; #endif return ret; } const auto& bulletin = std::get(bulletin_or_override); std::regex regex_expiration(R"(#@\s+([0-9]+))"); stringstream ss(bulletin); for (string line; getline(ss, line); ) { std::smatch bulletin_entry; bool is_match = std::regex_search(line, bulletin_entry, regex_expiration); if (is_match) { if (bulletin_entry.size() != 2) { throw runtime_error( "Incorrect number of matched TAI IETF bulletin expiration"); } const string expiry_data_str(bulletin_entry[1]); try { const int64_t expiry_unix = std::stoll(expiry_data_str) - NTP_UNIX_OFFSET; #ifdef TAI_TEST { // Do not use `now` for anything else but debugging, otherwise it // breaks the cache. time_t now = time(nullptr); etiLog.level(debug) << "Bulletin " << get_source() << " expires in " << expiry_unix - now; } #endif ret.expires_at = expiry_unix; ret.offset = parse_ietf_bulletin(bulletin); ret.valid = true; } catch (const invalid_argument& e) { etiLog.level(warn) << "Could not parse bulletin from " << get_source() << ": " << e.what(); } catch (const out_of_range&) { etiLog.level(warn) << "Parse bulletin from " << get_source() << ": conversion is out of range"; } catch (const runtime_error& e) { etiLog.level(warn) << "Parse bulletin from " << get_source() << ": " << e.what(); } break; } } return ret; } // callback that receives data from cURL static size_t fill_bulletin(char *ptr, size_t size, size_t nmemb, void *ctx) { auto *bulletin = reinterpret_cast(ctx); size_t len = size * nmemb; for (size_t i = 0; i < len; i++) { *bulletin << ptr[i]; } return len; } Bulletin Bulletin::download_from_url(const char* url) { stringstream bulletin_data; #ifdef HAVE_CURL CURL *curl; CURLcode res; curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, url); /* Tell libcurl to follow redirection */ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fill_bulletin); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &bulletin_data); res = curl_easy_perform(curl); /* always cleanup ! */ curl_easy_cleanup(curl); if (res != CURLE_OK) { throw runtime_error( "TAI-UTC bulletin download failed: " + string(curl_easy_strerror(res))); } } Bulletin bulletin; bulletin.source = url; bulletin.bulletin_or_override = bulletin_data.str(); return bulletin; #else throw runtime_error("Cannot download TAI Clock information without cURL"); #endif // HAVE_CURL } Bulletin Bulletin::create_with_fixed_offset(int offset) { Bulletin bulletin; bulletin.source = "manual override"; OverrideData od; od.offset = offset; time_t now = time(nullptr); // 10 years is probably equivalent to infinity in this case... od.expires_at = now + 10L * 365 * 24 * 3600; bulletin.bulletin_or_override = od; return bulletin; } Bulletin Bulletin::load_from_file(const char* cache_filename) { Bulletin bulletin; bulletin.source = cache_filename; int fd = open(cache_filename, O_RDWR); // lockf requires O_RDWR if (fd == -1) { etiLog.level(error) << "TAI-UTC bulletin open cache for reading: " << strerror(errno); return bulletin; } lseek(fd, 0, SEEK_SET); vector buf(1024); vector new_bulletin_data; ssize_t ret = lockf(fd, F_LOCK, 0); if (ret == 0) { // exclusive lock acquired do { ret = read(fd, buf.data(), buf.size()); if (ret == -1) { close(fd); etiLog.level(error) << "TAI-UTC bulletin read cache: " << strerror(errno); return bulletin; } copy(buf.data(), buf.data() + ret, back_inserter(new_bulletin_data)); } while (ret > 0); close(fd); bulletin.bulletin_or_override = string{new_bulletin_data.data(), new_bulletin_data.size()}; } else { etiLog.level(error) << "TAI-UTC bulletin acquire cache lock for reading: " << strerror(errno); close(fd); } return bulletin; } void Bulletin::clear_expiry_if_overridden() { if (std::holds_alternative(bulletin_or_override)) { auto& od = std::get(bulletin_or_override); time_t now = time(nullptr); od.expires_at = now; } } #if ENABLE_REMOTECONTROL ClockTAI::ClockTAI(const std::vector& bulletin_urls) : RemoteControllable("clocktai") { RC_ADD_PARAMETER(tai_utc_offset, "TAI-UTC offset"); RC_ADD_PARAMETER(expiry, "Number of seconds until TAI Bulletin expires"); RC_ADD_PARAMETER(expires_at, "UNIX timestamp when TAI Bulletin expires"); RC_ADD_PARAMETER(url, "URLs used to fetch the bulletin, separated by pipes"); #else ClockTAI::ClockTAI(const std::vector& bulletin_urls) { #endif // ENABLE_REMOTECONTROL if (bulletin_urls.empty()) { etiLog.level(debug) << "Initialising default TAI Bulletin URLs"; for (const auto url : default_tai_urls) { m_bulletin_urls.push_back(url); } } else { etiLog.level(debug) << "Initialising user-configured TAI Bulletin URLs"; m_bulletin_urls = bulletin_urls; } etiLog.level(debug) << "ClockTAI uses bulletin URL: '" << join_string_with_pipe(m_bulletin_urls) << "'"; } BulletinState ClockTAI::get_valid_offset() { std::unique_lock lock(m_data_mutex); const auto state = m_bulletin.state(); #if TAI_TEST etiLog.level(info) << "TAI get_valid_offset STEP 1 "; etiLog.level(info) << " " << m_bulletin.get_source() << " " << state.valid << " " << state.usable() << " " << state.expires_in(); #endif if (state.usable()) { return state; } #if TAI_TEST etiLog.level(info) << "TAI get_valid_offset STEP 2"; #endif const auto cache_bulletin = Bulletin::load_from_file(tai_cache_location); const auto cache_state = cache_bulletin.state(); if (cache_state.usable()) { m_bulletin = cache_bulletin; #if TAI_TEST etiLog.level(info) << "TAI get_valid_offset STEP 2 take cache"; #endif return cache_state; } #if TAI_TEST etiLog.level(info) << "TAI get_valid_offset STEP 3"; #endif vector bulletins({m_bulletin, cache_bulletin}); for (const auto& url : m_bulletin_urls) { try { #if TAI_TEST etiLog.level(info) << "Load bulletin from " << url; #endif const auto new_bulletin = Bulletin::download_from_url(url.c_str()); bulletins.push_back(new_bulletin); const auto new_state = new_bulletin.state(); if (new_state.usable()) { m_bulletin = new_bulletin; new_bulletin.store_to_cache(tai_cache_location); etiLog.level(debug) << "Loaded valid TAI Bulletin from " << url << " giving offset=" << new_state.offset; return new_state; } else { etiLog.level(debug) << "Skipping invalid TAI bulletin from " << url; } } catch (const runtime_error& e) { etiLog.level(warn) << "TAI-UTC offset could not be retrieved from " << url << " : " << e.what(); } } #if TAI_TEST etiLog.level(info) << "TAI get_valid_offset STEP 4"; #endif // Maybe we have a valid but expired bulletin available. // Place bulletins with largest expiry first std::sort(bulletins.begin(), bulletins.end(), [](const Bulletin& a, const Bulletin& b) { return a.state().expires_at > b.state().expires_at; }); for (const auto& bulletin : bulletins) { const auto& state = bulletin.state(); if (state.valid) { etiLog.level(warn) << "Taking TAI-UTC offset from expired bulletin from " << bulletin.get_source() << " : " << state.offset << "s expired " << state.expires_in() << "s ago"; m_bulletin = bulletin; return state; } } throw download_failed(); } int ClockTAI::get_offset() { using namespace std::chrono; const auto time_now = steady_clock::now(); std::unique_lock lock(m_data_mutex); if (not m_state.has_value()) { // First time we run we must block until we know // the offset lock.unlock(); try { m_state = get_valid_offset(); } catch (const download_failed&) { throw runtime_error("Unable to download TAI bulletin"); } lock.lock(); m_state_last_updated = time_now; etiLog.level(info) << "Initialised TAI-UTC offset to " << m_state->offset << "s."; } if (m_state_last_updated + hours(1) < time_now) { // Once per hour, parse the bulletin again, and // if necessary trigger a download. // Leap seconds are announced several months in advance etiLog.level(debug) << "Trying to refresh TAI bulletin"; if (m_offset_future.valid()) { auto state = m_offset_future.wait_for(seconds(0)); switch (state) { case future_status::ready: try { m_state = m_offset_future.get(); m_state_last_updated = time_now; etiLog.level(info) << "Updated TAI-UTC offset to " << m_state->offset << "s."; } catch (const download_failed&) { etiLog.level(warn) << "TAI-UTC download failed, will retry in " << refresh_retry_interval_hours << " hour(s)"; #if TAI_TEST m_state_last_updated += seconds(11); #else m_state_last_updated += hours(refresh_retry_interval_hours); #endif } break; case future_status::deferred: case future_status::timeout: // Not ready yet #ifdef TAI_TEST etiLog.level(debug) << " async not ready yet"; #endif break; } } else { #ifdef TAI_TEST etiLog.level(debug) << " Launch async"; #endif m_offset_future = async(launch::async, &ClockTAI::get_valid_offset, this); } } if (m_state) { return m_state->offset; } throw std::logic_error("ClockTAI: No valid m_state at end of get_offset()"); } #if SUPPORT_SETTING_CLOCK_TAI int ClockTAI::update_local_tai_clock(int offset) { struct timex timex_request; timex_request.modes = ADJ_TAI; timex_request.constant = offset; int err = adjtimex(&timex_request); if (err == -1) { perror("adjtimex"); } printf("adjtimex: %d, tai %d\n", err, timex_request.tai); return err; } #endif void Bulletin::store_to_cache(const char* cache_filename) const { if (not std::holds_alternative(bulletin_or_override)) { etiLog.level(error) << "ClockTAI: Cannot store an artificial bulletin to cache!"; } const auto& bulletin = std::get(bulletin_or_override); int fd = open(cache_filename, O_RDWR | O_CREAT, 00664); if (fd == -1) { etiLog.level(error) << "TAI-UTC bulletin open cache for writing: " << strerror(errno); return; } lseek(fd, 0, SEEK_SET); ssize_t ret = lockf(fd, F_LOCK, 0); if (ret == 0) { // exclusive lock acquired const char *data = bulletin.data(); size_t remaining = bulletin.size(); while (remaining > 0) { ret = write(fd, data, remaining); if (ret == -1) { close(fd); etiLog.level(error) << "TAI-UTC bulletin write cache: " << strerror(errno); return; } remaining -= ret; data += ret; } etiLog.level(debug) << "TAI-UTC bulletin cache updated"; close(fd); } else { close(fd); etiLog.level(error) << "TAI-UTC bulletin acquire cache lock for writing: " << strerror(errno); return; } } #if ENABLE_REMOTECONTROL void ClockTAI::set_parameter(const string& parameter, const string& value) { if (parameter == "expiry" or parameter == "expires_at") { throw ParameterError("Parameter '" + parameter + "' is read-only in controllable " + get_rc_name()); } else if (parameter == "tai_utc_offset") { const auto offset = std::stoi(value); auto b = Bulletin::create_with_fixed_offset(offset); etiLog.level(warn) << "ClockTAI: manually overriding UTC-TAI offset to " << offset; std::unique_lock lock(m_data_mutex); m_bulletin = b; m_state = b.state(); m_state_last_updated = chrono::steady_clock::now(); } else if (parameter == "url") { { std::unique_lock lock(m_data_mutex); m_bulletin_urls = split_pipe_separated_string(value); m_state_last_updated = chrono::steady_clock::time_point::min(); // Setting URL expires the bulletin, if it was manually overridden, // so that the selection logic doesn't prefer it m_bulletin.clear_expiry_if_overridden(); } etiLog.level(info) << "ClockTAI: triggering a reload from URLs..."; } else { throw ParameterError("Parameter '" + parameter + "' is not exported by controllable " + get_rc_name()); } } const string ClockTAI::get_parameter(const string& parameter) const { if (parameter == "expiry") { std::unique_lock lock(m_data_mutex); return to_string(m_bulletin.state().expires_in()); } else if (parameter == "expires_at") { std::unique_lock lock(m_data_mutex); return to_string(m_bulletin.state().expires_at); } else if (parameter == "tai_utc_offset") { std::unique_lock lock(m_data_mutex); if (m_state) { return to_string(m_state->offset); } throw ParameterError("Parameter '" + parameter + "' has no current value" + get_rc_name()); } else if (parameter == "url") { return join_string_with_pipe(m_bulletin_urls);; } else { throw ParameterError("Parameter '" + parameter + "' is not exported by controllable " + get_rc_name()); } } const json::map_t ClockTAI::get_all_values() const { json::map_t stat; std::unique_lock lock(m_data_mutex); const auto& state = m_bulletin.state(); #if TAI_TEST etiLog.level(debug) << "CALC FROM m_bulletin: " << state.valid << " " << state.offset << " " << state.expires_at << " -> " << state.expires_in(); etiLog.level(debug) << "CACHED IN m_state: " << m_state->valid << " " << m_state->offset << " " << m_state->expires_at << " -> " << m_state->expires_in(); #endif stat["tai_utc_offset"].v = state.offset; stat["expiry"].v = state.expires_in(); // Might be negative when expired or 0 when invalid if (state.valid) { stat["expires_at"].v = state.expires_at; } else { stat["expires_at"].v = nullopt; } stat["url"].v = join_string_with_pipe(m_bulletin_urls); return stat; } #endif // ENABLE_REMOTECONTROL #if 0 // Example testing code void debug_tai_clk() { struct timespec rt_clk; int err = clock_gettime(CLOCK_REALTIME, &rt_clk); if (err) { perror("REALTIME clock_gettime failed"); } struct timespec tai_clk; err = clock_gettime(CLOCK_TAI, &tai_clk); if (err) { perror("TAI clock_gettime failed"); } printf("RT - TAI = %ld\n", rt_clk.tv_sec - tai_clk.tv_sec); struct timex timex_request; timex_request.modes = 0; // Do not set anything err = adjtimex(&timex_request); if (err == -1) { perror("adjtimex"); } printf("adjtimex: %d, tai %d\n", err, timex_request.tai); } #endif Opendigitalradio-ODR-DabMux-29c710c/lib/ClockTAI.h000066400000000000000000000107251476627344300214570ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 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 . */ /* The EDI output needs TAI clock, according to ETSI TS 102 693 Annex F * "EDI Timestamps". This module can set the local CLOCK_TAI clock by * setting the TAI-UTC offset using adjtimex. * * This functionality requires Linux 3.10 (30 Jun 2013) or newer. */ #pragma once #include #include #include #include #include #include #include #include #include "RemoteControl.h" // EDI needs to know UTC-TAI, but doesn't need the CLOCK_TAI to be set. // We can keep this code, maybe for future use #define SUPPORT_SETTING_CLOCK_TAI 0 struct BulletinState { bool valid = false; int64_t expires_at = 0; int offset = 0; int64_t expires_in() const; bool usable() const; bool expires_soon() const; }; class Bulletin { public: static Bulletin download_from_url(const char *url); static Bulletin create_with_fixed_offset(int offset); static Bulletin load_from_file(const char *cache_filename); void clear_expiry_if_overridden(); void store_to_cache(const char* cache_filename) const; std::string get_source() const { return source; } BulletinState state() const; private: // URL or file path from which the bulletin has been/will be loaded std::string source; struct OverrideData { int offset = 0; int expires_at = 0; }; // string: A cache of the bulletin, or empty string if not loaded // int: A manually overridden offset std::variant bulletin_or_override; }; /* Loads, parses and represents TAI-UTC offset information from the IETF bulletin */ class ClockTAI #if ENABLE_REMOTECONTROL : public RemoteControllable #endif // ENABLE_REMOTECONTROL { public: ClockTAI(const std::vector& bulletin_urls); // Fetch the bulletin from the IETF website and return the current // TAI-UTC offset. // Throws runtime_error on failure. int get_offset(void); #if SUPPORT_SETTING_CLOCK_TAI // Update the local TAI clock according to the TAI-UTC offset // return 0 on success int update_local_tai_clock(int offset); #endif private: class download_failed {}; // Either retrieve the bulletin from the cache or if necessarly // download it, and calculate the TAI-UTC offset. // Returns the offset or throws download_failed or a range_error // if the offset is out of bounds. BulletinState get_valid_offset(void); // Download of new bulletin is done asynchronously std::future m_offset_future; // Protect all data members, as RC functions are in another thread mutable std::mutex m_data_mutex; std::vector m_bulletin_urls; Bulletin m_bulletin; // The currently used TAI-UTC offset, extracted the bulletin and cached // here to avoid having to parse the bulletin all the time std::optional m_state; std::chrono::steady_clock::time_point m_state_last_updated; #if ENABLE_REMOTECONTROL public: /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; #endif // ENABLE_REMOTECONTROL }; Opendigitalradio-ODR-DabMux-29c710c/lib/Globals.cpp000066400000000000000000000023311476627344300217760ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/Json.cpp000066400000000000000000000102601476627344300213240ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/Json.h000066400000000000000000000034261476627344300207770ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/Log.cpp000066400000000000000000000133161476627344300211410ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/Log.h000066400000000000000000000127631476627344300206130ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/ReedSolomon.cpp000066400000000000000000000055131476627344300226460ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 "ReedSolomon.h" #include #include #include #include #include #include // For memcpy extern "C" { #include "fec/fec.h" } #include #define SYMSIZE 8 ReedSolomon::ReedSolomon(int N, int K, bool reverse, int gfpoly, int firstRoot, int primElem) { setReverse(reverse); m_N = N; m_K = K; const int symsize = SYMSIZE; const int nroots = N - K; // For EDI PFT, this must be 48 const int pad = ((1 << symsize) - 1) - N; // is 255-N rsData = init_rs_char(symsize, gfpoly, firstRoot, primElem, nroots, pad); if (rsData == nullptr) { std::stringstream ss; ss << "Invalid Reed-Solomon parameters! " << "N=" << N << " ; K=" << K << " ; pad=" << pad; throw std::invalid_argument(ss.str()); } } ReedSolomon::~ReedSolomon() { if (rsData != nullptr) { free_rs_char(rsData); } } void ReedSolomon::setReverse(bool state) { reverse = state; } int ReedSolomon::encode(void* data, void* fec, size_t size) { uint8_t* input = reinterpret_cast(data); uint8_t* output = reinterpret_cast(fec); int ret = 0; if (reverse) { std::vector buffer(m_N); memcpy(&buffer[0], input, m_K); memcpy(&buffer[m_K], output, m_N - m_K); ret = decode_rs_char(rsData, &buffer[0], nullptr, 0); if ((ret != 0) && (ret != -1)) { memcpy(input, &buffer[0], m_K); memcpy(output, &buffer[m_K], m_N - m_K); } } else { encode_rs_char(rsData, input, output); } return ret; } int ReedSolomon::encode(void* data, size_t size) { uint8_t* input = reinterpret_cast(data); int ret = 0; if (reverse) { ret = decode_rs_char(rsData, input, nullptr, 0); } else { encode_rs_char(rsData, input, &input[m_K]); } return ret; } Opendigitalradio-ODR-DabMux-29c710c/lib/ReedSolomon.h000066400000000000000000000027771476627344300223240ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include #endif #include #include class ReedSolomon { public: ReedSolomon(int N, int K, bool reverse = false, int gfpoly = 0x11d, int firstRoot = 0, int primElem = 1); ReedSolomon(const ReedSolomon& other) = delete; ReedSolomon operator=(const ReedSolomon& other) = delete; ~ReedSolomon(); void setReverse(bool state); int encode(void* data, void* fec, size_t size); int encode(void* data, size_t size); private: int m_N; int m_K; void* rsData; bool reverse; }; Opendigitalradio-ODR-DabMux-29c710c/lib/RemoteControl.cpp000066400000000000000000000456741476627344300232300ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/RemoteControl.h000066400000000000000000000166351476627344300226700ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/Socket.cpp000066400000000000000000001111021476627344300216400ustar00rootroot00000000000000/* 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 #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 } TCPConnection::stats_t TCPConnection::get_stats() const { TCPConnection::stats_t s; const vector buffer_sizes = queue.map( [](const vector& vec) { return vec.size(); } ); s.buffer_fullness = std::accumulate(buffer_sizes.cbegin(), buffer_sizes.cend(), 0); s.remote_address = m_sock.get_remote_address(); return s; } 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; } } std::vector TCPDataDispatcher::get_stats() const { std::vector s; for (const auto& conn : m_connections) { s.push_back(conn.get_stats()); } return s; } 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-DabMux-29c710c/lib/Socket.h000066400000000000000000000300451476627344300213130ustar00rootroot00000000000000/* 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; } InetAddress get_remote_address() const { return m_remote_address; } 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; struct stats_t { size_t buffer_fullness = 0; InetAddress remote_address; }; stats_t get_stats() const; 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); std::vector get_stats() const; 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-DabMux-29c710c/lib/ThreadsafeQueue.h000066400000000000000000000154341476627344300231430ustar00rootroot00000000000000/* Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2025 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 #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_back(val); } else if (queue_size_before < max_size) { the_queue.push_back(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_back(std::move(val)); } else if (queue_size_before < max_size) { the_queue.emplace_back(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_front(); } the_queue.push_back(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_front(); } the_queue.emplace_back(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_back(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_front(); 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_front(); lock.unlock(); the_tx_notification.notify_one(); } } template std::vector map(std::function func) const { std::vector result; std::unique_lock lock(the_mutex); for (const T& elem : the_queue) { result.push_back(func(elem)); } return result; } private: std::deque 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-DabMux-29c710c/lib/charset/000077500000000000000000000000001476627344300213415ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/charset/README000066400000000000000000000002611476627344300222200ustar00rootroot00000000000000This UTF-8 to EBU charset (defined in ETSI TS 101 756v1.8.1) was copied from ODR-PadEnc, with utf8 library v4.0.5 from https://github.com/nemtrif/utfcpp/tree/master/source/utf8 Opendigitalradio-ODR-DabMux-29c710c/lib/charset/charset.cpp000066400000000000000000000117421476627344300235030ustar00rootroot00000000000000/* Copyright (C) 2018 Matthias P. Braendli (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 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 . */ /*! \file charset.cpp \brief A converter for UTF-8 to EBU Latin charset according to ETSI TS 101 756 Annex C, used for DLS and Labels. \author Matthias P. Braendli \author Lindsay Cornell */ #include "charset.h" #include /**********************************************/ /************* BIG FAT WARNING ****************/ /**********************************************/ /**** Make sure this file is always saved ****/ /**** encoded in UTF-8, otherwise you will ****/ /**** mess up the table below ! ****/ /**********************************************/ /********* END OF BIG FAT WARNING *************/ /**********************************************/ #define CHARSET_TABLE_OFFSET 1 // NUL at index 0 cannot be represented #define CHARSET_TABLE_ENTRIES (256 - CHARSET_TABLE_OFFSET) static const char* utf8_encoded_EBU_Latin[CHARSET_TABLE_ENTRIES] = { "Ę", "Į", "Ų", "Ă", "Ė", "Ď", "Ș", "Ț", "Ċ", "\n","\v","Ġ", "Ĺ", "Ż", "Ń", "ą", "ę", "į", "ų", "ă", "ė", "ď", "ș", "ț", "ċ", "Ň", "Ě", "ġ", "ĺ", "ż", "\u0082", " ", "!", "\"","#", "ł", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "Ů", "]", "Ł", "_", "Ą", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "«", "ů", "»", "Ľ", "Ħ", "á", "à", "é", "è", "í", "ì", "ó", "ò", "ú", "ù", "Ñ", "Ç", "Ş", "ß", "¡", "Ÿ", "â", "ä", "ê", "ë", "î", "ï", "ô", "ö", "û", "ü", "ñ", "ç", "ş", "ğ", "ı", "ÿ", "Ķ", "Ņ", "©", "Ģ", "Ğ", "ě", "ň", "ő", "Ő", "€", "£", "$", "Ā", "Ē", "Ī", "Ū", "ķ", "ņ", "Ļ", "ģ", "ļ", "İ", "ń", "ű", "Ű", "¿", "ľ", "°", "ā", "ē", "ī", "ū", "Á", "À", "É", "È", "Í", "Ì", "Ó", "Ò", "Ú", "Ù", "Ř", "Č", "Š", "Ž", "Ð", "Ŀ", "Â", "Ä", "Ê", "Ë", "Î", "Ï", "Ô", "Ö", "Û", "Ü", "ř", "č", "š", "ž", "đ", "ŀ", "Ã", "Å", "Æ", "Œ", "ŷ", "Ý", "Õ", "Ø", "Þ", "Ŋ", "Ŕ", "Ć", "Ś", "Ź", "Ť", "ð", "ã", "å", "æ", "œ", "ŵ", "ý", "õ", "ø", "þ", "ŋ", "ŕ", "ć", "ś", "ź", "ť", "ħ"}; using namespace std; CharsetConverter::CharsetConverter() { /*! Build the converstion table that contains the known code points, * at the indices corresponding to the EBU Latin table */ using namespace std; for (size_t i = 0; i < CHARSET_TABLE_ENTRIES; i++) { string table_entry(utf8_encoded_EBU_Latin[i]); string::iterator it = table_entry.begin(); uint32_t code_point = utf8::next(it, table_entry.end()); m_conversion_table.push_back(code_point); } } std::string CharsetConverter::utf8_to_ebu(std::string line_utf8, bool up_to_first_error) { string::iterator end_it; if (up_to_first_error) { // check for invalid utf-8, we only convert up to the first error end_it = utf8::find_invalid(line_utf8.begin(), line_utf8.end()); } else { end_it = line_utf8.end(); } // Convert it to utf-32 vector utf32line; utf8::utf8to32(line_utf8.begin(), end_it, back_inserter(utf32line)); string encoded_line(utf32line.size(), '0'); // Try to convert each codepoint for (size_t i = 0; i < utf32line.size(); i++) { vector::iterator iter = find(m_conversion_table.begin(), m_conversion_table.end(), utf32line[i]); if (iter != m_conversion_table.end()) { size_t index = std::distance(m_conversion_table.begin(), iter); encoded_line[i] = (char)(index + CHARSET_TABLE_OFFSET); } else { encoded_line[i] = ' '; } } return encoded_line; } std::string CharsetConverter::ebu_to_utf8(const std::string& str) { string utf8_str; for (const uint8_t c : str) { // Table offset because NUL is not represented if (c >= CHARSET_TABLE_OFFSET) { string utf8_char(utf8_encoded_EBU_Latin[c - CHARSET_TABLE_OFFSET]); utf8_str += utf8_char; } else { utf8_str += "⁇"; } } return utf8_str; } Opendigitalradio-ODR-DabMux-29c710c/lib/charset/charset.h000066400000000000000000000033661476627344300231530ustar00rootroot00000000000000/* Copyright (C) 2018 Matthias P. Braendli (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 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 . */ /*! \file charset.h \brief A converter for UTF-8 to EBU Latin charset according to ETSI TS 101 756 Annex C, used for DLS and Labels. \author Matthias P. Braendli \author Lindsay Cornell */ #pragma once #include #include #include #include "utf8.h" class CharsetConverter { public: CharsetConverter(); /*! Convert a UTF-8 encoded text line into an EBU Latin encoded byte * stream. If up_to_first_error is set, convert as much text as possible. * If false, raise an utf8::exception in case of conversion errors. */ std::string utf8_to_ebu(std::string line_utf8, bool up_to_first_error = true); /*! Convert a EBU Latin byte stream to a UTF-8 encoded string. * Invalid input characters are converted to ⁇ (unicode U+2047). */ std::string ebu_to_utf8(const std::string& str); private: // Representation of the table in 32-bit unicode std::vector m_conversion_table; }; Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8.h000066400000000000000000000037041476627344300224040ustar00rootroot00000000000000// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 /* To control the C++ language version used by the library, you can define UTF_CPP_CPLUSPLUS macro and set it to one of the values used by the __cplusplus predefined macro. For instance, #define UTF_CPP_CPLUSPLUS 199711L will cause the UTF-8 CPP library to use only types and language features available in the C++ 98 standard. Some library features will be disabled. If you leave UTF_CPP_CPLUSPLUS undefined, it will be internally assigned to __cplusplus. */ #include "utf8/checked.h" #include "utf8/unchecked.h" #endif // header guard Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/000077500000000000000000000000001476627344300222275ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/checked.h000066400000000000000000000311171476627344300237710ustar00rootroot00000000000000// Copyright 2006-2016 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include "core.h" #include namespace utf8 { // Base for the exceptions that may be thrown from the library class exception : public ::std::exception { }; // Exceptions that may be thrown from the library functions. class invalid_code_point : public exception { utfchar32_t cp; public: invalid_code_point(utfchar32_t codepoint) : cp(codepoint) {} virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid code point"; } utfchar32_t code_point() const {return cp;} }; class invalid_utf8 : public exception { utfchar8_t u8; public: invalid_utf8 (utfchar8_t u) : u8(u) {} invalid_utf8 (char c) : u8(static_cast(c)) {} virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid UTF-8"; } utfchar8_t utf8_octet() const {return u8;} }; class invalid_utf16 : public exception { utfchar16_t u16; public: invalid_utf16 (utfchar16_t u) : u16(u) {} virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Invalid UTF-16"; } utfchar16_t utf16_word() const {return u16;} }; class not_enough_room : public exception { public: virtual const char* what() const UTF_CPP_NOEXCEPT UTF_CPP_OVERRIDE { return "Not enough space"; } }; /// The library API - functions intended to be called by the users template octet_iterator append(utfchar32_t cp, octet_iterator result) { if (!utf8::internal::is_code_point_valid(cp)) throw invalid_code_point(cp); return internal::append(cp, result); } inline void append(utfchar32_t cp, std::string& s) { append(cp, std::back_inserter(s)); } template word_iterator append16(utfchar32_t cp, word_iterator result) { if (!utf8::internal::is_code_point_valid(cp)) throw invalid_code_point(cp); return internal::append16(cp, result); } template output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) { while (start != end) { octet_iterator sequence_start = start; internal::utf_error err_code = utf8::internal::validate_next(start, end); switch (err_code) { case internal::UTF8_OK : for (octet_iterator it = sequence_start; it != start; ++it) *out++ = *it; break; case internal::NOT_ENOUGH_ROOM: out = utf8::append (replacement, out); start = end; break; case internal::INVALID_LEAD: out = utf8::append (replacement, out); ++start; break; case internal::INCOMPLETE_SEQUENCE: case internal::OVERLONG_SEQUENCE: case internal::INVALID_CODE_POINT: out = utf8::append (replacement, out); ++start; // just one replacement mark for the sequence while (start != end && utf8::internal::is_trail(*start)) ++start; break; } } return out; } template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); return utf8::replace_invalid(start, end, out, replacement_marker); } inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) { std::string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); return result; } inline std::string replace_invalid(const std::string& s) { std::string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result)); return result; } template utfchar32_t next(octet_iterator& it, octet_iterator end) { utfchar32_t cp = 0; internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); switch (err_code) { case internal::UTF8_OK : break; case internal::NOT_ENOUGH_ROOM : throw not_enough_room(); case internal::INVALID_LEAD : case internal::INCOMPLETE_SEQUENCE : case internal::OVERLONG_SEQUENCE : throw invalid_utf8(static_cast(*it)); case internal::INVALID_CODE_POINT : throw invalid_code_point(cp); } return cp; } template utfchar32_t next16(word_iterator& it, word_iterator end) { utfchar32_t cp = 0; internal::utf_error err_code = utf8::internal::validate_next16(it, end, cp); if (err_code == internal::NOT_ENOUGH_ROOM) throw not_enough_room(); return cp; } template utfchar32_t peek_next(octet_iterator it, octet_iterator end) { return utf8::next(it, end); } template utfchar32_t prior(octet_iterator& it, octet_iterator start) { // can't do much if it == start if (it == start) throw not_enough_room(); octet_iterator end = it; // Go back until we hit either a lead octet or start while (utf8::internal::is_trail(*(--it))) if (it == start) throw invalid_utf8(*it); // error - no lead byte in the sequence return utf8::peek_next(it, end); } template void advance (octet_iterator& it, distance_type n, octet_iterator end) { const distance_type zero(0); if (n < zero) { // backward for (distance_type i = n; i < zero; ++i) utf8::prior(it, end); } else { // forward for (distance_type i = zero; i < n; ++i) utf8::next(it, end); } } template typename std::iterator_traits::difference_type distance (octet_iterator first, octet_iterator last) { typename std::iterator_traits::difference_type dist; for (dist = 0; first < last; ++dist) utf8::next(first, last); return dist; } template octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { utfchar32_t cp = utf8::internal::mask16(*start++); // Take care of surrogate pairs first if (utf8::internal::is_lead_surrogate(cp)) { if (start != end) { const utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); if (utf8::internal::is_trail_surrogate(trail_surrogate)) cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; else throw invalid_utf16(static_cast(trail_surrogate)); } else throw invalid_utf16(static_cast(cp)); } // Lone trail surrogate else if (utf8::internal::is_trail_surrogate(cp)) throw invalid_utf16(static_cast(cp)); result = utf8::append(cp, result); } return result; } template u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) { while (start < end) { const utfchar32_t cp = utf8::next(start, end); if (cp > 0xffff) { //make a surrogate pair *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); } else *result++ = static_cast(cp); } return result; } template octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) { while (start != end) result = utf8::append(*(start++), result); return result; } template u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) { while (start < end) (*result++) = utf8::next(start, end); return result; } // The iterator class template class iterator { octet_iterator it; octet_iterator range_start; octet_iterator range_end; public: typedef utfchar32_t value_type; typedef utfchar32_t* pointer; typedef utfchar32_t& reference; typedef std::ptrdiff_t difference_type; typedef std::bidirectional_iterator_tag iterator_category; iterator () {} explicit iterator (const octet_iterator& octet_it, const octet_iterator& rangestart, const octet_iterator& rangeend) : it(octet_it), range_start(rangestart), range_end(rangeend) { if (it < range_start || it > range_end) throw std::out_of_range("Invalid utf-8 iterator position"); } // the default "big three" are OK octet_iterator base () const { return it; } utfchar32_t operator * () const { octet_iterator temp = it; return utf8::next(temp, range_end); } bool operator == (const iterator& rhs) const { if (range_start != rhs.range_start || range_end != rhs.range_end) throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); return (it == rhs.it); } bool operator != (const iterator& rhs) const { return !(operator == (rhs)); } iterator& operator ++ () { utf8::next(it, range_end); return *this; } iterator operator ++ (int) { iterator temp = *this; utf8::next(it, range_end); return temp; } iterator& operator -- () { utf8::prior(it, range_start); return *this; } iterator operator -- (int) { iterator temp = *this; utf8::prior(it, range_start); return temp; } }; // class iterator } // namespace utf8 #if UTF_CPP_CPLUSPLUS >= 202002L // C++ 20 or later #include "cpp20.h" #elif UTF_CPP_CPLUSPLUS >= 201703L // C++ 17 or later #include "cpp17.h" #elif UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later #include "cpp11.h" #endif // C++ 11 or later #endif //header guard Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/core.h000066400000000000000000000411361476627344300233350ustar00rootroot00000000000000// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include #include #include // Determine the C++ standard version. // If the user defines UTF_CPP_CPLUSPLUS, use that. // Otherwise, trust the unreliable predefined macro __cplusplus #if !defined UTF_CPP_CPLUSPLUS #define UTF_CPP_CPLUSPLUS __cplusplus #endif #if UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later #define UTF_CPP_OVERRIDE override #define UTF_CPP_NOEXCEPT noexcept #else // C++ 98/03 #define UTF_CPP_OVERRIDE #define UTF_CPP_NOEXCEPT throw() #endif // C++ 11 or later namespace utf8 { // The typedefs for 8-bit, 16-bit and 32-bit code units #if UTF_CPP_CPLUSPLUS >= 201103L // C++ 11 or later #if UTF_CPP_CPLUSPLUS >= 202002L // C++ 20 or later typedef char8_t utfchar8_t; #else // C++ 11/14/17 typedef unsigned char utfchar8_t; #endif typedef char16_t utfchar16_t; typedef char32_t utfchar32_t; #else // C++ 98/03 typedef unsigned char utfchar8_t; typedef unsigned short utfchar16_t; typedef unsigned int utfchar32_t; #endif // C++ 11 or later // Helper code - not intended to be directly called by the library users. May be changed at any time namespace internal { // Unicode constants // Leading (high) surrogates: 0xd800 - 0xdbff // Trailing (low) surrogates: 0xdc00 - 0xdfff const utfchar16_t LEAD_SURROGATE_MIN = 0xd800u; const utfchar16_t LEAD_SURROGATE_MAX = 0xdbffu; const utfchar16_t TRAIL_SURROGATE_MIN = 0xdc00u; const utfchar16_t TRAIL_SURROGATE_MAX = 0xdfffu; const utfchar16_t LEAD_OFFSET = 0xd7c0u; // LEAD_SURROGATE_MIN - (0x10000 >> 10) const utfchar32_t SURROGATE_OFFSET = 0xfca02400u; // 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN // Maximum valid value for a Unicode code point const utfchar32_t CODE_POINT_MAX = 0x0010ffffu; template inline utfchar8_t mask8(octet_type oc) { return static_cast(0xff & oc); } template inline utfchar16_t mask16(u16_type oc) { return static_cast(0xffff & oc); } template inline bool is_trail(octet_type oc) { return ((utf8::internal::mask8(oc) >> 6) == 0x2); } inline bool is_lead_surrogate(utfchar32_t cp) { return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); } inline bool is_trail_surrogate(utfchar32_t cp) { return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); } inline bool is_surrogate(utfchar32_t cp) { return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); } inline bool is_code_point_valid(utfchar32_t cp) { return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); } inline bool is_in_bmp(utfchar32_t cp) { return cp < utfchar32_t(0x10000); } template int sequence_length(octet_iterator lead_it) { const utfchar8_t lead = utf8::internal::mask8(*lead_it); if (lead < 0x80) return 1; else if ((lead >> 5) == 0x6) return 2; else if ((lead >> 4) == 0xe) return 3; else if ((lead >> 3) == 0x1e) return 4; else return 0; } inline bool is_overlong_sequence(utfchar32_t cp, int length) { if (cp < 0x80) { if (length != 1) return true; } else if (cp < 0x800) { if (length != 2) return true; } else if (cp < 0x10000) { if (length != 3) return true; } return false; } enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; /// Helper for get_sequence_x template utf_error increase_safely(octet_iterator& it, const octet_iterator end) { if (++it == end) return NOT_ENOUGH_ROOM; if (!utf8::internal::is_trail(*it)) return INCOMPLETE_SEQUENCE; return UTF8_OK; } #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} /// get_sequence_x functions decode utf-8 sequences of the length x template utf_error get_sequence_1(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); return UTF8_OK; } template utf_error get_sequence_2(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); return UTF8_OK; } template utf_error get_sequence_3(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point += (*it) & 0x3f; return UTF8_OK; } template utf_error get_sequence_4(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; code_point = utf8::internal::mask8(*it); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) code_point += (*it) & 0x3f; return UTF8_OK; } #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR template utf_error validate_next(octet_iterator& it, octet_iterator end, utfchar32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; // Save the original value of it so we can go back in case of failure // Of course, it does not make much sense with i.e. stream iterators octet_iterator original_it = it; utfchar32_t cp = 0; // Determine the sequence length based on the lead octet const int length = utf8::internal::sequence_length(it); // Get trail octets and calculate the code point utf_error err = UTF8_OK; switch (length) { case 0: return INVALID_LEAD; case 1: err = utf8::internal::get_sequence_1(it, end, cp); break; case 2: err = utf8::internal::get_sequence_2(it, end, cp); break; case 3: err = utf8::internal::get_sequence_3(it, end, cp); break; case 4: err = utf8::internal::get_sequence_4(it, end, cp); break; } if (err == UTF8_OK) { // Decoding succeeded. Now, security checks... if (utf8::internal::is_code_point_valid(cp)) { if (!utf8::internal::is_overlong_sequence(cp, length)){ // Passed! Return here. code_point = cp; ++it; return UTF8_OK; } else err = OVERLONG_SEQUENCE; } else err = INVALID_CODE_POINT; } // Failure branch - restore the original value of the iterator it = original_it; return err; } template inline utf_error validate_next(octet_iterator& it, octet_iterator end) { utfchar32_t ignored; return utf8::internal::validate_next(it, end, ignored); } template utf_error validate_next16(word_iterator& it, word_iterator end, utfchar32_t& code_point) { if (it == end) return NOT_ENOUGH_ROOM; // Save the original value of it so we can go back in case of failure // Of course, it does not make much sense with i.e. stream iterators word_iterator original_it = it; utf_error err = UTF8_OK; const utfchar16_t first_word = *it++; if (!is_surrogate(first_word)) { code_point = first_word; return UTF8_OK; } else { if (it == end) err = NOT_ENOUGH_ROOM; else if (is_lead_surrogate(first_word)) { const utfchar16_t second_word = *it++; if (is_trail_surrogate(second_word)) { code_point = (first_word << 10) + second_word + SURROGATE_OFFSET; return UTF8_OK; } else err = INCOMPLETE_SEQUENCE; } else { err = INVALID_LEAD; } } // error branch it = original_it; return err; } // Internal implementation of both checked and unchecked append() function // This function will be invoked by the overloads below, as they will know // the octet_type. template octet_iterator append(utfchar32_t cp, octet_iterator result) { if (cp < 0x80) // one octet *(result++) = static_cast(cp); else if (cp < 0x800) { // two octets *(result++) = static_cast((cp >> 6) | 0xc0); *(result++) = static_cast((cp & 0x3f) | 0x80); } else if (cp < 0x10000) { // three octets *(result++) = static_cast((cp >> 12) | 0xe0); *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); *(result++) = static_cast((cp & 0x3f) | 0x80); } else { // four octets *(result++) = static_cast((cp >> 18) | 0xf0); *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); *(result++) = static_cast((cp & 0x3f) | 0x80); } return result; } // One of the following overloads will be invoked from the API calls // A simple (but dangerous) case: the caller appends byte(s) to a char array inline char* append(utfchar32_t cp, char* result) { return append(cp, result); } // Hopefully, most common case: the caller uses back_inserter // i.e. append(cp, std::back_inserter(str)); template std::back_insert_iterator append (utfchar32_t cp, std::back_insert_iterator result) { return append, typename container_type::value_type>(cp, result); } // The caller uses some other kind of output operator - not covered above // Note that in this case we are not able to determine octet_type // so we assume it's utfchar8_t; that can cause a conversion warning if we are wrong. template octet_iterator append(utfchar32_t cp, octet_iterator result) { return append(cp, result); } // Internal implementation of both checked and unchecked append16() function // This function will be invoked by the overloads below, as they will know // the word_type. template word_iterator append16(utfchar32_t cp, word_iterator result) { if (is_in_bmp(cp)) *(result++) = static_cast(cp); else { // Code points from the supplementary planes are encoded via surrogate pairs *(result++) = static_cast(LEAD_OFFSET + (cp >> 10)); *(result++) = static_cast(TRAIL_SURROGATE_MIN + (cp & 0x3FF)); } return result; } // Hopefully, most common case: the caller uses back_inserter // i.e. append16(cp, std::back_inserter(str)); template std::back_insert_iterator append16 (utfchar32_t cp, std::back_insert_iterator result) { return append16, typename container_type::value_type>(cp, result); } // The caller uses some other kind of output operator - not covered above // Note that in this case we are not able to determine word_type // so we assume it's utfchar16_t; that can cause a conversion warning if we are wrong. template word_iterator append16(utfchar32_t cp, word_iterator result) { return append16(cp, result); } } // namespace internal /// The library API - functions intended to be called by the users // Byte order mark const utfchar8_t bom[] = {0xef, 0xbb, 0xbf}; template octet_iterator find_invalid(octet_iterator start, octet_iterator end) { octet_iterator result = start; while (result != end) { utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); if (err_code != internal::UTF8_OK) return result; } return result; } inline const char* find_invalid(const char* str) { const char* end = str + std::strlen(str); return find_invalid(str, end); } inline std::size_t find_invalid(const std::string& s) { std::string::const_iterator invalid = find_invalid(s.begin(), s.end()); return (invalid == s.end()) ? std::string::npos : static_cast(invalid - s.begin()); } template inline bool is_valid(octet_iterator start, octet_iterator end) { return (utf8::find_invalid(start, end) == end); } inline bool is_valid(const char* str) { return (*(utf8::find_invalid(str)) == '\0'); } inline bool is_valid(const std::string& s) { return is_valid(s.begin(), s.end()); } template inline bool starts_with_bom (octet_iterator it, octet_iterator end) { return ( ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) ); } inline bool starts_with_bom(const std::string& s) { return starts_with_bom(s.begin(), s.end()); } } // namespace utf8 #endif // header guard Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/cpp11.h000066400000000000000000000045361476627344300233340ustar00rootroot00000000000000// Copyright 2018 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_a184c22c_d012_11e8_a8d5_f2801f1b9fd1 #define UTF8_FOR_CPP_a184c22c_d012_11e8_a8d5_f2801f1b9fd1 #include "checked.h" namespace utf8 { inline void append16(utfchar32_t cp, std::u16string& s) { append16(cp, std::back_inserter(s)); } inline std::string utf16to8(const std::u16string& s) { std::string result; utf16to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u16string utf8to16(const std::string& s) { std::u16string result; utf8to16(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::string utf32to8(const std::u32string& s) { std::string result; utf32to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u32string utf8to32(const std::string& s) { std::u32string result; utf8to32(s.begin(), s.end(), std::back_inserter(result)); return result; } } // namespace utf8 #endif // header guard Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/cpp17.h000066400000000000000000000061571476627344300233430ustar00rootroot00000000000000// Copyright 2018 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_7e906c01_03a3_4daf_b420_ea7ea952b3c9 #define UTF8_FOR_CPP_7e906c01_03a3_4daf_b420_ea7ea952b3c9 #include "cpp11.h" namespace utf8 { inline std::string utf16to8(std::u16string_view s) { std::string result; utf16to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u16string utf8to16(std::string_view s) { std::u16string result; utf8to16(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::string utf32to8(std::u32string_view s) { std::string result; utf32to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u32string utf8to32(std::string_view s) { std::u32string result; utf8to32(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::size_t find_invalid(std::string_view s) { std::string_view::const_iterator invalid = find_invalid(s.begin(), s.end()); return (invalid == s.end()) ? std::string_view::npos : static_cast(invalid - s.begin()); } inline bool is_valid(std::string_view s) { return is_valid(s.begin(), s.end()); } inline std::string replace_invalid(std::string_view s, char32_t replacement) { std::string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); return result; } inline std::string replace_invalid(std::string_view s) { std::string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result)); return result; } inline bool starts_with_bom(std::string_view s) { return starts_with_bom(s.begin(), s.end()); } } // namespace utf8 #endif // header guard Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/cpp20.h000066400000000000000000000076571476627344300233430ustar00rootroot00000000000000// Copyright 2022 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_207e906c01_03a3_4daf_b420_ea7ea952b3c9 #define UTF8_FOR_CPP_207e906c01_03a3_4daf_b420_ea7ea952b3c9 #include "cpp17.h" namespace utf8 { inline std::u8string utf16tou8(const std::u16string& s) { std::u8string result; utf16to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u8string utf16tou8(std::u16string_view s) { std::u8string result; utf16to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u16string utf8to16(const std::u8string& s) { std::u16string result; utf8to16(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u16string utf8to16(const std::u8string_view& s) { std::u16string result; utf8to16(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u8string utf32tou8(const std::u32string& s) { std::u8string result; utf32to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u8string utf32tou8(const std::u32string_view& s) { std::u8string result; utf32to8(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u32string utf8to32(const std::u8string& s) { std::u32string result; utf8to32(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::u32string utf8to32(const std::u8string_view& s) { std::u32string result; utf8to32(s.begin(), s.end(), std::back_inserter(result)); return result; } inline std::size_t find_invalid(const std::u8string& s) { std::u8string::const_iterator invalid = find_invalid(s.begin(), s.end()); return (invalid == s.end()) ? std::string_view::npos : static_cast(invalid - s.begin()); } inline bool is_valid(const std::u8string& s) { return is_valid(s.begin(), s.end()); } inline std::u8string replace_invalid(const std::u8string& s, char32_t replacement) { std::u8string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); return result; } inline std::u8string replace_invalid(const std::u8string& s) { std::u8string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result)); return result; } inline bool starts_with_bom(const std::u8string& s) { return starts_with_bom(s.begin(), s.end()); } } // namespace utf8 #endif // header guard Opendigitalradio-ODR-DabMux-29c710c/lib/charset/utf8/unchecked.h000066400000000000000000000253251476627344300243400ustar00rootroot00000000000000// Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 #include "core.h" namespace utf8 { namespace unchecked { template octet_iterator append(utfchar32_t cp, octet_iterator result) { return internal::append(cp, result); } template word_iterator append16(utfchar32_t cp, word_iterator result) { return internal::append16(cp, result); } template output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, utfchar32_t replacement) { while (start != end) { octet_iterator sequence_start = start; internal::utf_error err_code = utf8::internal::validate_next(start, end); switch (err_code) { case internal::UTF8_OK : for (octet_iterator it = sequence_start; it != start; ++it) *out++ = *it; break; case internal::NOT_ENOUGH_ROOM: out = utf8::unchecked::append(replacement, out); start = end; break; case internal::INVALID_LEAD: out = utf8::unchecked::append(replacement, out); ++start; break; case internal::INCOMPLETE_SEQUENCE: case internal::OVERLONG_SEQUENCE: case internal::INVALID_CODE_POINT: out = utf8::unchecked::append(replacement, out); ++start; // just one replacement mark for the sequence while (start != end && utf8::internal::is_trail(*start)) ++start; break; } } return out; } template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { static const utfchar32_t replacement_marker = utf8::internal::mask16(0xfffd); return utf8::unchecked::replace_invalid(start, end, out, replacement_marker); } inline std::string replace_invalid(const std::string& s, utfchar32_t replacement) { std::string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result), replacement); return result; } inline std::string replace_invalid(const std::string& s) { std::string result; replace_invalid(s.begin(), s.end(), std::back_inserter(result)); return result; } template utfchar32_t next(octet_iterator& it) { utfchar32_t cp = utf8::internal::mask8(*it); switch (utf8::internal::sequence_length(it)) { case 1: break; case 2: it++; cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); break; case 3: ++it; cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); ++it; cp += (*it) & 0x3f; break; case 4: ++it; cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); ++it; cp += (utf8::internal::mask8(*it) << 6) & 0xfff; ++it; cp += (*it) & 0x3f; break; } ++it; return cp; } template utfchar32_t peek_next(octet_iterator it) { return utf8::unchecked::next(it); } template utfchar32_t next16(word_iterator& it) { utfchar32_t cp = utf8::internal::mask16(*it++); if (utf8::internal::is_lead_surrogate(cp)) return (cp << 10) + *it++ + utf8::internal::SURROGATE_OFFSET; return cp; } template utfchar32_t prior(octet_iterator& it) { while (utf8::internal::is_trail(*(--it))) ; octet_iterator temp = it; return utf8::unchecked::next(temp); } template void advance(octet_iterator& it, distance_type n) { const distance_type zero(0); if (n < zero) { // backward for (distance_type i = n; i < zero; ++i) utf8::unchecked::prior(it); } else { // forward for (distance_type i = zero; i < n; ++i) utf8::unchecked::next(it); } } template typename std::iterator_traits::difference_type distance(octet_iterator first, octet_iterator last) { typename std::iterator_traits::difference_type dist; for (dist = 0; first < last; ++dist) utf8::unchecked::next(first); return dist; } template octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { utfchar32_t cp = utf8::internal::mask16(*start++); // Take care of surrogate pairs first if (utf8::internal::is_lead_surrogate(cp)) { if (start == end) return result; utfchar32_t trail_surrogate = utf8::internal::mask16(*start++); cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; } result = utf8::unchecked::append(cp, result); } return result; } template u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) { while (start < end) { utfchar32_t cp = utf8::unchecked::next(start); if (cp > 0xffff) { //make a surrogate pair *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); } else *result++ = static_cast(cp); } return result; } template octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) { while (start != end) result = utf8::unchecked::append(*(start++), result); return result; } template u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) { while (start < end) (*result++) = utf8::unchecked::next(start); return result; } // The iterator class template class iterator { octet_iterator it; public: typedef utfchar32_t value_type; typedef utfchar32_t* pointer; typedef utfchar32_t& reference; typedef std::ptrdiff_t difference_type; typedef std::bidirectional_iterator_tag iterator_category; iterator () {} explicit iterator (const octet_iterator& octet_it): it(octet_it) {} // the default "big three" are OK octet_iterator base () const { return it; } utfchar32_t operator * () const { octet_iterator temp = it; return utf8::unchecked::next(temp); } bool operator == (const iterator& rhs) const { return (it == rhs.it); } bool operator != (const iterator& rhs) const { return !(operator == (rhs)); } iterator& operator ++ () { ::std::advance(it, utf8::internal::sequence_length(it)); return *this; } iterator operator ++ (int) { iterator temp = *this; ::std::advance(it, utf8::internal::sequence_length(it)); return temp; } iterator& operator -- () { utf8::unchecked::prior(it); return *this; } iterator operator -- (int) { iterator temp = *this; utf8::unchecked::prior(it); return temp; } }; // class iterator } // namespace utf8::unchecked } // namespace utf8 #endif // header guard Opendigitalradio-ODR-DabMux-29c710c/lib/crc.c000066400000000000000000000241661476627344300206340ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/crc.h000066400000000000000000000031171476627344300206320ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/edi/000077500000000000000000000000001476627344300204515ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/edi/PFT.cpp000066400000000000000000000450661476627344300216210ustar00rootroot00000000000000/* ------------------------------------------------------------------ * 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-DabMux-29c710c/lib/edi/PFT.hpp000066400000000000000000000124141476627344300216150ustar00rootroot00000000000000/* ------------------------------------------------------------------ * 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-DabMux-29c710c/lib/edi/STIDecoder.cpp000066400000000000000000000155241476627344300231110ustar00rootroot00000000000000/* 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 "STIDecoder.hpp" #include "buffer_unpack.hpp" #include "crc.h" #include "Log.h" #include #include #include namespace EdiDecoder { using namespace std; STIDecoder::STIDecoder(STIDataCollector& data_collector) : m_data_collector(data_collector), m_dispatcher(std::bind(&STIDecoder::packet_completed, this)) { using std::placeholders::_1; using std::placeholders::_2; m_dispatcher.register_tag("*ptr", std::bind(&STIDecoder::decode_starptr, this, _1, _2)); m_dispatcher.register_tag("dsti", std::bind(&STIDecoder::decode_dsti, this, _1, _2)); m_dispatcher.register_tag("ss", std::bind(&STIDecoder::decode_ssn, this, _1, _2)); m_dispatcher.register_tag("*dmy", std::bind(&STIDecoder::decode_stardmy, this, _1, _2)); m_dispatcher.register_tag("ODRa", std::bind(&STIDecoder::decode_odraudiolevel, this, _1, _2)); m_dispatcher.register_tag("ODRv", std::bind(&STIDecoder::decode_odrversion, this, _1, _2)); } void STIDecoder::set_verbose(bool verbose) { m_dispatcher.set_verbose(verbose); } void STIDecoder::push_bytes(const vector &buf) { m_dispatcher.push_bytes(buf); } void STIDecoder::push_packet(Packet &pack) { m_dispatcher.push_packet(pack); } void STIDecoder::setMaxDelay(int num_af_packets) { m_dispatcher.setMaxDelay(num_af_packets); } void STIDecoder::filter_stream_index(bool enable, uint16_t index) { m_filter_stream = enable; m_filtered_stream_index = index; } #define AFPACKET_HEADER_LEN 10 // includes SYNC bool STIDecoder::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 STIDecoder::decode_dsti(const std::vector& value, const tag_name_t& /*n*/) { size_t offset = 0; const uint16_t dstiHeader = read_16b(value.begin() + offset); offset += 2; sti_management_data md; md.stihf = (dstiHeader >> 15) & 0x1; md.atstf = (dstiHeader >> 14) & 0x1; md.rfadf = (dstiHeader >> 13) & 0x1; uint8_t dfcth = (dstiHeader >> 8) & 0x1F; uint8_t dfctl = dstiHeader & 0xFF; md.dlfc = dfcth * 250 + dfctl; // modulo 5000 counter const size_t expected_length = 2 + (md.stihf ? 3 : 0) + (md.atstf ? 1 + 4 + 3 : 0) + (md.rfadf ? 9 : 0); if (value.size() != expected_length) { etiLog.level(warn) << "EDI dsti: decoding error: " << "value.size() != expected_length: " << value.size() << " " << expected_length << " " << (md.stihf ? "STIHF " : " ") << (md.atstf ? "ATSTF " : " ") << (md.rfadf ? "RFADF " : " "); return false; } if (md.stihf) { const uint8_t stat = value[offset++]; const uint16_t spid = read_16b(value.begin() + offset); m_data_collector.update_stat(stat, spid); offset += 2; } if (md.atstf) { uint8_t utco = value[offset]; offset++; uint32_t seconds = read_32b(value.begin() + offset); offset += 4; m_data_collector.update_edi_time(utco, seconds); md.tsta = read_24b(value.begin() + offset); offset += 3; } else { // Null timestamp, ETSI ETS 300 799, C.2.2 md.tsta = 0xFFFFFF; } if (md.rfadf) { std::array rfad; copy(value.cbegin() + offset, value.cbegin() + offset + 9, rfad.begin()); offset += 9; m_data_collector.update_rfad(rfad); } m_data_collector.update_sti_management(md); return true; } bool STIDecoder::decode_ssn(const std::vector& value, const tag_name_t& name) { sti_payload_data sti; uint16_t n = 0; n = (uint16_t)(name[2]) << 8; n |= (uint16_t)(name[3]); if (n == 0) { etiLog.level(warn) << "EDI: Stream index SSnn tag is zero"; } if (m_filter_stream and m_filtered_stream_index != n) { return true; } sti.stream_index = n - 1; // n is 1-indexed sti.rfa = value[0] >> 3; sti.tid = value[0] & 0x07; uint16_t istc = read_24b(value.begin() + 1); sti.tidext = istc >> 13; sti.crcstf = (istc >> 12) & 0x1; sti.stid = istc & 0xFFF; if (sti.rfa != 0) { etiLog.level(warn) << "EDI: rfa field in SSnn tag non-null"; } copy( value.cbegin() + 3, value.cend(), back_inserter(sti.istd)); m_data_collector.add_payload(move(sti)); return true; } bool STIDecoder::decode_stardmy(const std::vector&, const tag_name_t&) { return true; } bool STIDecoder::decode_odraudiolevel(const std::vector& value, const tag_name_t& /*n*/) { constexpr size_t expected_length = 2 * sizeof(int16_t); audio_level_data audio_level; if (value.size() == expected_length) { audio_level.left = read_16b(value.begin()); audio_level.right = read_16b(value.begin() + 2); } else { audio_level.left = 0; audio_level.right = 0; etiLog.level(warn) << "EDI: ODR AudioLevel TAG has wrong length!"; } m_data_collector.update_audio_levels(audio_level); // Not being able to decode the audio level is a soft-failure, it should // not disrupt decoding the actual audio data. return true; } bool STIDecoder::decode_odrversion(const std::vector& value, const tag_name_t& /*n*/) { const auto vd = parse_odr_version_data(value); m_data_collector.update_odr_version(vd); return true; } void STIDecoder::packet_completed() { auto seq = m_dispatcher.get_seq_info(); m_data_collector.assemble(seq); } } Opendigitalradio-ODR-DabMux-29c710c/lib/edi/STIDecoder.hpp000066400000000000000000000113071476627344300231110ustar00rootroot00000000000000/* 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 "common.hpp" #include #include #include #include #include namespace EdiDecoder { // Information for STI-D Management struct sti_management_data { bool stihf; bool atstf; bool rfadf; uint16_t dlfc; uint32_t tsta; }; // Information for a subchannel available in EDI struct sti_payload_data { uint16_t stream_index; uint8_t rfa; uint8_t tid; uint8_t tidext; bool crcstf; uint16_t stid; std::vector istd; // Return the length of ISTD in bytes uint16_t stl(void) const { return istd.size(); } }; struct audio_level_data { int16_t left = 0; int16_t right = 0; }; /* A class that receives STI data must implement the interface described * in the STIDataCollector. This can be e.g. a converter to ETI, or something that * prepares data structures for a modulator. */ class STIDataCollector { 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; // STAT error field and service provider ID virtual void update_stat(uint8_t stat, uint16_t spid) = 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_rfad(std::array rfad) = 0; virtual void update_sti_management(const sti_management_data& data) = 0; virtual void add_payload(sti_payload_data&& payload) = 0; virtual void update_audio_levels(const audio_level_data& data) = 0; virtual void update_odr_version(const odr_version_data& data) = 0; virtual void assemble(seq_info_t sequences) = 0; }; /* The STIDecoder 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 STIDecoder { public: STIDecoder(STIDataCollector& 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). 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(Packet &pack); /* Set the maximum delay in number of AF Packets before we * abandon decoding a given pseq. */ void setMaxDelay(int num_af_packets); /* Enable/disable stream-index filtering. * index==0 is out of spec, but some encoders do it anyway. */ void filter_stream_index(bool enable, uint16_t index); private: bool decode_starptr(const std::vector& value, const tag_name_t& n); bool decode_dsti(const std::vector& value, const tag_name_t& n); bool decode_ssn(const std::vector& value, const tag_name_t& n); bool decode_stardmy(const std::vector& value, const tag_name_t& n); bool decode_odraudiolevel(const std::vector& value, const tag_name_t& n); bool decode_odrversion(const std::vector& value, const tag_name_t& n); void packet_completed(); STIDataCollector& m_data_collector; TagDispatcher m_dispatcher; bool m_filter_stream = false; uint16_t m_filtered_stream_index = 1; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edi/STIWriter.cpp000066400000000000000000000067201476627344300230160ustar00rootroot00000000000000/* Copyright (C) 2019 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 "STIWriter.hpp" #include "crc.h" #include "Log.h" #include #include #include #include #include #include #include #include namespace EdiDecoder { using namespace std; STIWriter::STIWriter(std::function&& frame_callback) : m_frame_callback(move(frame_callback)) { } void STIWriter::update_protocol( const std::string& proto, uint16_t major, uint16_t minor) { m_proto_valid = (proto == "DSTI" and major == 0 and minor == 0); if (not m_proto_valid) { throw std::invalid_argument("Wrong EDI protocol"); } } void STIWriter::update_stat(uint8_t stat, uint16_t spid) { m_stat = stat; m_spid = spid; m_stat_valid = true; if (m_stat != 0xFF) { etiLog.log(warn, "STI errorlevel %02x", m_stat); } } void STIWriter::update_rfad(std::array rfad) { (void)rfad; } void STIWriter::update_sti_management(const sti_management_data& data) { m_management_data = data; m_management_data_valid = true; } void STIWriter::add_payload(sti_payload_data&& payload) { m_payload = move(payload); m_payload_valid = true; } void STIWriter::update_audio_levels(const audio_level_data& data) { m_audio_levels = data; } void STIWriter::update_odr_version(const odr_version_data& data) { m_version_data = data; } void STIWriter::update_edi_time( uint32_t utco, uint32_t seconds) { if (not m_proto_valid) { throw std::runtime_error("Cannot update time before protocol"); } m_utco = utco; m_seconds = seconds; // TODO check validity m_time_valid = true; } void STIWriter::assemble(seq_info_t seq) { if (not m_proto_valid) { throw std::runtime_error("Cannot assemble STI before protocol"); } if (not m_management_data_valid) { throw std::runtime_error("Cannot assemble STI before management data"); } if (not m_payload_valid) { throw std::runtime_error("Cannot assemble STI without frame data"); } // TODO check time validity sti_frame_t stiFrame; stiFrame.dlfc = m_management_data.dlfc; stiFrame.frame = move(m_payload.istd); stiFrame.timestamp.seconds = m_seconds; stiFrame.timestamp.utco = m_utco; stiFrame.timestamp.tsta = m_management_data.tsta; stiFrame.audio_levels = m_audio_levels; stiFrame.version_data = m_version_data; stiFrame.sequence_counters = seq; m_frame_callback(move(stiFrame)); m_proto_valid = false; m_management_data_valid = false; m_stat_valid = false; m_time_valid = false; m_payload_valid = false; } } Opendigitalradio-ODR-DabMux-29c710c/lib/edi/STIWriter.hpp000066400000000000000000000055461476627344300230300ustar00rootroot00000000000000/* Copyright (C) 2019 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 "common.hpp" #include "STIDecoder.hpp" #include #include #include #include #include namespace EdiDecoder { struct sti_frame_t { std::vector frame; uint16_t dlfc; frame_timestamp_t timestamp; audio_level_data audio_levels; odr_version_data version_data; seq_info_t sequence_counters; }; class STIWriter : public STIDataCollector { public: // The callback gets called for every STI frame that gets assembled STIWriter(std::function&& frame_callback); // 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; virtual void update_stat(uint8_t stat, uint16_t spid) override; virtual void update_edi_time( uint32_t utco, uint32_t seconds) override; virtual void update_rfad(std::array rfad) override; virtual void update_sti_management(const sti_management_data& data) override; virtual void add_payload(sti_payload_data&& payload) override; virtual void update_audio_levels(const audio_level_data& data) override; virtual void update_odr_version(const odr_version_data& data) override; virtual void assemble(seq_info_t seq) override; private: std::function m_frame_callback; bool m_proto_valid = false; bool m_management_data_valid = false; sti_management_data m_management_data; bool m_stat_valid = false; uint8_t m_stat = 0; uint16_t m_spid = 0; bool m_time_valid = false; uint32_t m_utco = 0; uint32_t m_seconds = 0; bool m_payload_valid = false; sti_payload_data m_payload; audio_level_data m_audio_levels; odr_version_data m_version_data; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edi/buffer_unpack.hpp000066400000000000000000000031011476627344300237670ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/edi/common.cpp000066400000000000000000000333641476627344300224560ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/edi/common.hpp000066400000000000000000000133631476627344300224600ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/edioutput/000077500000000000000000000000001476627344300217325ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/AFPacket.cpp000066400000000000000000000054651476627344300240660ustar00rootroot00000000000000/* Copyright (C) 2021 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output. This implements an AF Packet as defined ETSI TS 102 821. Also see ETSI TS 102 693 */ /* 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 "crc.h" #include "AFPacket.h" #include "TagItems.h" #include "TagPacket.h" #include #include #include #include #include #include namespace edi { // Header PT field. AF packet contains TAG payload const uint8_t AFHEADER_PT_TAG = 'T'; // AF Packet Major (3 bits) and Minor (4 bits) version const uint8_t AFHEADER_VERSION = 0x10; // MAJ=1, MIN=0 AFPacket AFPacketiser::Assemble(TagPacket tag_packet) { std::vector payload = tag_packet.Assemble(); if (m_verbose) std::cerr << "Assemble AFPacket " << m_seq << std::endl; std::string pack_data("AF"); // SYNC std::vector packet(pack_data.begin(), pack_data.end()); uint32_t taglength = payload.size(); if (m_verbose) std::cerr << " AFPacket payload size " << payload.size() << std::endl; // write length into packet packet.push_back((taglength >> 24) & 0xFF); packet.push_back((taglength >> 16) & 0xFF); packet.push_back((taglength >> 8) & 0xFF); packet.push_back(taglength & 0xFF); // fill rest of header packet.push_back(m_seq >> 8); packet.push_back(m_seq & 0xFF); m_seq++; packet.push_back((m_have_crc ? 0x80 : 0) | AFHEADER_VERSION); // ar_cf: CRC=1 packet.push_back(AFHEADER_PT_TAG); // insert payload, must have a length multiple of 8 bytes packet.insert(packet.end(), payload.begin(), payload.end()); // calculate CRC over AF Header and payload uint16_t crc = 0xffff; crc = crc16(crc, &(packet.front()), packet.size()); crc ^= 0xffff; if (m_verbose) fprintf(stderr, " AFPacket crc %x\n", crc); packet.push_back((crc >> 8) & 0xFF); packet.push_back(crc & 0xFF); if (m_verbose) std::cerr << " AFPacket length " << packet.size() << std::endl; return packet; } void AFPacketiser::OverrideSeq(uint16_t seq) { m_seq = seq; } } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/AFPacket.h000066400000000000000000000027651476627344300235330ustar00rootroot00000000000000/* Copyright (C) 2021 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output. This implements an AF Packet as defined ETSI TS 102 821. Also see ETSI TS 102 693 */ /* 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 #include #include #include "TagItems.h" #include "TagPacket.h" namespace edi { typedef std::vector AFPacket; // ETSI TS 102 821, 6.1 AF packet structure class AFPacketiser { public: AFPacketiser() : m_verbose(false) {}; AFPacketiser(bool verbose) : m_verbose(verbose) {}; AFPacket Assemble(TagPacket tag_packet); void OverrideSeq(uint16_t seq); private: static const bool m_have_crc = true; uint16_t m_seq = 0; //counter that overflows at 0xFFFF bool m_verbose; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/EDIConfig.h000066400000000000000000000051751476627344300236420ustar00rootroot00000000000000/* Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output, UDP and TCP transports and their configuration */ /* 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 #include #include #include #include namespace edi { /** Configuration for EDI output */ struct destination_t { virtual ~destination_t() {}; }; // Can represent both unicast and multicast destinations struct udp_destination_t : public destination_t { std::string dest_addr; unsigned int dest_port = 0; std::string source_addr; unsigned int source_port = 0; unsigned int ttl = 10; }; // TCP server that can accept multiple connections struct tcp_server_t : public destination_t { unsigned int listen_port = 0; size_t max_frames_queued = 1024; // The TCP Server output can preroll a fixed number of previous buffers each time a new client connects. size_t tcp_server_preroll_buffers = 0; }; // TCP client that connects to one endpoint struct tcp_client_t : public destination_t { std::string dest_addr; unsigned int dest_port = 0; size_t max_frames_queued = 1024; }; struct configuration_t { unsigned chunk_len = 207; // RSk, data length of each chunk unsigned fec = 0; // number of fragments that can be recovered bool dump = false; // dump a file with the EDI packets bool verbose = false; bool enable_pft = false; // Enable protection and fragmentation unsigned int tagpacket_alignment = 0; std::vector > destinations; double fragment_spreading_factor = 0.95; // Spread transmission of fragments in time. 1.0 = 100% means spreading over the whole duration of a frame (24ms) // Above 100% means that the fragments are spread over several 24ms periods, interleaving the AF packets. bool enabled() const { return destinations.size() > 0; } void print() const; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/PFT.cpp000066400000000000000000000233171476627344300230750ustar00rootroot00000000000000/* Copyright (C) 2021 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output, Protection, Fragmentation and Transport. (PFT) Are supported: Reed-Solomon and Fragmentation This implements part of PFT as defined ETSI TS 102 821. */ /* 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 #include #include #include "PFT.h" #include "crc.h" #include "ReedSolomon.h" namespace edi { using namespace std; // An integer division that rounds up, i.e. ceil(a/b) #define CEIL_DIV(a, b) (a % b == 0 ? a / b : a / b + 1) PFT::PFT() { } PFT::PFT(const configuration_t &conf) : m_k(conf.chunk_len), m_m(conf.fec), m_pseq(0), m_num_chunks(0), m_verbose(conf.verbose) { if (m_k > 207) { etiLog.level(warn) << "EDI PFT: maximum chunk size is 207."; throw std::out_of_range("EDI PFT Chunk size too large."); } if (m_m > 5) { etiLog.level(warn) << "EDI PFT: high number of recoverable fragments" " may lead to large overhead"; // See TS 102 821, 7.2.1 Known values, list entry for 'm' } } RSBlock PFT::Protect(AFPacket af_packet) { RSBlock rs_block; // number of chunks is ceil(afpacketsize / m_k) // TS 102 821 7.2.2: c = ceil(l / k_max) m_num_chunks = CEIL_DIV(af_packet.size(), m_k); if (m_verbose) { fprintf(stderr, "Protect %zu chunks of size %zu\n", m_num_chunks, af_packet.size()); } // calculate size of chunk: // TS 102 821 7.2.2: k = ceil(l / c) // chunk_len does not include the 48 bytes of protection. const size_t chunk_len = CEIL_DIV(af_packet.size(), m_num_chunks); if (chunk_len > 207) { std::stringstream ss; ss << "Chunk length " << chunk_len << " too large (>207)"; throw std::runtime_error(ss.str()); } // The last RS chunk is zero padded // TS 102 821 7.2.2: z = c*k - l const size_t zero_pad = m_num_chunks * chunk_len - af_packet.size(); // Create the RS(k+p,k) encoder const int firstRoot = 1; // Discovered by analysing EDI dump const int gfPoly = 0x11d; const bool reverse = false; // 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 ReedSolomon rs_encoder(255, 207, reverse, gfPoly, firstRoot); // add zero padding to last chunk for (size_t i = 0; i < zero_pad; i++) { af_packet.push_back(0); } if (m_verbose) { fprintf(stderr, " add %zu zero padding\n", zero_pad); } // Calculate RS for each chunk and assemble RS block for (size_t i = 0; i < af_packet.size(); i+= chunk_len) { vector chunk(207); vector protection(PARITYBYTES); // copy chunk_len bytes into new chunk memcpy(&chunk.front(), &af_packet[i], chunk_len); // calculate RS for chunk with padding rs_encoder.encode(&chunk.front(), &protection.front(), 207); // Drop the padding chunk.resize(chunk_len); // append new chunk and protection to the RS Packet rs_block.insert(rs_block.end(), chunk.begin(), chunk.end()); rs_block.insert(rs_block.end(), protection.begin(), protection.end()); } return rs_block; } vector< vector > PFT::ProtectAndFragment(AFPacket af_packet) { const bool enable_RS = (m_m > 0); if (enable_RS) { RSBlock rs_block = Protect(af_packet); #if 0 fprintf(stderr, " af_packet (%zu):", af_packet.size()); for (size_t i = 0; i < af_packet.size(); i++) { fprintf(stderr, "%02x ", af_packet[i]); } fprintf(stderr, "\n"); fprintf(stderr, " rs_block (%zu):", rs_block.size()); for (size_t i = 0; i < rs_block.size(); i++) { fprintf(stderr, "%02x ", rs_block[i]); } fprintf(stderr, "\n"); #endif // TS 102 821 7.2.2: s_max = MIN(floor(c*p/(m+1)), MTU - h)) const size_t max_payload_size = ( m_num_chunks * PARITYBYTES ) / (m_m + 1); // Calculate fragment count and size // TS 102 821 7.2.2: ceil((l + c*p + z) / s_max) // l + c*p + z = length of RS block const size_t num_fragments = CEIL_DIV(rs_block.size(), max_payload_size); // TS 102 821 7.2.2: ceil((l + c*p + z) / f) const size_t fragment_size = CEIL_DIV(rs_block.size(), num_fragments); if (m_verbose) fprintf(stderr, " PnF fragment_size %zu, num frag %zu\n", fragment_size, num_fragments); vector< vector > fragments(num_fragments); for (size_t i = 0; i < num_fragments; i++) { fragments[i].resize(fragment_size); for (size_t j = 0; j < fragment_size; j++) { const size_t ix = j*num_fragments + i; if (ix < rs_block.size()) { fragments[i][j] = rs_block[ix]; } else { fragments[i][j] = 0; } } } return fragments; } else { // No RS, only fragmentation // TS 102 821 7.2.2: s_max = MTU - h // Ethernet MTU is 1500, but maybe you are routing over a network which // has some sort of packet encapsulation. Add some margin. const size_t max_payload_size = 1400; // Calculate fragment count and size // TS 102 821 7.2.2: ceil((l + c*p + z) / s_max) // l + c*p + z = length of AF packet const size_t num_fragments = CEIL_DIV(af_packet.size(), max_payload_size); // TS 102 821 7.2.2: ceil((l + c*p + z) / f) const size_t fragment_size = CEIL_DIV(af_packet.size(), num_fragments); vector< vector > fragments(num_fragments); for (size_t i = 0; i < num_fragments; i++) { fragments[i].reserve(fragment_size); for (size_t j = 0; j < fragment_size; j++) { const size_t ix = i*fragment_size + j; if (ix < af_packet.size()) { fragments[i].push_back(af_packet.at(ix)); } else { break; } } } return fragments; } } std::vector< PFTFragment > PFT::Assemble(AFPacket af_packet) { vector< vector > fragments = ProtectAndFragment(af_packet); vector< vector > pft_fragments; // These contain PF headers const bool enable_RS = (m_m > 0); unsigned int findex = 0; unsigned fcount = fragments.size(); // calculate size of chunk: // TS 102 821 7.2.2: k = ceil(l / c) // chunk_len does not include the 48 bytes of protection. const size_t chunk_len = enable_RS ? CEIL_DIV(af_packet.size(), m_num_chunks) : 0; // The last RS chunk is zero padded // TS 102 821 7.2.2: z = c*k - l const size_t zero_pad = enable_RS ? m_num_chunks * chunk_len - af_packet.size() : 0; for (const auto &fragment : fragments) { // Psync std::string psync("PF"); std::vector packet(psync.begin(), psync.end()); // Pseq packet.push_back(m_pseq >> 8); packet.push_back(m_pseq & 0xFF); // Findex packet.push_back(findex >> 16); packet.push_back(findex >> 8); packet.push_back(findex & 0xFF); findex++; // Fcount packet.push_back(fcount >> 16); packet.push_back(fcount >> 8); packet.push_back(fcount & 0xFF); // RS (1 bit), transport (1 bit) and Plen (14 bits) unsigned int plen = fragment.size(); if (enable_RS) { plen |= 0x8000; // Set FEC bit } if (m_transport_header) { plen |= 0x4000; // Set ADDR bit } packet.push_back(plen >> 8); packet.push_back(plen & 0xFF); if (enable_RS) { packet.push_back(chunk_len); // RSk packet.push_back(zero_pad); // RSz } if (m_transport_header) { // Source (16 bits) packet.push_back(m_addr_source >> 8); packet.push_back(m_addr_source & 0xFF); // Dest (16 bits) packet.push_back(m_dest_port >> 8); packet.push_back(m_dest_port & 0xFF); } // calculate CRC over AF Header and payload uint16_t crc = 0xffff; crc = crc16(crc, &(packet.front()), packet.size()); crc ^= 0xffff; packet.push_back((crc >> 8) & 0xFF); packet.push_back(crc & 0xFF); // insert payload, must have a length multiple of 8 bytes packet.insert(packet.end(), fragment.begin(), fragment.end()); pft_fragments.push_back(packet); #if 0 fprintf(stderr, "* PFT pseq %d, findex %d, fcount %d, plen %d\n", m_pseq, findex, fcount, plen & ~0xC000); #endif } m_pseq++; return pft_fragments; } void PFT::OverridePSeq(uint16_t pseq) { m_pseq = pseq; } } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/PFT.h000066400000000000000000000043501476627344300225360ustar00rootroot00000000000000/* Copyright (C) 2021 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output, Protection, Fragmentation and Transport. (PFT) Are supported: Reed-Solomon and Fragmentation This implements part of PFT as defined ETSI TS 102 821. */ /* 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 #include #include #include #include #include "AFPacket.h" #include "Log.h" #include "ReedSolomon.h" #include "EDIConfig.h" namespace edi { typedef std::vector RSBlock; typedef std::vector PFTFragment; class PFT { public: static constexpr int PARITYBYTES = 48; PFT(); PFT(const configuration_t& conf); // return a list of PFT fragments with the correct // PFT headers std::vector< PFTFragment > Assemble(AFPacket af_packet); // Apply Reed-Solomon FEC to the AF Packet RSBlock Protect(AFPacket af_packet); // Cut a RSBlock into several fragments that can be transmitted std::vector< std::vector > ProtectAndFragment(AFPacket af_packet); void OverridePSeq(uint16_t pseq); private: unsigned int m_k = 207; // length of RS data word unsigned int m_m = 3; // number of fragments that can be recovered if lost uint16_t m_pseq = 0; size_t m_num_chunks = 0; bool m_verbose = false; // Transport header is always deactivated const bool m_transport_header = false; const uint16_t m_addr_source = 0; const unsigned int m_dest_port = 0; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/TagItems.cpp000066400000000000000000000277361476627344300241720ustar00rootroot00000000000000/* EDI output. This defines a few TAG items as defined ETSI TS 102 821 and ETSI TS 102 693 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 . */ #include "TagItems.h" #include #include #include #include #include namespace edi { TagStarPTR::TagStarPTR(const std::string& protocol) : m_protocol(protocol) { if (m_protocol.size() != 4) { throw std::runtime_error("TagStarPTR protocol invalid length"); } } std::vector TagStarPTR::Assemble() { //std::cerr << "TagItem *ptr" << std::endl; std::string pack_data("*ptr"); std::vector packet(pack_data.begin(), pack_data.end()); packet.push_back(0); packet.push_back(0); packet.push_back(0); packet.push_back(0x40); packet.insert(packet.end(), m_protocol.begin(), m_protocol.end()); // Major packet.push_back(0); packet.push_back(0); // Minor packet.push_back(0); packet.push_back(0); return packet; } std::vector TagDETI::Assemble() { std::string pack_data("deti"); std::vector packet(pack_data.begin(), pack_data.end()); packet.reserve(256); // Placeholder for length packet.push_back(0); packet.push_back(0); packet.push_back(0); packet.push_back(0); uint8_t fct = dlfc % 250; uint8_t fcth = dlfc / 250; uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15); packet.push_back(detiHeader >> 8); packet.push_back(detiHeader & 0xFF); uint32_t etiHeader = mnsc | (rfu << 16) | (rfa << 17) | (fp << 19) | (mid << 22) | (stat << 24); packet.push_back((etiHeader >> 24) & 0xFF); packet.push_back((etiHeader >> 16) & 0xFF); packet.push_back((etiHeader >> 8) & 0xFF); packet.push_back(etiHeader & 0xFF); if (atstf) { packet.push_back(utco); packet.push_back((seconds >> 24) & 0xFF); packet.push_back((seconds >> 16) & 0xFF); packet.push_back((seconds >> 8) & 0xFF); packet.push_back(seconds & 0xFF); packet.push_back((tsta >> 16) & 0xFF); packet.push_back((tsta >> 8) & 0xFF); packet.push_back(tsta & 0xFF); } if (ficf) { for (size_t i = 0; i < fic_length; i++) { packet.push_back(fic_data[i]); } } if (rfudf) { packet.push_back((rfud >> 16) & 0xFF); packet.push_back((rfud >> 8) & 0xFF); packet.push_back(rfud & 0xFF); } // calculate and update size // remove TAG name and TAG length fields and convert to bits uint32_t taglength = (packet.size() - 8) * 8; // write length into packet packet[4] = (taglength >> 24) & 0xFF; packet[5] = (taglength >> 16) & 0xFF; packet[6] = (taglength >> 8) & 0xFF; packet[7] = taglength & 0xFF; dlfc = (dlfc+1) % 5000; /* std::cerr << "TagItem deti, packet.size " << packet.size() << std::endl; std::cerr << " fic length " << fic_length << std::endl; std::cerr << " length " << taglength / 8 << std::endl; */ return packet; } void TagDETI::set_edi_time(const std::time_t t, int tai_utc_offset) { utco = tai_utc_offset - 32; const std::time_t posix_timestamp_1_jan_2000 = 946684800; seconds = t - posix_timestamp_1_jan_2000 + utco; } std::vector TagESTn::Assemble() { std::string pack_data("est"); std::vector packet(pack_data.begin(), pack_data.end()); packet.reserve(mst_length*8 + 16); packet.push_back(id); // Placeholder for length packet.push_back(0); packet.push_back(0); packet.push_back(0); packet.push_back(0); if (tpl > 0x3F) { throw std::runtime_error("TagESTn: invalid TPL value"); } if (sad > 0x3FF) { throw std::runtime_error("TagESTn: invalid SAD value"); } if (scid > 0x3F) { throw std::runtime_error("TagESTn: invalid SCID value"); } uint32_t sstc = (scid << 18) | (sad << 8) | (tpl << 2) | rfa; packet.push_back((sstc >> 16) & 0xFF); packet.push_back((sstc >> 8) & 0xFF); packet.push_back(sstc & 0xFF); for (size_t i = 0; i < mst_length * 8; i++) { packet.push_back(mst_data[i]); } // calculate and update size // remove TAG name and TAG length fields and convert to bits uint32_t taglength = (packet.size() - 8) * 8; // write length into packet packet[4] = (taglength >> 24) & 0xFF; packet[5] = (taglength >> 16) & 0xFF; packet[6] = (taglength >> 8) & 0xFF; packet[7] = taglength & 0xFF; /* std::cerr << "TagItem ESTn, length " << packet.size() << std::endl; std::cerr << " mst_length " << mst_length << std::endl; */ return packet; } std::vector TagDSTI::Assemble() { std::string pack_data("dsti"); std::vector packet(pack_data.begin(), pack_data.end()); packet.reserve(256); // Placeholder for length packet.push_back(0); packet.push_back(0); packet.push_back(0); packet.push_back(0); uint8_t dfctl = dlfc % 250; uint8_t dfcth = dlfc / 250; uint16_t dstiHeader = dfctl | (dfcth << 8) | (rfadf << 13) | (atstf << 14) | (stihf << 15); packet.push_back(dstiHeader >> 8); packet.push_back(dstiHeader & 0xFF); if (stihf) { packet.push_back(stat); packet.push_back((spid >> 8) & 0xFF); packet.push_back(spid & 0xFF); } if (atstf) { packet.push_back(utco); packet.push_back((seconds >> 24) & 0xFF); packet.push_back((seconds >> 16) & 0xFF); packet.push_back((seconds >> 8) & 0xFF); packet.push_back(seconds & 0xFF); packet.push_back((tsta >> 16) & 0xFF); packet.push_back((tsta >> 8) & 0xFF); packet.push_back(tsta & 0xFF); } if (rfadf) { for (size_t i = 0; i < rfad.size(); i++) { packet.push_back(rfad[i]); } } // calculate and update size // remove TAG name and TAG length fields and convert to bits uint32_t taglength = (packet.size() - 8) * 8; // write length into packet packet[4] = (taglength >> 24) & 0xFF; packet[5] = (taglength >> 16) & 0xFF; packet[6] = (taglength >> 8) & 0xFF; packet[7] = taglength & 0xFF; dlfc = (dlfc+1) % 5000; /* std::cerr << "TagItem dsti, packet.size " << packet.size() << std::endl; std::cerr << " length " << taglength / 8 << std::endl; */ return packet; } void TagDSTI::set_edi_time(const std::time_t t, int tai_utc_offset) { utco = tai_utc_offset - 32; const std::time_t posix_timestamp_1_jan_2000 = 946684800; seconds = t - posix_timestamp_1_jan_2000 + utco; } #if 0 /* Update the EDI time. t is in UTC, TAI offset is requested from adjtimex */ void TagDSTI::set_edi_time(const std::time_t t) { if (tai_offset_cache_updated_at == 0 or tai_offset_cache_updated_at + 3600 < t) { struct timex timex_request; timex_request.modes = 0; int err = adjtimex(&timex_request); if (err == -1) { throw std::runtime_error("adjtimex failed"); } if (timex_request.tai == 0) { throw std::runtime_error("CLOCK_TAI is not properly set up"); } tai_offset_cache = timex_request.tai; tai_offset_cache_updated_at = t; fprintf(stderr, "adjtimex: %d, tai %d\n", err, timex_request.tai); } utco = tai_offset_cache - 32; const std::time_t posix_timestamp_1_jan_2000 = 946684800; seconds = t - posix_timestamp_1_jan_2000 + utco; } #endif std::vector TagSSm::Assemble() { std::string pack_data("ss"); std::vector packet(pack_data.begin(), pack_data.end()); packet.reserve(istd_length + 16); packet.push_back((id >> 8) & 0xFF); packet.push_back(id & 0xFF); // Placeholder for length packet.push_back(0); packet.push_back(0); packet.push_back(0); packet.push_back(0); if (rfa > 0x1F) { throw std::runtime_error("TagSSm: invalid RFA value"); } if (tid > 0x7) { throw std::runtime_error("TagSSm: invalid tid value"); } if (tidext > 0x7) { throw std::runtime_error("TagSSm: invalid tidext value"); } if (stid > 0x0FFF) { throw std::runtime_error("TagSSm: invalid stid value"); } uint32_t istc = (rfa << 19) | (tid << 16) | (tidext << 13) | ((crcstf ? 1 : 0) << 12) | stid; packet.push_back((istc >> 16) & 0xFF); packet.push_back((istc >> 8) & 0xFF); packet.push_back(istc & 0xFF); for (size_t i = 0; i < istd_length; i++) { packet.push_back(istd_data[i]); } // calculate and update size // remove TAG name and TAG length fields and convert to bits uint32_t taglength = (packet.size() - 8) * 8; // write length into packet packet[4] = (taglength >> 24) & 0xFF; packet[5] = (taglength >> 16) & 0xFF; packet[6] = (taglength >> 8) & 0xFF; packet[7] = taglength & 0xFF; /* std::cerr << "TagItem SSm, length " << packet.size() << std::endl; std::cerr << " istd_length " << istd_length << std::endl; */ return packet; } std::vector TagStarDMY::Assemble() { std::string pack_data("*dmy"); std::vector packet(pack_data.begin(), pack_data.end()); packet.resize(4 + 4 + length_); const uint32_t length_bits = length_ * 8; packet[4] = (length_bits >> 24) & 0xFF; packet[5] = (length_bits >> 16) & 0xFF; packet[6] = (length_bits >> 8) & 0xFF; packet[7] = length_bits & 0xFF; // The remaining bytes in the packet are "undefined data" return packet; } TagODRVersion::TagODRVersion(const std::string& version, uint32_t uptime_s) : m_version(version), m_uptime(uptime_s) { } std::vector TagODRVersion::Assemble() { std::string pack_data("ODRv"); std::vector packet(pack_data.begin(), pack_data.end()); const size_t length = m_version.size() + sizeof(uint32_t); packet.resize(4 + 4 + length); const uint32_t length_bits = length * 8; size_t i = 4; packet[i++] = (length_bits >> 24) & 0xFF; packet[i++] = (length_bits >> 16) & 0xFF; packet[i++] = (length_bits >> 8) & 0xFF; packet[i++] = length_bits & 0xFF; copy(m_version.cbegin(), m_version.cend(), packet.begin() + i); i += m_version.size(); packet[i++] = (m_uptime >> 24) & 0xFF; packet[i++] = (m_uptime >> 16) & 0xFF; packet[i++] = (m_uptime >> 8) & 0xFF; packet[i++] = m_uptime & 0xFF; return packet; } TagODRAudioLevels::TagODRAudioLevels(int16_t audiolevel_left, int16_t audiolevel_right) : m_audio_left(audiolevel_left), m_audio_right(audiolevel_right) { } std::vector TagODRAudioLevels::Assemble() { std::string pack_data("ODRa"); std::vector packet(pack_data.begin(), pack_data.end()); constexpr size_t length = 2*sizeof(int16_t); packet.resize(4 + 4 + length); const uint32_t length_bits = length * 8; size_t i = 4; packet[i++] = (length_bits >> 24) & 0xFF; packet[i++] = (length_bits >> 16) & 0xFF; packet[i++] = (length_bits >> 8) & 0xFF; packet[i++] = length_bits & 0xFF; packet[i++] = (m_audio_left >> 8) & 0xFF; packet[i++] = m_audio_left & 0xFF; packet[i++] = (m_audio_right >> 8) & 0xFF; packet[i++] = m_audio_right & 0xFF; return packet; } } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/TagItems.h000066400000000000000000000163501476627344300236250ustar00rootroot00000000000000/* EDI output. This defines a few TAG items as defined ETSI TS 102 821 and ETSI TS 102 693 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 . */ #pragma once #include #include #include #include #include namespace edi { class TagItem { public: virtual std::vector Assemble() = 0; }; // ETSI TS 102 693, 5.1.1 Protocol type and revision class TagStarPTR : public TagItem { public: TagStarPTR(const std::string& protocol); std::vector Assemble(); private: std::string m_protocol = ""; }; // ETSI TS 102 693, 5.1.3 DAB ETI(LI) Management (deti) class TagDETI : public TagItem { public: std::vector Assemble(); /***** DATA in intermediary format ****/ // For the ETI Header: must be defined ! uint8_t stat = 0; uint8_t mid = 0; uint8_t fp = 0; uint8_t rfa = 0; uint8_t rfu = 0; // MNSC is valid uint16_t mnsc = 0; uint16_t dlfc = 0; // modulo 5000 frame counter // ATST (optional) bool atstf = false; // presence of atst data /* UTCO: Offset (in seconds) between UTC and the Seconds value. The * value is expressed as an unsigned 8-bit quantity. As of February * 2009, the value shall be 2 and shall change as a result of each * modification of the number of leap seconds, as proscribed by * International Earth Rotation and Reference Systems Service (IERS). * * According to Annex F * EDI = TAI - 32s (constant) * EDI = UTC + UTCO * we derive * UTCO = TAI-UTC - 32 * where the TAI-UTC offset is given by the USNO bulletin using * the ClockTAI module. */ uint8_t utco = 0; /* Update the EDI time. t is in UTC */ void set_edi_time(const std::time_t t, int tai_utc_offset); /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an * unsigned 32-bit quantity. Contrary to POSIX, this value also * counts leap seconds. */ uint32_t seconds = 0; /* TSTA: Shall be the 24 least significant bits of the Time Stamp * (TIST) field from the STI-D(LI) Frame. The full definition for the * STI TIST can be found in annex B of EN 300 797 [4]. The most * significant 8 bits of the TIST field of the incoming STI-D(LI) * frame, if required, may be carried in the RFAD field. */ uint32_t tsta = 0xFFFFFF; // the FIC (optional) bool ficf = false; const unsigned char* fic_data; size_t fic_length; // rfu bool rfudf = false; uint32_t rfud = 0; }; // ETSI TS 102 693, 5.1.5 ETI Sub-Channel Stream class TagESTn : public TagItem { public: std::vector Assemble(); // SSTCn uint8_t scid; uint16_t sad; uint8_t tpl; uint8_t rfa; // Pointer to MSTn data uint8_t* mst_data; size_t mst_length; // STLn * 8 bytes // id is 1-indexed. uint8_t id = 1; }; // ETSI TS 102 693, 5.1.2 DAB STI-D(LI) Management class TagDSTI : public TagItem { public: std::vector Assemble(); // dsti Header bool stihf = false; bool atstf = false; // presence of atst data bool rfadf = false; uint16_t dlfc = 0; // modulo 5000 frame counter // STI Header (optional) uint8_t stat = 0; uint16_t spid = 0; /* UTCO: Offset (in seconds) between UTC and the Seconds value. The * value is expressed as an unsigned 8-bit quantity. As of February * 2009, the value shall be 2 and shall change as a result of each * modification of the number of leap seconds, as proscribed by * International Earth Rotation and Reference Systems Service (IERS). * * According to Annex F * EDI = TAI - 32s (constant) * EDI = UTC + UTCO * we derive * UTCO = TAI-UTC - 32 * where the TAI-UTC offset is given by the USNO bulletin using * the ClockTAI module. */ uint8_t utco = 0; /* Update the EDI time. t is in UTC */ void set_edi_time(const std::time_t t, int tai_utc_offset); /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an * unsigned 32-bit quantity. Contrary to POSIX, this value also * counts leap seconds. */ uint32_t seconds = 0; /* TSTA: Shall be the 24 least significant bits of the Time Stamp * (TIST) field from the STI-D(LI) Frame. The full definition for the * STI TIST can be found in annex B of EN 300 797 [4]. The most * significant 8 bits of the TIST field of the incoming STI-D(LI) * frame, if required, may be carried in the RFAD field. */ uint32_t tsta = 0xFFFFFF; std::array rfad; private: int tai_offset_cache = 0; std::time_t tai_offset_cache_updated_at = 0; }; // ETSI TS 102 693, 5.1.4 STI-D Payload Stream class TagSSm : public TagItem { public: std::vector Assemble(); // SSTCn uint8_t rfa = 0; uint8_t tid = 0; // See EN 300 797, 5.4.1.1. Value 0 means "MSC sub-channel" uint8_t tidext = 0; // EN 300 797, 5.4.1.3, Value 0 means "MSC audio stream" bool crcstf = false; uint16_t stid = 0; // Pointer to ISTDm data const uint8_t *istd_data; size_t istd_length; // bytes // id is 1-indexed. uint16_t id = 1; }; // ETSI TS 102 821, 5.2.2.2 Dummy padding class TagStarDMY : public TagItem { public: /* length is the TAG value length in bytes */ TagStarDMY(uint32_t length) : length_(length) {} std::vector Assemble(); private: uint32_t length_; }; // Custom TAG that carries version information of the EDI source class TagODRVersion : public TagItem { public: TagODRVersion(const std::string& version, uint32_t uptime_s); std::vector Assemble(); private: std::string m_version; uint32_t m_uptime; }; // Custom TAG that carries audio level metadata class TagODRAudioLevels : public TagItem { public: TagODRAudioLevels(int16_t audiolevel_left, int16_t audiolevel_right); std::vector Assemble(); private: int16_t m_audio_left; int16_t m_audio_right; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/TagPacket.cpp000066400000000000000000000042031476627344300243000ustar00rootroot00000000000000/* Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output. This defines a TAG Packet. */ /* 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 "TagPacket.h" #include "TagItems.h" #include #include #include #include #include #include namespace edi { TagPacket::TagPacket(unsigned int alignment) : m_alignment(alignment) { } std::vector TagPacket::Assemble() { if (raw_tagpacket.size() > 0 and tag_items.size() > 0) { throw std::logic_error("TagPacket: both raw and items used!"); } if (raw_tagpacket.size() > 0) { return raw_tagpacket; } std::vector packet; for (auto tag : tag_items) { std::vector tag_data = tag->Assemble(); packet.insert(packet.end(), tag_data.begin(), tag_data.end()); } if (m_alignment == 0) { /* no padding */ } else if (m_alignment == 8) { // Add padding inside TAG packet while (packet.size() % 8 > 0) { packet.push_back(0); // TS 102 821, 5.1, "padding shall be undefined" } } else if (m_alignment > 8) { TagStarDMY dmy(m_alignment - 8); auto dmy_data = dmy.Assemble(); packet.insert(packet.end(), dmy_data.begin(), dmy_data.end()); } else { std::cerr << "Invalid alignment requirement " << m_alignment << " defined in TagPacket" << std::endl; } return packet; } } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/TagPacket.h000066400000000000000000000030121476627344300237420ustar00rootroot00000000000000/* Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output. This defines a TAG Packet. */ /* 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 #include "TagItems.h" #include #include #include #include namespace edi { // A TagPacket is nothing else than a list of tag items, with an // Assemble function that puts the bytestream together and adds // padding such that the total length is a multiple of 8 Bytes. // // Alternatively, a raw tagpacket can be used instead of the // items list // // ETSI TS 102 821, 5.1 Tag Packet class TagPacket { public: TagPacket(unsigned int alignment); std::vector Assemble(); std::list tag_items; std::vector raw_tagpacket; private: unsigned int m_alignment; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/Transport.cpp000066400000000000000000000234601476627344300244370ustar00rootroot00000000000000/* Copyright (C) 2025 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output, UDP and TCP transports and their configuration */ /* 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 "Transport.h" #include #include #include using namespace std; namespace edi { void configuration_t::print() const { etiLog.level(info) << "EDI Output"; etiLog.level(info) << " verbose " << verbose; for (auto edi_dest : destinations) { if (auto udp_dest = dynamic_pointer_cast(edi_dest)) { etiLog.level(info) << " UDP to " << udp_dest->dest_addr << ":" << udp_dest->dest_port; if (not udp_dest->source_addr.empty()) { etiLog.level(info) << " source " << udp_dest->source_addr; etiLog.level(info) << " ttl " << udp_dest->ttl; } etiLog.level(info) << " source port " << udp_dest->source_port; } else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { etiLog.level(info) << " TCP listening on port " << tcp_dest->listen_port; etiLog.level(info) << " max frames queued " << tcp_dest->max_frames_queued; } else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { etiLog.level(info) << " TCP client connecting to " << tcp_dest->dest_addr << ":" << tcp_dest->dest_port; etiLog.level(info) << " max frames queued " << tcp_dest->max_frames_queued; } else { throw logic_error("EDI destination not implemented"); } } } Sender::Sender(const configuration_t& conf) : m_conf(conf), edi_pft(m_conf) { if (m_conf.verbose) { etiLog.level(info) << "Setup EDI Output"; } for (const auto& edi_dest : m_conf.destinations) { if (const auto udp_dest = dynamic_pointer_cast(edi_dest)) { auto udp_socket = std::make_shared(udp_dest->source_port); if (not udp_dest->source_addr.empty()) { udp_socket->setMulticastSource(udp_dest->source_addr.c_str()); udp_socket->setMulticastTTL(udp_dest->ttl); } udp_sockets.emplace(udp_dest.get(), udp_socket); } else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { auto dispatcher = make_shared( tcp_dest->max_frames_queued, tcp_dest->tcp_server_preroll_buffers); dispatcher->start(tcp_dest->listen_port, "0.0.0.0"); tcp_dispatchers.emplace(tcp_dest.get(), dispatcher); } else if (auto tcp_dest = dynamic_pointer_cast(edi_dest)) { auto tcp_send_client = make_shared(tcp_dest->dest_addr, tcp_dest->dest_port); tcp_senders.emplace(tcp_dest.get(), tcp_send_client); } else { throw logic_error("EDI destination not implemented"); } } if (m_conf.dump) { edi_debug_file.open("./edi.debug"); } if (m_conf.enable_pft) { unique_lock lock(m_mutex); m_running = true; m_thread = thread(&Sender::run, this); } if (m_conf.verbose) { etiLog.log(info, "EDI output set up"); } } Sender::~Sender() { { unique_lock lock(m_mutex); m_running = false; } if (m_thread.joinable()) { m_thread.join(); } } void Sender::write(const TagPacket& tagpacket) { // Assemble into one AF Packet edi::AFPacket af_packet = edi_afPacketiser.Assemble(tagpacket); write(af_packet); } void Sender::write(const AFPacket& af_packet) { if (m_conf.enable_pft) { // Apply PFT layer to AF Packet (Reed Solomon FEC and Fragmentation) vector edi_fragments = edi_pft.Assemble(af_packet); if (m_conf.verbose and m_last_num_pft_fragments != edi_fragments.size()) { etiLog.log(debug, "EDI Output: Number of PFT fragments %zu\n", edi_fragments.size()); m_last_num_pft_fragments = edi_fragments.size(); } /* Spread out the transmission of all fragments over part of the 24ms AF packet duration * to reduce the risk of losing a burst of fragments because of congestion. */ using namespace std::chrono; auto inter_fragment_wait_time = microseconds(1); if (edi_fragments.size() > 1) { if (m_conf.fragment_spreading_factor > 0) { inter_fragment_wait_time = microseconds( llrint(m_conf.fragment_spreading_factor * 24000.0 / edi_fragments.size()) ); } } /* Separate insertion into map and transmission so as to make spreading possible */ const auto now = steady_clock::now(); { auto tp = now; unique_lock lock(m_mutex); for (auto& edi_frag : edi_fragments) { m_pending_frames[tp] = move(edi_frag); tp += inter_fragment_wait_time; } } // Transmission done in run() function } else /* PFT disabled */ { // Send over ethernet if (m_conf.dump) { ostream_iterator debug_iterator(edi_debug_file); copy(af_packet.begin(), af_packet.end(), debug_iterator); } for (auto& dest : m_conf.destinations) { if (const auto& udp_dest = dynamic_pointer_cast(dest)) { Socket::InetAddress addr; addr.resolveUdpDestination(udp_dest->dest_addr, udp_dest->dest_port); if (af_packet.size() > 1400 and not m_udp_fragmentation_warning_printed) { fprintf(stderr, "EDI Output: AF packet larger than 1400," " consider using PFT to avoid UP fragmentation.\n"); m_udp_fragmentation_warning_printed = true; } udp_sockets.at(udp_dest.get())->send(af_packet, addr); } else if (auto tcp_dest = dynamic_pointer_cast(dest)) { tcp_dispatchers.at(tcp_dest.get())->write(af_packet); } else if (auto tcp_dest = dynamic_pointer_cast(dest)) { const auto error_stats = tcp_senders.at(tcp_dest.get())->sendall(af_packet); if (m_conf.verbose and error_stats.has_seen_new_errors) { fprintf(stderr, "TCP output %s:%d has %zu reconnects: most recent error: %s\n", tcp_dest->dest_addr.c_str(), tcp_dest->dest_port, error_stats.num_reconnects, error_stats.last_error.c_str()); } } else { throw logic_error("EDI destination not implemented"); } } } } void Sender::override_af_sequence(uint16_t seq) { edi_afPacketiser.OverrideSeq(seq); } void Sender::override_pft_sequence(uint16_t pseq) { edi_pft.OverridePSeq(pseq); } std::vector Sender::get_tcp_server_stats() const { std::vector stats; for (auto& el : tcp_dispatchers) { Sender::stats_t s; s.listen_port = el.first->listen_port; s.stats = el.second->get_stats(); stats.push_back(s); } return stats; } void Sender::run() { while (m_running) { unique_lock lock(m_mutex); const auto now = chrono::steady_clock::now(); // Send over ethernet for (auto it = m_pending_frames.begin(); it != m_pending_frames.end(); ) { const auto& edi_frag = it->second; if (it->first <= now) { if (m_conf.dump) { ostream_iterator debug_iterator(edi_debug_file); copy(edi_frag.begin(), edi_frag.end(), debug_iterator); } for (auto& dest : m_conf.destinations) { if (const auto& udp_dest = dynamic_pointer_cast(dest)) { Socket::InetAddress addr; addr.resolveUdpDestination(udp_dest->dest_addr, udp_dest->dest_port); udp_sockets.at(udp_dest.get())->send(edi_frag, addr); } else if (auto tcp_dest = dynamic_pointer_cast(dest)) { tcp_dispatchers.at(tcp_dest.get())->write(edi_frag); } else if (auto tcp_dest = dynamic_pointer_cast(dest)) { tcp_senders.at(tcp_dest.get())->sendall(edi_frag); } else { throw logic_error("EDI destination not implemented"); } } it = m_pending_frames.erase(it); } else { ++it; } } lock.unlock(); this_thread::sleep_for(chrono::microseconds(500)); } } } Opendigitalradio-ODR-DabMux-29c710c/lib/edioutput/Transport.h000066400000000000000000000061431476627344300241030ustar00rootroot00000000000000/* Copyright (C) 2025 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org EDI output, UDP and TCP transports and their configuration */ /* 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 #include "EDIConfig.h" #include "AFPacket.h" #include "PFT.h" #include "Socket.h" #include #include #include #include #include #include #include #include namespace edi { /** ETI/STI sender for EDI output */ class Sender { public: Sender(const configuration_t& conf); Sender(const Sender&) = delete; Sender operator=(const Sender&) = delete; ~Sender(); // Assemble the tagpacket into an AF packet, and if needed, // apply PFT and then schedule for transmission. void write(const TagPacket& tagpacket); // Schedule an already assembled AF Packet for transmission, // applying PFT if needed. void write(const AFPacket& af_packet); // Set the sequence numbers to be used for the next call to write() // seq is for the AF layer // pseq is for the PFT layer void override_af_sequence(uint16_t seq); void override_pft_sequence(uint16_t pseq); struct stats_t { uint16_t listen_port; std::vector stats; }; std::vector get_tcp_server_stats() const; private: void run(); bool m_udp_fragmentation_warning_printed = false; configuration_t m_conf; std::ofstream edi_debug_file; // The TagPacket will then be placed into an AFPacket edi::AFPacketiser edi_afPacketiser; // The AF Packet will be protected with reed-solomon and split in fragments edi::PFT edi_pft; std::unordered_map> udp_sockets; std::unordered_map> tcp_dispatchers; std::unordered_map> tcp_senders; // PFT spreading requires sending UDP packets at specific time, independently of // time when write() gets called std::thread m_thread; std::mutex m_mutex; bool m_running = false; std::map m_pending_frames; size_t m_last_num_pft_fragments = 0; }; } Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/000077500000000000000000000000001476627344300213555ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/linux/000077500000000000000000000000001476627344300225145ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/linux/farsync.h000066400000000000000000000664741476627344300243530ustar00rootroot00000000000000/* * FarSync OEM driver for Linux * * Copyright (C) 2001-2012 FarSite Communications Ltd. * www.farsite.co.uk * $Id: farsync.h 1471 2013-09-10 08:01:11Z kevinc $ * * 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. * * Author: R.J.Dunlop * * For the most part this file only contains structures and information * that is visible to applications outside the driver. Shared memory * layout etc is internal to the driver and described within farsync.c. * Overlap exists in that the values used for some fields within the * ioctl interface extend into the cards firmware interface so values in * this file may not be changed arbitrarily. */ #define FST_NAME "fst" /* In debug/info etc */ #define FST_NDEV_NAME "sync" /* For net interface */ #define FST_DEV_NAME "farsync" /* For misc interfaces */ /* User version number * * This version number is incremented with each official release of the * package and is a simplified number for normal user reference. * Individual files are tracked by the version control system and may * have individual versions (or IDs) that move much faster than the * the release version as individual updates are tracked. */ #define FST_USER_VERSION "2.1.8" #define FST_PATCH_LEVEL "02" #ifdef __x86_64__ #define FST_PLATFORM "64bit" #else #define FST_PLATFORM "32bit" #endif #define FST_ADDITIONAL FST_BUILD_NO #define FST_INCLUDES_CHAR struct fst_device_stats { unsigned long rx_packets; /* total packets received */ unsigned long tx_packets; /* total packets transmitted */ unsigned long rx_bytes; /* total bytes received */ unsigned long tx_bytes; /* total bytes transmitted */ unsigned long rx_errors; /* bad packets received */ unsigned long tx_errors; /* packet transmit problems */ unsigned long rx_dropped; /* no space in linux buffers */ unsigned long tx_dropped; /* no space available in linux */ unsigned long multicast; /* multicast packets received */ unsigned long collisions; /* detailed rx_errors: */ unsigned long rx_length_errors; unsigned long rx_over_errors; /* receiver ring buff overflow */ unsigned long rx_crc_errors; /* recved pkt with crc error */ unsigned long rx_frame_errors;/* recv'd frame alignment error */ unsigned long rx_fifo_errors; /* recv'r fifo overrun */ unsigned long rx_missed_errors; /* receiver missed packet */ /* detailed tx_errors */ unsigned long tx_aborted_errors; unsigned long tx_carrier_errors; unsigned long tx_fifo_errors; unsigned long tx_heartbeat_errors; unsigned long tx_underrun_errors; /* for cslip etc */ unsigned long rx_compressed; unsigned long tx_compressed; }; #define COM_STOP_BITS_1 0 #define COM_STOP_BITS_1_5 1 #define COM_STOP_BITS_2 2 #define COM_NO_PARITY 0 #define COM_ODD_PARITY 1 #define COM_EVEN_PARITY 2 #define COM_FORCE_PARITY_1 3 #define COM_FORCE_PARITY_0 4 #define COM_FLOW_CONTROL_NONE 1 #define COM_FLOW_CONTROL_RTSCTS 2 #define COM_FLOW_CONTROL_XONXOFF 3 struct fstioc_async_conf { unsigned char flow_control; unsigned char stop_bits; unsigned char parity; unsigned char word_length; unsigned char xon_char; unsigned char xoff_char; }; /* Ioctl call command values * * The first three private ioctls are used by the sync-PPP module, * allowing a little room for expansion we start our numbering at 10. */ #define FSTWRITE (SIOCDEVPRIVATE+4) #define FSTCPURESET (SIOCDEVPRIVATE+5) #define FSTCPURELEASE (SIOCDEVPRIVATE+6) #define FSTGETCONF (SIOCDEVPRIVATE+7) #define FSTSETCONF (SIOCDEVPRIVATE+8) #define FSTSNOTIFY (SIOCDEVPRIVATE+9) #define FSTGSTATE (SIOCDEVPRIVATE+10) #define FSTSYSREQ (SIOCDEVPRIVATE+11) #define FSTGETSHELL (SIOCDEVPRIVATE+12) #define FSTSETMON (SIOCDEVPRIVATE+13) #define FSTSETPORT (SIOCDEVPRIVATE+14) #define FSTCMD (SIOCDEVPRIVATE+15) /* FSTWRITE * * Used to write a block of data (firmware etc) before the card is running */ struct fstioc_write { unsigned int size; unsigned int offset; unsigned char data[0]; }; struct fstioc_control_request { #define FSCONTROLREQUEST_VERSION 1 __u32 uVersion; // Version of this structure __u8 bDirection; // 1 ==> HostToDevice, 0 ==> DeviceToHost __u8 byRequest; __u16 wValue; __u16 wIndex; __u16 wDataLength; __u8 Data[256]; }; /* FSTCPURESET and FSTCPURELEASE * * These take no additional data. * FSTCPURESET forces the cards CPU into a reset state and holds it there. * FSTCPURELEASE releases the CPU from this reset state allowing it to run, * the reset vector should be setup before this ioctl is run. */ /* FSTGETCONF and FSTSETCONF * * Get and set a card/ports configuration. * In order to allow selective setting of items and for the kernel to * indicate a partial status response the first field "valid" is a bitmask * indicating which other fields in the structure are valid. * Many of the field names in this structure match those used in the * firmware shared memory configuration interface and come originally from * the NT header file Smc.h * * When used with FSTGETCONF this structure should be zeroed before use. * This is to allow for possible future expansion when some of the fields * might be used to indicate a different (expanded) structure. */ struct fstioc_info { unsigned int valid; /* Bits of structure that are valid */ unsigned int nports; /* Number of serial ports */ unsigned int type; /* Type index of card */ unsigned int state; /* State of card */ unsigned int index; /* Index of port ioctl was issued on */ unsigned int smcFirmwareVersion; unsigned long kernelVersion; /* What Kernel version we are working with */ unsigned short lineInterface; /* Physical interface type */ unsigned char proto; /* Line protocol */ unsigned char internalClock; /* 1 => internal clock, 0 => external */ unsigned int lineSpeed; /* Speed in bps */ unsigned int estLineSpeed; /* Estimated speed in bps */ unsigned int v24IpSts; /* V.24 control input status */ unsigned int v24OpSts; /* V.24 control output status */ unsigned short clockStatus; /* lsb: 0=> present, 1=> absent */ unsigned short cableStatus; /* lsb: 0=> present, 1=> absent */ unsigned short cardMode; /* lsb: LED id mode */ unsigned short debug; /* Debug flags */ unsigned char transparentMode; /* Not used always 0 */ unsigned char invertClock; /* Invert clock feature for syncing */ unsigned char asyncAbility ; /* The ability to do async */ unsigned char synthAbility; /* The ability to syth a clock */ unsigned char extendedClocking;/* New T4e clock modes */ unsigned char startingSlot; /* Time slot to use for start of tx */ unsigned char clockSource; /* External or internal */ unsigned char framing; /* E1, T1 or J1 */ unsigned char structure; /* unframed, double, crc4, f4, f12, */ /* f24 f72 */ unsigned char interface; /* rj48c or bnc */ unsigned char coding; /* hdb3 b8zs */ unsigned char lineBuildOut; /* 0, -7.5, -15, -22 */ unsigned char equalizer; /* short or lon haul settings */ unsigned char loopMode; /* various loopbacks */ unsigned char range; /* cable lengths */ unsigned char txBufferMode; /* tx elastic buffer depth */ unsigned char rxBufferMode; /* rx elastic buffer depth */ unsigned char losThreshold; /* Attenuation on LOS signal */ unsigned char idleCode; /* Value to send as idle timeslot */ unsigned int receiveBufferDelay; /* delay thro rx buffer timeslots */ unsigned int framingErrorCount; /* framing errors */ unsigned int codeViolationCount; /* code violations */ unsigned int crcErrorCount; /* CRC errors */ int lineAttenuation; /* in dB*/ unsigned short lossOfSignal; unsigned short receiveRemoteAlarm; unsigned short alarmIndicationSignal; unsigned short _reserved[64]; unsigned char ignoreCarrier; /* If set transmit regardless of carrier state */ unsigned char numTxBuffers; /* No of tx buffers in card window */ unsigned char numRxBuffers; /* No of rx buffers in card window */ unsigned int txBufferSize; /* Size of tx buffers in card window */ unsigned int rxBufferSize; /* Size of rx buffers in card window */ unsigned char terminalType; /* Additional hdsl */ unsigned char annexType; unsigned char encap; unsigned char testMode; unsigned char backoff; unsigned char bLineProbingEnable; unsigned char snrth; unsigned char lpath; unsigned short vpi; unsigned short vci; unsigned char activationStatus; unsigned char noCommonModeStatus; unsigned char transceiverStatus1; unsigned char transceiverStatus2; unsigned char lineLoss; char signalQuality; unsigned char nearEndBlockErrorCount; char signalToNoiseRatio; unsigned char erroredSecondCount; unsigned char severelyErroredSecondCount; unsigned char lossOfSyncWordSecondCount; unsigned char unavailableSecondCount; char frequencyDeviation; char negotiatedPowerBackOff; unsigned char negotiatedPSD; unsigned char negotiatedBChannels; unsigned char negotiatedZBits; unsigned short negotiatedSyncWord; unsigned char negotiatedStuffBits; unsigned char chipVersion; unsigned char firmwareVersion; unsigned char romVersion; unsigned short atmTxCellCount; unsigned short atmRxCellCount; unsigned short atmHecErrorCount; unsigned int atmCellsDropped; unsigned char transmitMSBFirst; unsigned char receiveMSBFirst; unsigned char xpldVersion; unsigned char farEndCountryCode[2]; unsigned char farEndProviderCode[4]; unsigned char farEndVendorInfo[2]; unsigned char utopiaAtmStatus; unsigned int termination; unsigned int txRxStart; unsigned char enableNRZIClocking; /* Ver 1 addition */ unsigned char lowLatency; /* Ver 1 addition */ struct fst_device_stats stats; /* Ver 1 addition */ struct fstioc_async_conf async_conf; /* Ver 2 addition */ unsigned char iocinfo_version; /* Ver 2 addition */ unsigned char extSyncClockEnable; /* Ver 3 addition */ unsigned char extSyncClockOffset; /* Ver 3 addition */ unsigned int extSyncClockRate; /* Ver 3 addition */ unsigned char ppsEnable; /* Ver 3 addition */ unsigned char ppsOffset; /* Ver 3 addition */ unsigned char cardRevMajor; /* Ver 4 addition */ unsigned char cardRevMinor; /* Ver 4 addition */ unsigned char cardRevBuild; /* Ver 4 addition */ unsigned int features; /* Ver 4 addition */ }; #define FST_MODE_HDLC 0 #define FST_MODE_TRANSPARENT 1 #define FST_MODE_BISYNC 2 #define FST_MODE_ASYNC 3 #define lowLatencyDisable 0 #define lowLatencyRx 1 #define lowLatencyTx 2 #define lowLatencyRxTx 3 /* * FSTSNOTIFY * */ #define FST_NOTIFY_OFF 0 #define FST_NOTIFY_ON 1 #define FST_NOTIFY_EXTENDED 2 #define FST_NOTIFY_BASIC_SIZE 2*sizeof(int) /* FSTGSTATE * * Used to query why a state change message has been issued by the driver * It could be because there was a change in line states or that the txq * has reached an empty state */ struct fstioc_status { int carrier_state; int txq_length; int rxq_length; struct fst_device_stats stats; }; /* FSTSYSREQ * * Used to provide a simple transparent command/repsonse interface between * an application and the firmware running on the card */ struct fstioc_req { unsigned short msg_type; unsigned short msg_len; unsigned short ret_code; unsigned short i_reg_idx; unsigned short value; unsigned char u_msg[16]; unsigned char u_msg_reserved[16]; unsigned char u_reserved[4]; }; #define MSG_FIFO_DEF_SLAVE_1X 0x0001 #define MSG_FIFO_DEF_SLAVE_16X 0x0002 #define MSG_FIFO_DEF_MASTER 0x0003 #define MSG_FIFO_EEPROM_RD 0x769b #define MSG_FIFO_EEPROM_WR 0xcd4a #define RSP_FIFO_SUCCESS 0x0000 #define RSP_FIFO_FAILURE 0x0001 /* FSTSETMON * * Used to provide a simple monitoring data */ #define FSTIOC_MON_VERSION 0 #define FST_MON_RX 0 #define FST_MON_TX 1 struct fstioc_mon { unsigned char version; unsigned char tx_rx_ind; unsigned int sequence; unsigned long timestamp; unsigned int length; }; /* FSTSETPORT * * Used to provide a DSL port control */ #define FST_DSL_PORT_NORMAL 0 #define FST_DSL_PORT_ACTIVE 1 /* FSTCMD * * Used to read and write card data */ #define FSTCMD_GET_SERIAL 0 #define FSTCMD_SET_V24O 1 #define FSTCMD_GET_VERSION 2 #define FSTCMD_SET_VERSION 3 #define FSTCMD_GET_INTS 4 #define FSTCMD_RESET_INTS 5 #define FSTCMD_RESET_STATS 6 #define FSTCMD_SET_READV 7 #define FSTCMD_SET_CHAR 8 #define FSTCMD_GET_PRESERVE_SIGNALS 9 #define FSTCMD_SET_PRESERVE_SIGNALS 10 #define FSTCMD_SET_LATENCY 11 #define FSTCMD_UPDATE_CLOCK 12 #define FSTCMD_SET_CUSTOM_RATE 13 #ifdef __x86_64__ #define fstioc_info_sz_old 316 #define fstioc_info_sz_ver1 504 #define fstioc_info_sz_ver2 512 #define fstioc_info_sz_ver3 528 #define fstioc_info_sz_ver4 536 #define fstioc_info_sz_current sizeof(struct fstioc_info) #else #define fstioc_info_sz_old 312 #define fstioc_info_sz_ver1 408 #define fstioc_info_sz_ver2 416 #define fstioc_info_sz_ver3 428 #define fstioc_info_sz_ver4 436 #define fstioc_info_sz_current sizeof(struct fstioc_info) #endif #define FST_VERSION 4 #define FST_VERSION_CURRENT FST_VERSION #define FST_VERSION_V3 3 #define FST_VERSION_V2 2 #define FST_VERSION_V1 1 #define FST_VERSION_OLD 0 #define FST_READV_NORMAL 0 #define FST_READV_SYNC 1 #define FST_READV_SYNC2 2 struct fstioc_char_data { unsigned char queue_len; unsigned char threshold; unsigned char pad[14]; }; struct fstioc_latency_data { unsigned int tx_size; unsigned int rx_size; unsigned int rate; }; struct fstioc_cmd { unsigned int version; unsigned int command; unsigned int status; unsigned int input_data_len; unsigned int output_data_len; unsigned char * data_ptr; }; #define FST_CUSTOM_RATE_CONFIG_VERSION 1 #define FST_CUSTOM_RATE_CONFIG_LENGTH (33+1) #define FST_CUSTOM_RATE_CLOCK_SLAVE 0 #define FST_CUSTOM_RATE_CLOCK_LOW_SLAVE 1 #define FST_CUSTOM_RATE_CLOCK_LOW_MASTER 2 #define FST_CUSTOM_CLOCK_MULTIPLIER_1 1 #define FST_CUSTOM_CLOCK_MULTIPLIER_16 2 struct fstioc_custom_rate_config { unsigned int version; unsigned int rate; unsigned int permanent; unsigned int multiplier; /* 1 or 16 */ unsigned int clock_type; /* slave, low_slave, low_master */ char rate_info[FST_CUSTOM_RATE_CONFIG_LENGTH]; }; /* "valid" bitmask */ #define FSTVAL_NONE 0x00000000 /* Nothing valid (firmware not running). * Slight misnomer. In fact nports, * type, state and index will be set * based on hardware detected. */ #define FSTVAL_OMODEM 0x0000001F /* First 5 bits correspond to the * output status bits defined for * v24OpSts */ #define FSTVAL_SPEED 0x00000020 /* internalClock, lineSpeed, clockStatus */ #define FSTVAL_CABLE 0x00000040 /* lineInterface, cableStatus */ #define FSTVAL_IMODEM 0x00000080 /* v24IpSts */ #define FSTVAL_CARD 0x00000100 /* nports, type, state, index, * smcFirmwareVersion */ #define FSTVAL_PROTO 0x00000200 /* proto */ #define FSTVAL_MODE 0x00000400 /* cardMode */ #define FSTVAL_PHASE 0x00000800 /* Clock phase */ #define FSTVAL_TE1 0x00001000 /* T1E1 Configuration */ #define FSTVAL_BUFFERS 0x00002000 /* Tx and Rx buffer settings */ #define FSTVAL_DSL_S1 0x00004000 /* DSL-S1 Configuration */ #define FSTVAL_T4E 0x00008000 /* T4E Mk II Configuration */ #define FSTVAL_FLEX 0x00010000 /* FarSync Flex */ #define FSTVAL_ASYNC 0x00020000 /* Async config */ #define FSTVAL_DEBUG 0x80000000 /* debug */ #define FSTVAL_ALL 0x000FFFFF /* Note: does not include DEBUG flag */ /* "type" */ #define FST_TYPE_NONE 0 /* Probably should never happen */ #define FST_TYPE_T2P 1 /* T2P X21 2 port card */ #define FST_TYPE_T4P 2 /* T4P X21 4 port card */ #define FST_TYPE_T1U 3 /* T1U X21 1 port card */ #define FST_TYPE_T2U 4 /* T2U X21 2 port card */ #define FST_TYPE_T4U 5 /* T4U X21 4 port card */ #define FST_TYPE_TE1 6 /* T1E1 X21 1 port card */ #define FST_TYPE_DSL_S1 7 /* DSL-S1 card */ #define FST_TYPE_T4E 8 /* T4E Mk II */ #define FST_TYPE_FLEX1 9 /* FarSync Flex 1 port */ #define FST_TYPE_T4UE 10 /* T4UE 4 port PCI Express */ #define FST_TYPE_T2UE 11 /* T2UE 2 port PCI Express */ #define FST_TYPE_T4Ep 12 /* T4E+ 4 port card */ #define FST_TYPE_T2U_PMC 13 /* T2U_PMC 2 port PCI card */ #define FST_TYPE_TE1e 14 /* TE1e X21 1 port PCI Express */ #define FST_TYPE_T2Ee 15 /* T2Ee 2 port PCI Express */ #define FST_TYPE_T4Ee 16 /* T4Ee 4 port PCI Express */ #define FST_TYPE_FLEX2 17 /* FarSync Flex 1 port (v2) */ /* "family" */ #define FST_FAMILY_TXP 0 /* T2P or T4P */ #define FST_FAMILY_TXU 1 /* T1U or T2U or T4U */ /* "state" */ #define FST_UNINIT 0 /* Raw uninitialised state following * system startup */ #define FST_RESET 1 /* Processor held in reset state */ #define FST_DOWNLOAD 2 /* Card being downloaded */ #define FST_STARTING 3 /* Released following download */ #define FST_RUNNING 4 /* Processor running */ #define FST_BADVERSION 5 /* Bad shared memory version detected */ #define FST_HALTED 6 /* Processor flagged a halt */ #define FST_IFAILED 7 /* Firmware issued initialisation failed * interrupt */ /* "lineInterface" */ #define V24 1 #define X21 2 #define V35 3 #define X21D 4 #define NOCABLE 5 #define RS530_449 6 #define T1 7 #define E1 8 #define J1 9 #define SHDSL 10 #define RS485 11 #define UX35C 12 #define RS485_FDX 13 #ifndef IF_IFACE_SHDSL #define IF_IFACE_SHDSL 0x1007 /* SHDSL (FarSite) */ #define IF_IFACE_RS530_449 0x1008 /* RS530_449 (FarSite) */ #define IF_IFACE_RS485 0x1009 /* RS485 (FarSite) */ #endif #define IF_IFACE_RS485_FDX 0x100A /* RS485 Full Duplex (Farsite) */ #define IF_IFACE_UX35C 0x100B /* UX35C (Farsite) */ /* "proto" */ #define FST_HDLC 1 /* Cisco compatible HDLC */ #define FST_PPP 2 /* Sync PPP */ #define FST_MONITOR 3 /* Monitor only (raw packet reception) */ #define FST_RAW 4 /* Two way raw packets */ #define FST_GEN_HDLC 5 /* Using "Generic HDLC" module */ /* "internalClock" */ #define INTCLK 1 #define EXTCLK 0 /* * The bit pattern for extendedClocking is * 8 4 2 1 |8 4 2 1 * ec |ttrx tttx irx itx */ #define EXT_CLOCK_NONE 0x00 #define EXT_CLOCK_ERX_ETX 0x80 #define EXT_CLOCK_ERX_ITX 0x81 #define EXT_CLOCK_IRX_ETX 0x82 #define EXT_CLOCK_IRX_ITX 0x83 #define EXT_CLOCK_DTE_TT 0x84 #define EXT_CLOCK_DCE_TT 0x8B /* "v24IpSts" bitmask */ #define IPSTS_CTS 0x00000001 /* Clear To Send (Indicate for X.21) */ #define IPSTS_INDICATE IPSTS_CTS #define IPSTS_DSR 0x00000002 /* Data Set Ready (T2P Port A) */ #define IPSTS_DCD 0x00000004 /* Data Carrier Detect */ #define IPSTS_RI 0x00000008 /* Ring Indicator (T2P Port A) */ #define IPSTS_TMI 0x00000010 /* Test Mode Indicator (Not Supported)*/ /* "v24OpSts" bitmask */ #define OPSTS_RTS 0x00000001 /* Request To Send (Control for X.21) */ #define OPSTS_CONTROL OPSTS_RTS #define OPSTS_DTR 0x00000002 /* Data Terminal Ready */ #define OPSTS_DSRS 0x00000004 /* Data Signalling Rate Select (Not * Supported) */ #define OPSTS_SS 0x00000008 /* Select Standby (Not Supported) */ #define OPSTS_LL 0x00000010 /* Local Loop */ #define OPSTS_DCD 0x00000020 /* Only when DCD is enabled as an output */ #define OPSTS_RL 0x00000040 /* Remote Loop */ /* "cardMode" bitmask */ #define CARD_MODE_IDENTIFY 0x0001 /* * TxRx Start Parameters */ #define START_TX 1 #define START_RX 2 #define START_TX_AND_RX (START_TX | START_RX) #define START_DEFAULT START_TX_AND_RX /* * Constants for T1/E1 configuration */ /* * Clock source */ #define CLOCKING_SLAVE 0 #define CLOCKING_MASTER 1 /* * Framing */ #define FRAMING_E1 0 #define FRAMING_J1 1 #define FRAMING_T1 2 /* * Structure */ #define STRUCTURE_UNFRAMED 0 #define STRUCTURE_E1_DOUBLE 1 #define STRUCTURE_E1_CRC4 2 #define STRUCTURE_E1_CRC4M 3 #define STRUCTURE_T1_4 4 #define STRUCTURE_T1_12 5 #define STRUCTURE_T1_24 6 #define STRUCTURE_T1_72 7 /* * Interface */ #define INTERFACE_RJ48C 0 #define INTERFACE_BNC 1 /* * Coding */ #define CODING_HDB3 0 #define CODING_NRZ 1 #define CODING_CMI 2 #define CODING_CMI_HDB3 3 #define CODING_CMI_B8ZS 4 #define CODING_AMI 5 #define CODING_AMI_ZCS 6 #define CODING_B8ZS 7 #define CODING_NRZI 8 #define CODING_FM0 9 #define CODING_FM1 10 #define CODING_MANCHESTER 11 #define CODING_DIFF_MANCHESTER 12 /* * Line Build Out */ #define LBO_0dB 0 #define LBO_7dB5 1 #define LBO_15dB 2 #define LBO_22dB5 3 /* * Range for long haul t1 > 655ft */ #define RANGE_0_133_FT 0 #define RANGE_0_40_M RANGE_0_133_FT #define RANGE_133_266_FT 1 #define RANGE_40_81_M RANGE_133_266_FT #define RANGE_266_399_FT 2 #define RANGE_81_122_M RANGE_266_399_FT #define RANGE_399_533_FT 3 #define RANGE_122_162_M RANGE_399_533_FT #define RANGE_533_655_FT 4 #define RANGE_162_200_M RANGE_533_655_FT /* * Receive Equaliser */ #define EQUALIZER_SHORT 0 #define EQUALIZER_LONG 1 /* * Loop modes */ #define LOOP_NONE 0 #define LOOP_LOCAL 1 #define LOOP_PAYLOAD_EXC_TS0 2 #define LOOP_PAYLOAD_INC_TS0 3 #define LOOP_REMOTE 4 /* * Buffer modes */ #define BUFFER_2_FRAME 0 #define BUFFER_1_FRAME 1 #define BUFFER_96_BIT 2 #define BUFFER_NONE 3 /* * DSL Equipment types */ #define EQUIP_TYPE_REMOTE 0 #define EQUIP_TYPE_CENTRAL 1 /* * DSL Operating modes */ #define ANNEX_A 1 /* US */ #define ANNEX_B 0 /* EU */ /* * DSL ATM Encapsulation methods */ #define ENCAP_PPP 0 #define ENCAP_MPOA 1 #define MPOA_HEADER_LEN 8 /* * DSL Test Modes */ #define TEST_MODE_NONE 0 #define TEST_MODE_DEFAULT TEST_MODE_NONE #define TEST_MODE_ALTERNATING_SINGLE_PULSE 1 #define TEST_MODE_ANALOG_TRANSPARENT_LOOP 4 #define TEST_MODE_ANALOG_NON_TRANSPARENT_LOOP 8 #define TEST_MODE_TRANSMIT_SC_SR 9 #define TEST_MODE_TRANSMIT_TC_PAM_SCRONE 10 #define TEST_MODE_LINE_DRIVER_NO_SIGNAL 11 #define TEST_MODE_AGC_TO_LINE_DRIVER_LOOP 12 #define TEST_MODE_LOOP_TDM_TO_LINE 16 #define TEST_MODE_LOOP_PAYLOAD_TO_LINE 17 /* Debug support * * These should only be enabled for development kernels, production code * should define FST_DEBUG=0 in order to exclude the code. * Setting FST_DEBUG=1 will include all the debug code but in a disabled * state, use the FSTSETCONF ioctl to enable specific debug actions, or * FST_DEBUG can be set to prime the debug selection. */ #define FST_DEBUG 0x0000 #if FST_DEBUG extern int fst_debug_mask; /* Bit mask of actions to debug, bits * listed below. Note: Bit 0 is used * to trigger the inclusion of this * code, without enabling any actions. */ #define DBG_INIT 0x0002 /* Card detection and initialisation */ #define DBG_OPEN 0x0004 /* Open and close sequences */ #define DBG_PCI 0x0008 /* PCI config operations */ #define DBG_IOCTL 0x0010 /* Ioctls and other config */ #define DBG_INTR 0x0020 /* Interrupt routines (be careful) */ #define DBG_TX 0x0040 /* Packet transmission */ #define DBG_RX 0x0080 /* Packet reception */ #define DBG_CMD 0x0100 /* Port command issuing */ #define DBG_ATM 0x0200 /* ATM processing */ #define DBG_TTY 0x0400 /* PPPd processing */ #define DBG_USB 0x0800 /* USB device */ #define DBG_ASY 0x1000 /* Async functions */ #define DBG_FIFO 0x2000 /* Fifo functions */ #define DBG_ASS 0x0001 /* Assert like statements. Code that * should never be reached, if you see * one of these then I've been an ass */ #endif /* FST_DEBUG */ Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/windows/000077500000000000000000000000001476627344300230475ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/windows/fscfg.h000066400000000000000000000553571476627344300243270ustar00rootroot00000000000000/******************************************************************************* * * Program : FarSync (generic) * * File : fscfg.h * * Description : Common FarSync Configuration Definitions used through the * FarSync PC-based modules * * Modifications * * Version 2.1.0 01Mar01 WEB Add device and port configuration values * 09Mar01 WEB Add Fswan hwid * Version 2.2.0 18Jul01 WEB Certification candidate * 22Oct01 MJD Added Transparent Mode support - define * names and values for Transparent Mode and * Buffer Length parameters. * Version 2.2.6 26Oct01 WEB Add name for devices use only for SDCI applications. * Version 2.3.0 28Nov01 WEB Add minnow ids + DeviceIDToCardID map * Version 2.3.1 08Apr02 WEB Change min buffer length from 62 to 2 * Version 2.3.2 27Aug02 WEB Rationalised * Version 3.0.2 05Nov02 WEB Add 'U' to DeviceIDToCardID map * Version 3.2.0 09Dec02 WEB Add Invert Rx Clock, Dual Clocking & DMA definitions * Version 3.4.0 26Oct04 WEB Add fstap definitions * Version 3.5.0 11Nov04 WEB Add te1 definitions * Version 4.0.0 18Feb05 WEB Add configurable number and size of buffers, start * tx/rx * Version 4.0.1 03Mar05 WEB Add extended clocking definitions * Version 4.0.2 17Mar05 WEB Correct FS_LINE_INTERFACE_MAX - should be V35 * Version 4.1.0 24Jun05 WEB Add new T4E defs * Version 4.2.0 12Sep05 WEB Add FS_NDEVICE_CLASS i.e. support for T4E MkII * Reduce FS_LINE_RATE_MIN to 300 * *******************************************************************************/ #ifndef __FSCFG_H_ #define __FSCFG_H_ #define SMCUSER_PACKING 1 #include "smcuser.h" // Values also used in the adapter window interface */ // PCI IDs #define FS_VENDOR_ID 0x1619 #define FS_DEVICE_CLASS 0x0400 // ie. 0400 .. 04FF #define FS_TDEVICE_CLASS FS_DEVICE_CLASS // FarSync card classes/Device ID ranges #define FS_MDEVICE_CLASS 0x0500 // ie. 0500 .. 05FF #define FS_VDEVICE_CLASS 0x0000 // ie. 0000 .. 00FF #define FS_UDEVICE_CLASS 0x0600 // ie. 0600 .. 06FF #define FS_EDEVICE_CLASS 0x1600 // ie. 1600 .. 16FF #define FS_NDEVICE_CLASS 0x3600 // ie. 3600 .. 36FF #define FS_DDEVICE_CLASS 0x1700 // ie. 1700 .. 17FF #define FS_CARDID_MASK 0xFF00 // Class maps struct _DEVICEIDTOCARDID// e.g. 0400 -> T, 0500 -> M, 0600 -> T { ULONG uDeviceID; char cCardID; }; #define DEVICEIDTOCARDID struct _DEVICEIDTOCARDID #ifdef FS_DECLARE_CFG_ARRAYS DEVICEIDTOCARDID DeviceIDToCardID[] = { {FS_TDEVICE_CLASS, 'T'}, {FS_MDEVICE_CLASS, 'M'}, {FS_VDEVICE_CLASS, 'V'}, {FS_UDEVICE_CLASS, 'T'}, {FS_EDEVICE_CLASS, 'T'}, {FS_DDEVICE_CLASS, 'D'}, {FS_NDEVICE_CLASS, 'T'} }; typedef struct _DEVICEIDTOCARDID2 { ULONG uDeviceID; char cCardID; } DEVICEIDTOCARDID2; // e.g. 0400 -> P, 0500 -> P, 0600 -> U #endif #ifdef FS_DECLARE_CFG_ARRAYS DEVICEIDTOCARDID DeviceIDToCardID2[] = { {FS_TDEVICE_CLASS, 'P'}, {FS_MDEVICE_CLASS, 'P'}, {FS_VDEVICE_CLASS, 'P'}, {FS_UDEVICE_CLASS, 'U'}, {FS_EDEVICE_CLASS, 'E'}, {FS_DDEVICE_CLASS, 'S'}, {FS_NDEVICE_CLASS, 'E'} }; #else DEVICEIDTOCARDID DeviceIDToCardID2[]; #endif #define FS_FSWAN_HWID "fsvbus\\FS_TXP-00" // must match value in .inf #define FS_VBUS_PREFIX_U L"fsv" // Limits required for adapter/port enumeration #define FS_MAX_ADAPTERS 100 #define FS_MAX_SYNC_ADAPTERS 10 #define FS_MAX_PORTS MAX_PORTS #define FS_MAX_DEVICE_NAME 32 // number of WCHARs required to accomodate the longest device name // e.g. \Device\SYNCH, \DosDevices\SYNC1 or \DosDevices\FSPPP0006 #define FS_DEVICE_PREFIX "SYNC" #define FS_DEVICE_PREFIX_U L"SYNC" #define FS_PPP_DEVICE_NAME "FSPPP%s" // name of farsynct device when in ppp mode #define FS_SDCI_DEVICE_NAME "SDCI%s" // name of farsynct device when in sdci mode #define FS_PORT_DEVICE_NAME "PortDeviceName" // stores name of farsynct port to use #define LFS_PORT_DEVICE_NAME L"PortDeviceName" // required for use in ndiswan driver #define FS_MAX_PARAM_NAME 128 // max number of chars in FS_LINE_NAME_PARAM_NAME etc. /* Line name values */ #define FS_LINE_NAME_PARAM_NAME "Port%u_%u_Name" #define FS_LINE_NAME_MAX_CHARS 16 #define FS_LINE_NAME_DEF "FSWAN_%u" #define FS_PORT_NAME_DEF "Port %c" #define FS_PORT_NAME_PARAM_NAME "Port%u_Name" /* Line interface values */ #define FS_LINE_INTERFACE_PARAM_NAME "Port%u_%u_Interface" #define FS_LINE_INTERFACE_MIN V24 #define FS_LINE_INTERFACE_V24 V24 #define FS_LINE_INTERFACE_X21 X21 #define FS_LINE_INTERFACE_X21D X21D #define FS_LINE_INTERFACE_V35 V35 #define FS_LINE_INTERFACE_RS530_449 RS530_449 // Note: Specifying MAX as X21D intentionally makes RS530_449 not accessible to the current client GUIs #define FS_LINE_INTERFACE_MAX RS530_449 #define FS_LINE_INTERFACE_DEF X21 #define FS_LINE_INTERFACE_MAX_CHARS 7 #define FS_PORT_INTERFACE_PARAM_NAME "Port%u_Interface" #define FS_PORT_INTERFACE_PARAM_NAME_U L"Port%u_Interface" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsT4EInterfaceTypesTable[] = {"Auto", "V.24", "X.21", "V.35", "X.21 (Dual-Clocking)", "RS530/449", 0}; char* szFsT4EInterfaceTypesTable2[] = {"Auto", "V.24", "X.21", "V.35", "X.21D", "RS530", 0}; ULONG uFsT4EInterfaceTypesTableVals[] = {AUTO, V24, X21, V35, X21D, RS530_449, 0}; char* szFsInterfaceTypesTable[] = {"Auto", "V.24", "X.21", "V.35", "X.21 (Dual-Clocking)", 0}; char* szFsM1PInterfaceTypesTable[] = {"Auto", "V.24", "X.21", "V.35", 0}; #else char* szFsT4EInterfaceTypesTable[]; char* szFsT4EInterfaceTypesTable2[]; ULONG uFsT4EInterfaceTypesTableVals[]; char* szFsInterfaceTypesTable[]; char* szFsM1PInterfaceTypesTable[]; #endif /* Line rate values */ #define FS_LINE_RATE_PARAM_NAME "Port%u_%u_Rate" #define FS_LINE_RATE_MIN 300 #define FS_LINE_RATE_MAX 8192000 #define FS_LINE_RATE_DEF 64000 #define FS_LINE_RATE_MAX_CHARS 7 #define FS_PORT_RATE_PARAM_NAME "Port%u_Rate" #define FS_PORT_RATE_PARAM_NAME_U L"Port%u_Rate" #ifdef FS_DECLARE_CFG_ARRAYS ULONG uFsRateTable[] = {1200, 2400, 4800, 9600, 19200, 38400, 64000, 76800, 128000, 153600, 256000, 307200, 512000, 614400, 1024000, 2048000, 4096000, 8192000, 0 }; #else ULONG uFsRateTable[]; #endif /* Line clocking values */ #define FS_LINE_CLOCKING_PARAM_NAME "Port%u_%u_InternalClocking" #define FS_LINE_CLOCKING_MIN EXTCLK #define FS_LINE_CLOCKING_EXTERNAL EXTCLK #define FS_LINE_CLOCKING_INTERNAL INTCLK #define FS_LINE_CLOCKING_MAX INTCLK #define FS_LINE_CLOCKING_DEF EXTCLK #define FS_LINE_CLOCKING_MAX_CHARS 8 #define FS_PORT_CLOCKING_PARAM_NAME "Port%u_InternalClocking" #define FS_PORT_CLOCKING_PARAM_NAME_U L"Port%u_InternalClocking" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsClockingTypesTable[] = {"External", "Internal", 0}; // maps onto FALSE, TRUE #else char* szFsClockingTypesTable[]; #endif /* Transparent mode values */ #define FS_LINE_TRANSPARENT_MIN FALSE #define FS_LINE_TRANSPARENT_MAX TRUE #define FS_LINE_TRANSPARENT_DEF FALSE #define FS_PORT_TRANSPARENT_PARAM_NAME "Port%u_TransparentMode" #define FS_PORT_TRANSPARENT_PARAM_NAME_U L"Port%u_TransparentMode" /* HDLC/Transparent mode values */ #define FS_LINE_MODE_HDLC 0 #define FS_LINE_MODE_TRANSPARENT 1 #define FS_LINE_MODE_MIN FS_LINE_MODE_HDLC #define FS_LINE_MODE_MAX FS_LINE_MODE_TRANSPARENT #define FS_LINE_MODE_DEF FS_LINE_MODE_HDLC #define FS_LINE_MODE_MAX_CHARS 12 #ifdef FS_DECLARE_CFG_ARRAYS char* szFsModeTypesTable[] = {"HDLC", "Transparent", 0}; #else char* szFsModeTypesTable[]; #endif /* Buffer length values */ #define FS_LINE_BUFFER_MIN 2 #define FS_LINE_BUFFER_MAX 64*1024 #define FS_LINE_BUFFER_DEF 8*1024 #define FS_PORT_BUFFER_PARAM_NAME "Port%u_BufferLength" #define FS_PORT_BUFFER_PARAM_NAME_U L"Port%u_BufferLength" #define FS_PORT_TX_BUFFER_PARAM_NAME "Port%u_TxBufferLength" #define FS_PORT_TX_BUFFER_PARAM_NAME_U L"Port%u_TxBufferLength" #define FS_PORT_RX_BUFFER_PARAM_NAME "Port%u_RxBufferLength" #define FS_PORT_RX_BUFFER_PARAM_NAME_U L"Port%u_RxBufferLength" // Note: UNDEF values means that the vars are ignored (i.e. different from DEFault values) /* Invert Rx clock values */ #define FS_PORT_INVERT_RX_CLOCK_MIN FALSE #define FS_PORT_INVERT_RX_CLOCK_MAX TRUE #define FS_PORT_INVERT_RX_CLOCK_DEF FALSE #define FS_PORT_INVERT_RX_CLOCK_UNDEF 0xFFFFFFFF #define FS_PORT_INVERT_RX_CLOCK_PARAM_NAME "Port%u_InvertRxClock" #define FS_PORT_INVERT_RX_CLOCK_PARAM_NAME_U L"Port%u_InvertRxClock" /* Dual Clocking values */ #define FS_PORT_DUAL_CLOCKING_DEF FALSE #define FS_PORT_DUAL_CLOCKING_PARAM_NAME "Port%u_DualClocking" #define FS_PORT_DUAL_CLOCKING_PARAM_NAME_U L"Port%u_DualClocking" /* Tx DMA values */ #define FS_PORT_TX_DMA_UNDEF 0xFFFFFFFF #define FS_PORT_TX_DMA_PARAM_NAME "Port%u_TxDMA" #define FS_PORT_TX_DMA_PARAM_NAME_U L"Port%u_TxDMA" /* Rx DMA values */ #define FS_PORT_RX_DMA_UNDEF 0xFFFFFFFF #define FS_PORT_RX_DMA_PARAM_NAME "Port%u_RxDMA" #define FS_PORT_RX_DMA_PARAM_NAME_U L"Port%u_RxDMA" #define FS_PORT_DMA_MAX FSDMAMODE_ON #define FS_PORT_DMA_MIN FSDMAMODE_OFF #define FS_PORT_DMA_DEF FSDMAMODE_OFF /* Number of Tx buffers */ #define FS_PORT_TX_NUM_BUFFERS_MIN 2 #define FS_PORT_TX_NUM_BUFFERS_MAX 128 #define FS_PORT_TX_NUM_BUFFERS_DEF 8 #define FS_PORT_TX_NUM_BUFFERS_UNDEF 0xFFFFFFFF #define FS_PORT_TX_NUM_BUFFERS_PARAM_NAME "Port%u_TxNumBuffers" #define FS_PORT_TX_NUM_BUFFERS_PARAM_NAME_U L"Port%u_TxNumBuffers" /* Number of Rx buffers */ #define FS_PORT_RX_NUM_BUFFERS_MIN 2 #define FS_PORT_RX_NUM_BUFFERS_MAX 128 #define FS_PORT_RX_NUM_BUFFERS_DEF 8 #define FS_PORT_RX_NUM_BUFFERS_UNDEF 0xFFFFFFFF #define FS_PORT_RX_NUM_BUFFERS_PARAM_NAME "Port%u_RxNumBuffers" #define FS_PORT_RX_NUM_BUFFERS_PARAM_NAME_U L"Port%u_RxNumBuffers" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsNumBuffersTable[] = {"2", "4", "8", "16", "32", "64", "128", 0}; #else char* szFsNumBuffersTable[]; #endif /* Encoding values */ #define FS_PORT_ENCODING_PARAM_NAME "Port%u_Encoding" #define FS_PORT_ENCODING_PARAM_NAME_U L"Port%u_Encoding" #define FS_PORT_ENCODING_NRZ 0x80 #define FS_PORT_ENCODING_NRZI 0xa0 #define FS_PORT_ENCODING_FM0 0xc0 #define FS_PORT_ENCODING_FM1 0xd0 #define FS_PORT_ENCODING_MAN 0xe0 #define FS_PORT_ENCODING_MIN FS_PORT_ENCODING_NRZ #define FS_PORT_ENCODING_MAX FS_PORT_ENCODING_MAN #define FS_PORT_ENCODING_DEF FS_PORT_ENCODING_NRZ #ifdef FS_DECLARE_CFG_ARRAYS char* szFsEncodingTypesTable[] = {"NRZ", "NRZI", "FM0", "FM1", "MAN", 0}; #else char* szFsEncodingTypesTable[]; #endif /* Registry Config Names */ #define FS_EVENT_SUBKEY_NAME "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System\\farsynct" #define EVENT_VAR_TYPES_SUPPORTED "TypesSupported" #define EVENT_VAR_MESSAGE_FILE "EventMessageFile" #define FS_MESSAGE_FILE "%SystemRoot%\\System32\\IOLOGMSG.DLL;%SystemRoot%\\System32\\Drivers\\farsynct.sys" #define REG_SERVICES_ROOT_U L"\\REGISTRY\\Machine\\SYSTEM\\CurrentControlSet\\Services\\" #define REG_MACHINE_ROOT_U L"\\REGISTRY\\Machine\\" #define REG_FSWAN_CONFIG_PATH_U L"SOFTWARE\\FarSite\\FSWAN" /* Pool Tags */ #define FARSYNCT_TAG1 '1TsF' #define FARSYNCM_TAG1 '1PsF' #define FSX25MDM_TAG1 '1MsF' #define FSKUTL_TAG1 '1UsF' #define FSWAN_TAG1 '1WsF' #define FSWAN_TAG2 '2WsF' /* fstap config */ #define FSTAP_DEVICENAME_PARAM_NAME "DeviceName" #define FSTAP_PORT_PARAM_NAME "Port" #define FSTAP_IGNORE_SIGNALS_DEF FALSE #define FSTAP_IGNORE_SIGNALS_PARAM_NAME "IgnoreSignals" #define FSTAP_ENABLE_MONITORING_DEF TRUE #define FSTAP_ENABLE_MONITORING_PARAM_NAME "EnableMonitoring" /* te1 config */ #define FS_PORT_FRAMING_MIN FRAMING_E1 #define FS_PORT_FRAMING_MAX FRAMING_T1 #define FS_PORT_FRAMING_PARAM_NAME "Port%u_Framing" #define FS_PORT_FRAMING_PARAM_NAME_U L"Port%u_Framing" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsFramingTypesTable[] = {"E1", "J1", "T1", 0}; #else char* szFsFramingTypesTable[]; #endif #define FS_PORT_ECLOCKING_MIN CLOCKING_SLAVE #define FS_PORT_ECLOCKING_MAX CLOCKING_MASTER #ifdef notreq #define FS_PORT_ECLOCKING_PARAM_NAME "Port%u_Clocking" #define FS_PORT_ECLOCKING_PARAM_NAME_U L"Port%u_Clocking" #endif #ifdef FS_DECLARE_CFG_ARRAYS char* szFsEClockingTypesTable[] = {"Slave", "Master", 0}; #else char* szFsEClockingTypesTable[]; #endif #define FS_PORT_DATARATE_MIN 8000 #define FS_PORT_E1_DATARATE_MAX 2048000 #define FS_PORT_T1_DATARATE_MAX 1544000 #ifdef notreq #define FS_PORT_DATARATE_PARAM_NAME "Port%u_DataRate" #define FS_PORT_DATARATE_PARAM_NAME_U L"Port%u_DataRate" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsDataRateTable[] = {"1", "2", 0}; #else char* szFsDataRateTable[]; #endif #endif #define FS_PORT_STRUCTURE_MIN STRUCTURE_UNFRAMED #define FS_PORT_STRUCTURE_MAX STRUCTURE_T1_72 #define FS_PORT_STRUCTURE_PARAM_NAME "Port%u_Structure" #define FS_PORT_STRUCTURE_PARAM_NAME_U L"Port%u_Structure" #define STRUCTURE_UNFRAMED 0 #define STRUCTURE_E1_DOUBLE 1 #define STRUCTURE_E1_CRC4 2 #define STRUCTURE_E1_DEFAULT STRUCTURE_E1_CRC4 #define STRUCTURE_DEFAULT STRUCTURE_E1_CRC4 #define STRUCTURE_E1_CRC4M 3 #define STRUCTURE_T1_4 4 #define STRUCTURE_T1_12 5 #define STRUCTURE_T1_24 6 #define STRUCTURE_T1_DEFAULT STRUCTURE_T1_24 #define STRUCTURE_T1_72 7 #ifdef FS_DECLARE_CFG_ARRAYS char* szFsE1StructureTypesTable[] = {"Unframed", "Double", "CRC4", "CRC4M", 0}; int iFsE1StructureValuesTable[] = {STRUCTURE_UNFRAMED, STRUCTURE_E1_DOUBLE, STRUCTURE_E1_CRC4, STRUCTURE_E1_CRC4M, -1}; char* szFsT1StructureTypesTable[] = {"Unframed", "F4 (FT)", "F12 (D3/D4, SF)", "F24 (D5, Fe, ESF)", "F72 (SLC96)", 0}; int iFsT1StructureValuesTable[] = {STRUCTURE_UNFRAMED, STRUCTURE_T1_4, STRUCTURE_T1_12, STRUCTURE_T1_24, STRUCTURE_T1_72, -1}; #else char* szFsE1StructureTypesTable[]; int iFsE1StructureValuesTable[]; char* szFsT1StructureTypesTable[]; int iFsT1StructureValuesTable[]; #endif #define FS_PORT_IFACE_MIN INTERFACE_RJ48C #define FS_PORT_IFACE_MAX INTERFACE_BNC #define FS_PORT_IFACE_PARAM_NAME "Port%u_Iface" #define FS_PORT_IFACE_PARAM_NAME_U L"Port%u_Iface" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsE1IfaceTypesTable[] = {"RJ48C", "BNC", 0}; int iFsE1IfaceValuesTable[] = {INTERFACE_RJ48C, INTERFACE_BNC, -1}; char* szFsT1IfaceTypesTable[] = {"RJ48C", 0}; int iFsT1IfaceValuesTable[] = {INTERFACE_RJ48C, -1}; #else char* szFsE1IfaceTypesTable[]; int iFsE1IfaceValuesTable[]; char* szFsT1IfaceTypesTable[]; int iFsT1IfaceValuesTable[]; #endif #define FS_PORT_CODING_MIN CODING_HDB3 #define FS_PORT_CODING_MAX CODING_B8ZS #define FS_PORT_CODING_PARAM_NAME "Port%u_Coding" #define FS_PORT_CODING_PARAM_NAME_U L"Port%u_Coding" #ifdef FS_DECLARE_CFG_ARRAYS //char* szFsE1CodingTypesTable[] = {"HDB3", "NRZ", "CMI", "CMI-HDB3", "AMI", 0}; //int iFsE1CodingValuesTable[] = {CODING_HDB3, CODING_NRZ, CODING_CMI, CODING_CMI_HDB3, CODING_AMI, -1}; char* szFsE1CodingTypesTable[] = {"HDB3", "AMI", 0}; int iFsE1CodingValuesTable[] = {CODING_HDB3, CODING_AMI, -1}; //char* szFsT1CodingTypesTable[] = {"NRZ", "CMI", "CMI_B8ZS", "AMI", "AMI-ZCS", "B8ZS", 0}; //int iFsT1CodingValuesTable[] = {CODING_NRZ, CODING_CMI, CODING_CMI_B8ZS, CODING_AMI, CODING_AMI_ZCS, CODING_B8ZS, -1}; char* szFsT1CodingTypesTable[] = {"AMI", "AMI-ZCS", "B8ZS", 0}; int iFsT1CodingValuesTable[] = {CODING_AMI, CODING_AMI_ZCS, CODING_B8ZS, -1}; #else char* szFsE1CodingTypesTable[]; int iFsE1CodingValuesTable[]; char* szFsT1CodingTypesTable[]; int iFsT1CodingValuesTable[]; #endif #define FS_PORT_LBO_MIN LBO_0dB #define FS_PORT_LBO_MAX LBO_22dB5 #define FS_PORT_LBO_PARAM_NAME "Port%u_LBO" #define FS_PORT_LBO_PARAM_NAME_U L"Port%u_LBO" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsLBOTypesTable[] = {"0", "7.5", "15", "22.5", 0}; #else char* szFsLBOTypesTable[]; #endif #define FS_PORT_EQUALIZER_MIN EQUALIZER_SHORT #define FS_PORT_EQUALIZER_MAX EQUALIZER_LONG #define FS_PORT_EQUALIZER_PARAM_NAME "Port%u_Equalizer" #define FS_PORT_EQUALIZER_PARAM_NAME_U L"Port%u_Equalizer" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsEqualizerTypesTable[] = {"Short", "Long", 0}; #else char* szFsEqualizerTypesTable[]; #endif #define FS_PORT_LOOP_MODE_MIN LOOP_NONE #define FS_PORT_LOOP_MODE_MAX LOOP_REMOTE #define FS_PORT_LOOP_MODE_PARAM_NAME "Port%u_LoopMode" #define FS_PORT_LOOP_MODE_PARAM_NAME_U L"Port%u_LoopMode" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsLoopModeTypesTable[] = {"None", "Local", "Payload (excl TS0)", "Payload (incl TS0)", "Remote", 0}; #else char* szFsLoopModeTypesTable[]; #endif #define FS_PORT_RANGE_MIN RANGE_0_40_M #define FS_PORT_RANGE_MAX RANGE_162_200_M #define FS_PORT_RANGE_PARAM_NAME "Port%u_Range" #define FS_PORT_RANGE_PARAM_NAME_U L"Port%u_Range" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsRangeTypesTable[] = {"0-133ft (0-40m)", "133-266ft (40-81m)", "266-399ft (81-122m)", "399-533ft (122-162m)", "533-655ft (162-200m)", 0}; #else char* szFsRangeTypesTable[]; #endif #define FS_PORT_BUFFER_MODE_MIN BUFFER_2_FRAME #define FS_PORT_BUFFER_MODE_MAX BUFFER_NONE #define FS_PORT_TX_BUFFER_MODE_PARAM_NAME "Port%u_TxBufferMode" #define FS_PORT_TX_BUFFER_MODE_PARAM_NAME_U L"Port%u_TxBufferMode" #define FS_PORT_RX_BUFFER_MODE_PARAM_NAME "Port%u_RxBufferMode" #define FS_PORT_RX_BUFFER_MODE_PARAM_NAME_U L"Port%u_RxBufferMode" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsBufferModeTypesTable[] = {"2 Frame", "1 Frame", "96 bit", "None", 0}; #else char* szFsBufferModeTypesTable[]; #endif #define FS_PORT_STARTING_TS_MIN 0 #define FS_PORT_STARTING_TS_MAX 31 #define FS_PORT_STARTING_TS_PARAM_NAME "Port%u_StartingTs" #define FS_PORT_STARTING_TS_PARAM_NAME_U L"Port%u_StartingTs" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsE1StartingTSTypesTable[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", 0}; char* szFsT1StartingTSTypesTable[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", 0}; #else char* szFsE1StartingTSTypesTable[]; char* szFsT1StartingTSTypesTable[]; #endif #define FS_PORT_LOS_THRESHOLD_MIN 0 #define FS_PORT_LOS_THRESHOLD_MAX 7 #define FS_PORT_LOS_THRESHOLD_PARAM_NAME "Port%u_LOSThreshold" #define FS_PORT_LOS_THRESHOLD_PARAM_NAME_U L"Port%u_LOSThreshold" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsLOSThresholdTypesTable[] = { "0", "1", "2", "3", "4", "5", "6", "7", 0}; #else char* szFsLOSThresholdTypesTable[]; #endif #define FS_PORT_ENABLE_IDLE_CODE_MIN 0 #define FS_PORT_ENABLE_IDLE_CODE_MAX 1 #define FS_PORT_ENABLE_IDLE_CODE_PARAM_NAME "Port%u_EnableIdleCode" #define FS_PORT_ENABLE_IDLE_CODE_PARAM_NAME_U L"Port%u_EnableIdleCode" #define FS_PORT_IDLE_CODE_MIN 0 #define FS_PORT_IDLE_CODE_MAX 0xff #define FS_PORT_IDLE_CODE_PARAM_NAME "Port%u_IdleCode" #define FS_PORT_IDLE_CODE_PARAM_NAME_U L"Port%u_IdleCode" #define FS_PORT_START_TXRX_MIN 0 #define FS_PORT_START_TXRX_DEF START_TX_AND_RX #define FS_PORT_START_TXRX_MAX START_TX_AND_RX #define FS_PORT_START_TXRX_PARAM_NAME "Port%u_StartTxRx" #define FS_PORT_START_TXRX_PARAM_NAME_U L"Port%u_StartTxRx" #define FS_PORT_CLOCK_SOURCE_MIN FS_CLOCK_REFERENCE_OSCILLATOR #define FS_PORT_CLOCK_SOURCE_DEF FS_CLOCK_REFERENCE_OSCILLATOR #define FS_PORT_CLOCK_SOURCE_MAX FS_CLOCK_REFERENCE_CTBUS #define FS_PORT_CLOCK_SOURCE_PARAM_NAME "Port%u_ClockSource" #define FS_PORT_CLOCK_SOURCE_PARAM_NAME_U L"Port%u_ClockSource" #ifdef FS_DECLARE_CFG_ARRAYS char* szFsClockSourceTable[] = {"Local Oscillator", "CT_BUS", 0}; #else char* szFsClockSourceTable[]; #endif #define FS_SERIAL_EXTENDED_CLOCKING_DEF FALSE #define FS_SERIAL_EXTENDED_CLOCKING_PARAM_NAME "Port%u_ExtendedClocking" #define FS_SERIAL_EXTENDED_CLOCKING_PARAM_NAME_U L"Port%u_ExtendedClocking" #define FS_SERIAL_INT_TX_CLOCK_DEF FALSE #define FS_SERIAL_INT_TX_CLOCK_PARAM_NAME "Port%u_InternalTxClock" #define FS_SERIAL_INT_TX_CLOCK_PARAM_NAME_U L"Port%u_InternalTxClock" #define FS_SERIAL_INT_RX_CLOCK_DEF FALSE #define FS_SERIAL_INT_RX_CLOCK_PARAM_NAME "Port%u_InternalRxClock" #define FS_SERIAL_INT_RX_CLOCK_PARAM_NAME_U L"Port%u_InternalRxClock" #define FS_SERIAL_TERM_TX_CLOCK_DEF FALSE #define FS_SERIAL_TERM_TX_CLOCK_PARAM_NAME "Port%u_TerminalTxClock" #define FS_SERIAL_TERM_TX_CLOCK_PARAM_NAME_U L"Port%u_TerminalTxClock" #define FS_SERIAL_TERM_RX_CLOCK_DEF FALSE #define FS_SERIAL_TERM_RX_CLOCK_PARAM_NAME "Port%u_TerminalRxClock" #define FS_SERIAL_TERM_RX_CLOCK_PARAM_NAME_U L"Port%u_TerminalRxClock" #define FS_SERIAL_DCD_OUTPUT_DEF FALSE #define FS_SERIAL_DCD_OUTPUT_PARAM_NAME "Port%u_DCDOutput" #define FS_SERIAL_DCD_OUTPUT_PARAM_NAME_U L"Port%u_DCDOutput" #define FS_PORT_MSB_DEF FALSE #define FS_PORT_TX_MSB_PARAM_NAME "Port%u_TxMSB" #define FS_PORT_TX_MSB_PARAM_NAME_U L"Port%u_TxMSB" #define FS_PORT_RX_MSB_PARAM_NAME "Port%u_RxMSB" #define FS_PORT_RX_MSB_PARAM_NAME_U L"Port%u_RxMSB" #endif // __FSCFG_H_ Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/windows/sdci.h000066400000000000000000001132441476627344300241470ustar00rootroot00000000000000/******************************************************************************* * * Program : FarSync (generic) * * File : sdci.h * * Description : This header file is based on the Equates and Structure Layouts * page of the SDCI section of the July '99 MSDN * * Modifications * * Version 2.1.0 01Mar01 WEB Add QuickStart and GetLinkInterface * Version 2.2.0 18Jul01 WEB Certification candidate * Version 2.2.1 07Aug01 JPG Add FarSyncReadSignals * Version 2.2.2 03Sep01 WEB Extend USERDPC definition to include indication * param instead of bReadIR * 22Oct01 MJD Added Transparent Mode support - defined * LinkOption_Transparent * Version 3.0.0 12Sep02 WEB Add dma mode and cardinfoex support definitions * Version 3.1.0 07Nov02 WEB Add interrupt handshake mode support definitions * Version 3.2.0 09Dec02 WEB Add LinkOption_InvertRxClock * Add ResetStats IOCTL and extended InterfaceRecord * Complete CardInfoEx definition * Version 3.3.0 30Apr04 WEB Extend CardInfoEx to include physical + IO address * Version 3.3.1 03Mar05 WEB Removed SA_TxAbort which was previously incorrectly * named * Version 4.0.1 03Mar05 WEB Correct IOCTL_SDCI_xxx definitions. Now requires * CTL_CODE to be defined i.e winioctl.h (user-mode) or * wdm.h (kernel-mode) must be included before this file. * Add USERDPC_INDICATION_TX, FarSyncSetUserDpcEx, * IoctlCodeFarSyncSetPortConfig, * IoctlCodeFarSyncGetPortConfig, * IoctlCodeFarSyncSetSerialConfig, * IoctlCodeFarSyncGetSerialConfig * IoctlCodeFarSyncSetCTBusConfig & * IoctlCodeFarSyncGetCTBusConfig * Version 4.0.1.1 03Mar05 WEB Update FS_XXX_CONFIG structures * Version 4.0.1.2 04Mar05 WEB Update error counter explanations, * FarSync-specific error codes & further * rationalisations * Version 4.1.0.0 15Jun05 WEB Add TE1 and additional T4E definitions * Add monitoring IOCTLs * Version 4.1.0.1 02Aug05 MJD Added USERDPC_INDICATION_CLOCK_SWITCH_TO_PRIMARY * Version 4.1.0.2 09Aug05 MJD Update FS_CLOCKING_STATUS to include uCTAInUse so * that apps can tell which CT Bus the hardware is * using in slave mode, structure version now 2. * Version 4.2.0 12Sep05 WEB Add async and synth detection fields to * FSCARDINFOEX * SA_RxFrameTooBig is now supported by M1P * Add FS_ERROR_INVALID_LENGTH definition * *******************************************************************************/ #ifndef __SDCI_H_ #define __SDCI_H_ /*****************************************************************************/ // // There are the IOCTL code values used to communicate with the SDCI driver. // /*****************************************************************************/ #define IOCTL_SDCI_SetEvent CTL_CODE(0, (0x410 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_SetLinkCharacteristics CTL_CODE(0, (0x420 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_SetV24OutputStatus CTL_CODE(0, (0x430 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_TransmitFrame CTL_CODE(0, (0x440 >> 2), METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define IOCTL_SDCI_AbortTransmit CTL_CODE(0, (0x450 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_AbortReceiver CTL_CODE(0, (0x460 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_OffBoardLoad CTL_CODE(0, (0x470 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_GetV24Status CTL_CODE(0, (0x620 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_ReceiveFrame CTL_CODE(0, (0x630 >> 2), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReceiveFrame CTL_CODE(0, (0x630 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_ReadInterfaceRecord CTL_CODE(0, (0x640 >> 2), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReadInterfaceRecord CTL_CODE(0, (0x640 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReadInterfaceRecordEx CTL_CODE(0, (0x650 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetLinkInterface CTL_CODE(0, (0x710 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetUserDpc CTL_CODE(0, (0x720 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetCardMode CTL_CODE(0, (0x730 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReadCardInfo CTL_CODE(0, (0x740 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncQuickStart CTL_CODE(0, (0x750 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetLinkInterface CTL_CODE(0, (0x760 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReadSignals CTL_CODE(0, (0x770 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetDMAMode CTL_CODE(0, (0x780 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetDMAMode CTL_CODE(0, (0x790 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReadCardInfoEx CTL_CODE(0, (0x7a0 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetHandShakeMode CTL_CODE(0, (0x7b0 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetHandShakeMode CTL_CODE(0, (0x7c0 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncResetStats CTL_CODE(0, (0x7d0 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncReadTE1Status CTL_CODE(0, (0x7e0 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetUserDpcEx CTL_CODE(0, (0x7f0 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetPortConfig CTL_CODE(0, (0x810 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetPortConfig CTL_CODE(0, (0x820 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetSerialConfig CTL_CODE(0, (0x830 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetSerialConfig CTL_CODE(0, (0x840 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetCTBusConfig CTL_CODE(0, (0x850 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetCTBusConfig CTL_CODE(0, (0x860 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetTE1Config CTL_CODE(0, (0x870 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetTE1Config CTL_CODE(0, (0x880 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetCTBusBackupConfig CTL_CODE(0, (0x890 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetCTBusBackupConfig CTL_CODE(0, (0x8a0 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetClockingStatus CTL_CODE(0, (0x8b0 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncSetMonitoring CTL_CODE(0, (0x8c0 >> 2), METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SDCI_FarSyncGetMonitoringStatus CTL_CODE(0, (0x8d0 >> 2), METHOD_NEITHER, FILE_ANY_ACCESS) #define IoctlCodeSetEvent IOCTL_SDCI_SetEvent #define IoctlCodeSetLinkChar IOCTL_SDCI_SetLinkCharacteristics #define IoctlCodeSetV24 IOCTL_SDCI_SetV24OutputStatus #define IoctlCodeTxFrame IOCTL_SDCI_TransmitFrame #define IoctlCodeAbortTransmit IOCTL_SDCI_AbortTransmit #define IoctlCodeAbortReceiver IOCTL_SDCI_AbortReceiver #define IoctlCodeOffBoardLoad IOCTL_SDCI_OffBoardLoad #define IoctlCodeGetV24 IOCTL_SDCI_GetV24Status #define IoctlCodeRxFrame IOCTL_SDCI_ReceiveFrame #define IoctlCodeFarSyncRxFrame IOCTL_SDCI_FarSyncReceiveFrame #define IoctlCodeReadInterfaceRecord IOCTL_SDCI_ReadInterfaceRecord #define IoctlCodeFarSyncReadInterfaceRecord IOCTL_SDCI_FarSyncReadInterfaceRecord #define IoctlCodeFarSyncReadInterfaceRecordEx IOCTL_SDCI_FarSyncReadInterfaceRecordEx #define IoctlCodeFarSyncSetLinkInterface IOCTL_SDCI_FarSyncSetLinkInterface #define IoctlCodeSetLinkInterface IoctlCodeFarSyncSetLinkInterface #define IoctlCodeFarSyncSetUserDpc IOCTL_SDCI_FarSyncSetUserDpc #define IoctlCodeFarSyncSetCardMode IOCTL_SDCI_FarSyncSetCardMode #define IoctlCodeFarSyncReadCardInfo IOCTL_SDCI_FarSyncReadCardInfo #define IoctlCodeFarSyncQuickStart IOCTL_SDCI_FarSyncQuickStart #define IoctlCodeFarSyncGetLinkInterface IOCTL_SDCI_FarSyncGetLinkInterface #define IoctlCodeFarSyncReadSignals IOCTL_SDCI_FarSyncReadSignals #define IoctlCodeFarSyncSetDMAMode IOCTL_SDCI_FarSyncSetDMAMode #define IoctlCodeFarSyncGetDMAMode IOCTL_SDCI_FarSyncGetDMAMode #define IoctlCodeFarSyncReadCardInfoEx IOCTL_SDCI_FarSyncReadCardInfoEx #define IoctlCodeFarSyncSetHandShakeMode IOCTL_SDCI_FarSyncSetHandShakeMode #define IoctlCodeFarSyncGetHandShakeMode IOCTL_SDCI_FarSyncGetHandShakeMode #define IoctlCodeFarSyncResetStats IOCTL_SDCI_FarSyncResetStats #define IoctlCodeFarSyncReadTE1Status IOCTL_SDCI_FarSyncReadTE1Status #define IoctlCodeFarSyncSetUserDpcEx IOCTL_SDCI_FarSyncSetUserDpcEx #define IoctlCodeFarSyncSetPortConfig IOCTL_SDCI_FarSyncSetPortConfig #define IoctlCodeFarSyncGetPortConfig IOCTL_SDCI_FarSyncGetPortConfig #define IoctlCodeFarSyncSetSerialConfig IOCTL_SDCI_FarSyncSetSerialConfig #define IoctlCodeFarSyncGetSerialConfig IOCTL_SDCI_FarSyncGetSerialConfig #define IoctlCodeFarSyncSetCTBusConfig IOCTL_SDCI_FarSyncSetCTBusConfig #define IoctlCodeFarSyncGetCTBusConfig IOCTL_SDCI_FarSyncGetCTBusConfig #define IoctlCodeFarSyncSetTE1Config IOCTL_SDCI_FarSyncSetTE1Config #define IoctlCodeFarSyncGetTE1Config IOCTL_SDCI_FarSyncGetTE1Config #define IoctlCodeFarSyncSetCTBusBackupConfig IOCTL_SDCI_FarSyncSetCTBusBackupConfig #define IoctlCodeFarSyncGetCTBusBackupConfig IOCTL_SDCI_FarSyncGetCTBusBackupConfig #define IoctlCodeFarSyncGetClockingStatus IOCTL_SDCI_FarSyncGetClockingStatus #define IoctlCodeFarSyncSetMonitoring IOCTL_SDCI_FarSyncSetMonitoring #define IoctlCodeFarSyncGetMonitoringStatus IOCTL_SDCI_FarSyncGetMonitoringStatus /*****************************************************************************/ /* Constants for the driver-specific IOCtl return codes. */ /*****************************************************************************/ #define CEDNODMA 0xff80 /* Warning (NO DMA!) from set link chrctrstcs */ /*****************************************************************************/ /* Equates for the link options byte 1. */ /*****************************************************************************/ #define CEL4WIRE 0x80 #define CELNRZI 0x40 #define CELPDPLX 0x20 #define CELSDPLX 0x10 #define CELCLOCK 0x08 #define CELDSRS 0x04 #define CELSTNBY 0x02 #define CELDMA 0x01 /*****************************************************************************/ /* Equates for the driver set link characteristics byte 1. */ /*****************************************************************************/ #define CED4WIRE 0x80 #define CEDNRZI 0x40 #define CEDHDLC 0x20 #define CEDFDPLX 0x10 #define CEDCLOCK 0x08 #define CEDDMA 0x04 #define CEDRSTAT 0x02 #define CEDCSTAT 0x01 /* Nicer names for NT-style code */ #define LinkOption_4Wire CED4WIRE #define LinkOption_InvertRxClock CEDDMA #define LinkOption_NRZI CEDNRZI #define LinkOption_HDLC CEDHDLC #define LinkOption_FullDuplex CEDFDPLX #define LinkOption_InternalClock CEDCLOCK #define LinkOption_DMA CEDDMA #define LinkOption_ResetStatistics CEDRSTAT #define LinkOption_Transparent CEDCSTAT /*****************************************************************************/ /* Equates for the output V24 interface flags. */ /*****************************************************************************/ #define CED24RTS 0x01 #define CED24DTR 0x02 // #define CED24DRS 0x04- not used. 0x04 is instead used by DCD // i.e. when configured as an output - see below #define CED24SLS 0x08 #define CED24TST 0x10 /* Nicer names for NT-style code */ #define IR_OV24RTS CED24RTS #define IR_OV24DTR CED24DTR //#define IR_OV24DSRS CED24DRS #define IR_OV24SlSt CED24SLS #define IR_OV24Test CED24TST /*****************************************************************************/ /* Equates for the input V24 interface flags. */ /*****************************************************************************/ #define CED24CTS 0x01 #define CED24DSR 0x02 #define CED24DCD 0x04 #define CED24RI 0x08 // CEDCR indicates the presense of a generic carrier i.e. independent of port type // e.g. DCD for V.25, CTS for X.21 #define CEDCR 0x10 // Note: DCD can only be used as an output signal on a FarSync T4E // and only then if a FarSyncSetSerialConfig has been previously // issued for this port, with FsSerialConfig.uDCDOutput set to // TRUE /* Nicer names for NT-style code */ #define IR_IV24CTS CED24CTS #define IR_IV24DSR CED24DSR #define IR_IV24DCD CED24DCD #define IR_IV24RI CED24RI #define IR_IV24Test 0x10 /*****************************************************************************/ /* Structure for the device driver interface record. */ /*****************************************************************************/ #define CEDSTCRC 0 /* Frames received with incorrect CRC */ #define CEDSTOFL 1 /* Frames received longer than the maximum */ #define CEDSTUFL 2 /* Frames received less than 4 octets long */ #define CEDSTSPR 3 /* Frames received ending on a non-octet bndry */ #define CEDSTABT 4 /* Aborted frames received */ #define CEDSTTXU 5 /* Transmitter interrupt underruns */ #define CEDSTRXO 6 /* Receiver interrupt overruns */ #define CEDSTDCD 7 /* DCD (RLSD) lost during frame reception */ #define CEDSTCTS 8 /* CTS lost while transmitting */ #define CEDSTDSR 9 /* DSR drops */ #define CEDSTHDW 10 /* Hardware failures - adapter errors */ #define CEDSTMAX 11 #define SA_CRC_Error CEDSTCRC #define SA_RxFrameTooBig CEDSTOFL #define SA_RxFrameTooShort CEDSTUFL #define SA_Spare CEDSTSPR #define SA_RxAbort CEDSTABT #define SA_TxUnderrun CEDSTTXU #define SA_RxOverrun CEDSTRXO #define SA_DCDDrop CEDSTDCD #define SA_CTSDrop CEDSTCTS #define SA_DSRDrop CEDSTDSR #define SA_HardwareError CEDSTHDW // FarSync-specific counters mapped to existing SDCI definitions #define SA_FramingError SA_Spare #define SA_RxError SA_HardwareError #define SA_BufferUnavailable SA_DCDDrop #define SA_Parity SA_CRC_Error #define SA_Max_Stat CEDSTMAX // **************************************************************************************************** // FarSync supports the following counters. All other counter indices are not used by FarSync. // SA_CRC_Error Sync & Async modes // SA_FramingError Sync & Async modes // SA_RxOverrun Sync & Async modes // SA_RxAbort (Async) & (M1P Sync) modes only // SA_RxError Sync mode only // SA_TxUnderrun Sync mode only // SA_RxFrameTooShort M1P Sync mode only // SA_RxFrameTooBig Async mode (fifo overflow) & M1P Sync // Note that // 1) Async mode is currently only supported on the T4U - it is an optional extra feature // 2) On a TxU an SA_RxError indicates a received abort OR a rx frame length error (too big OR too small) // 3) On an M1P an SA_RxError indicates a alternative type of RxO as reported via SA_RxOverrun // **************************************************************************************************** /*****************************************************************************/ /* InterfaceRecord definition */ /* */ /* For use with: */ /* */ /* IoctlCodeReadInterfaceRecord, */ /* IoctlCodeFarSyncReadInterfaceRecord */ /* */ /*****************************************************************************/ typedef struct _INTERFACE_RECORD { int RxFrameCount; /* incremented after each frame rx'd */ int TxMaxFrSizeNow; /* max available frame size av. now */ /* (changes after each Tx DevIoctl */ /* to DD or after Tx completed) */ int StatusCount; /* How many status events have been */ /* triggered. */ UCHAR V24In; /* Last 'getv24 i/p' value got */ UCHAR V24Out; /* Last 'setv24 o/p' value set */ /* The values for the indexes into the link statistics array of the */ /* various types of statistic. */ int StatusArray[SA_Max_Stat]; } IR, * PIR; /*****************************************************************************/ /* InterfaceRecordEx definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncReadInterfaceRecordEx */ /* */ /*****************************************************************************/ typedef struct _INTERFACE_RECORD_EX { IR InterfaceRecord; int StatusCount; /* How many status events have been */ ULONG OpenedCount; ULONG TxRequestCount; ULONG TxCompleteCount; ULONG RxPostedCount; ULONG RxCompleteCount; } IREX, * PIREX; /*****************************************************************************/ /* Set link characteristics parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeSetLinkChar */ /* */ /*****************************************************************************/ typedef struct _SLPARMS { int SLFrameSize; /* max frame size on link, should */ /* include 2-byte CRC - max is 8K */ LONG SLDataRate; /* not used by us - external clocks */ UCHAR SLOurAddress1; /* ) e.g C1/FF or 00/00 or 01/03 */ UCHAR SLOurAddress2; /* ) */ UCHAR SLLinkOptionsByte; /* see documentation & LinkOption_* */ UCHAR SLSpare1; } SLPARMS, *PSLPARMS; /*****************************************************************************/ /* Set link interface parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetLinkInterface, */ /* IoctlCodeFarSyncGetLinkInterface */ /* */ /*****************************************************************************/ #pragma pack(push, 4) /* Note: sizeof(FsLinkIfParms) is expected to be 0x10 */ typedef struct _FSLINKIFPARMS { HANDLE Context; // context for completion routine - not used USHORT MaxFrameSize; // maximum frame size - not used USHORT Interface; // line interface: ULONG BaudRate; // baud rate UCHAR Reserved; // reserved } FSLINKIFPARMS, * PFSLINKIFPARMS; #pragma pack(pop) // USER DPC DEFINITIONS - KERNEL-MODE ONLY #define USERDPC_INDICATION_RX 0 #define USERDPC_INDICATION_SIGNAL 1 #define USERDPC_INDICATION_ERROR 2 #define USERDPC_INDICATION_TX 3 /* indicates that the number of tx buffers in use by the card has changed - examine *pUserDpcTxBuffersInUse for the current value */ // FarSync T4E clock notifications #define USERDPC_INDICATION_CLOCK_RATE_CHANGED FS_INDICATION_CLOCK_RATE_CHANGED #define USERDPC_INDICATION_CLOCK_OUT_OF_TOLERANCE1 FS_INDICATION_CLOCK_OUT_OF_TOLERANCE1 #define USERDPC_INDICATION_CLOCK_IN_TOLERANCE1 FS_INDICATION_CLOCK_IN_TOLERANCE1 #define USERDPC_INDICATION_CLOCK_SWITCH_TO_BACKUP FS_INDICATION_CLOCK_SWITCH_TO_BACKUP #define USERDPC_INDICATION_CLOCK_OUT_OF_TOLERANCE2 FS_INDICATION_CLOCK_OUT_OF_TOLERANCE2 #define USERDPC_INDICATION_CLOCK_IN_TOLERANCE2 FS_INDICATION_CLOCK_IN_TOLERANCE2 #define USERDPC_INDICATION_CLOCK_SWITCH_TO_OSC FS_INDICATION_CLOCK_SWITCH_TO_OSC #define USERDPC_INDICATION_CLOCK_SWITCH_TO_CTA FS_INDICATION_CLOCK_SWITCH_TO_CTA #define USERDPC_INDICATION_CLOCK_SWITCH_TO_CTB FS_INDICATION_CLOCK_SWITCH_TO_CTB #define USERDPC_INDICATION_CLOCK_SWITCH_TO_PRIMARY FS_INDICATION_CLOCK_SWITCH_TO_PRIMARY // user DPC callback - kernel mode only typedef VOID (*USERDPC)(PVOID /* caller's handle*/, unsigned char /* indication */); /*****************************************************************************/ /* Set user dpc parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetUserDpc , */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FSUSERDPCINFO { USERDPC fUserDpc; // callback address PVOID pUserDpcParam; // param to supply in callback } FSUSERDPCINFO, * PFSUSERDPCINFO; /*****************************************************************************/ /* Extended set user dpc parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetUserDpcEx , */ /* */ /* If used, this structure must be allocated in NonPaged memory. */ /* */ /*****************************************************************************/ typedef struct _FSUSERDPCINFOEX { FSUSERDPCINFO fUserDpcInfo; long * pUserDpcTxBuffersInUse; // ref to a SDCI client-owned var that is used to maintain the number of tx buffers // currently in use by the card. } FSUSERDPCINFOEX, * PFSUSERDPCINFOEX; #pragma pack(pop) /*****************************************************************************/ /* Card mode parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetCardMode , */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FSCARDMODE { BOOLEAN bIdentifyMode; } FSCARDMODE, * PFSCARDMODE; #pragma pack(pop) /*****************************************************************************/ /* Card info parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncReadCardInfo , */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FSCARDINFO { #define FSCARDINFO_VERSION 1 #define SERIAL_NO_LENGTH 8 ULONG uVersion; // Version of this structure USHORT uDeviceId; USHORT uSubSystemId; ULONG uNumberOfPorts; char szSerialNo[SERIAL_NO_LENGTH+1]; ULONG uMajorRevision; ULONG uMinorRevision; ULONG uBuildState; ULONG uCPUSpeed; ULONG uMode; } FSCARDINFO, * PFSCARDINFO; #pragma pack(pop) /*****************************************************************************/ /* DMA mode parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetDMAMode, */ /* IoctlCodeFarSyncGetDMAMode , */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FSDMAMODE { #define FSDMAMODE_VERSION 1 #define FSDMAMODE_OFF 1 #define FSDMAMODE_ON 2 #define FSDMAMODE_INTERMEDIATE 3 // use for processing rxs via intermediate buffer ULONG uVersion; // Version of this structure USHORT uTxDMAMode; USHORT uRxDMAMode; } FSDMAMODE, * PFSDMAMODE; #pragma pack(pop) /*****************************************************************************/ /* Interrupt handshake mode parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetHandShakeMode, */ /* IoctlCodeFarSyncGetHandShakeMode */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FSHANDSHAKEMODE { #define FSHANDSHAKEMODE_VERSION 1 #define FSHANDSHAKEMODE_2 2 #define FSHANDSHAKEMODE_3 3 ULONG uVersion; // Version of this structure USHORT uMode; } FSHANDSHAKEMODE, * PFSHANDSHAKEMODE; #pragma pack(pop) /*****************************************************************************/ /* Extended card info parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncReadCardInfoEx , */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FSCARDINFOEX { #define FSCARDINFOEX_VERSION 2 ULONG uVersion; // Version of this structure char szDeviceName[32+8]; // Including aligned PADDING to allow for null-terminator PVOID w_memory; // Virtual addresses of Adapter Resources PVOID w_controlSpace; PVOID w_localCfg; PVOID w_ioSpace; ULONG z_memory; // Lengths of Adapter Resources ULONG z_controlSpace; ULONG z_localCfg; ULONG z_ioSpace; ULONG uHiVersion; ULONG uLoVersion; ULONG uReservedA[30]; // these are reserved for FarSite's own use PVOID p_memory; // physical address of window - T-Series cards PVOID p_io; // main IO address of card ULONG uExtendedClockingSupported; // are (T4E) clock synths available on the card? ULONG uAsyncSupported; // does the card support async ULONG uReservedB[28]; // these are currently unused } FSCARDINFOEX, * PFSCARDINFOEX; #pragma pack(pop) /*****************************************************************************/ /* Port config parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetPortConfig, */ /* IoctlCodeFarSyncGetPortConfig */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FS_PORT_CONFIG { #define FSPORTCONFIG_VERSION 2 ULONG uVersion; // Version of this structure ULONG uPortTxNumBuffers; // 2,4,8,16,32,64,128 ULONG uPortRxNumBuffers; // 2,4,8,16,32,64,128 ULONG uPortTxBufferLength; // Min=2 Max=64*1024 Def= 8*1024 ULONG uPortRxBufferLength; // Min=2 Max=64*1024 Def= 8*1024 ULONG uPortTxDMA; // See fscfg.h for values i.e. FSDMAMODE_OFF=1, FSDMAMODE_ON=2 ULONG uPortRxDMA; // See fscfg.h for values i.e. FSDMAMODE_OFF=1, FSDMAMODE_ON=2 ULONG uPortStartTxRx; // See smcuser.h for values i.e. START_TX=1 | START_RX=2 ULONG uPortClockSource; // See smcuser.h for values i.e. FS_CLOCK_REFERENCE_xxx // Currently only supported on T4E ULONG uPortTxMSB; // FALSE ==> LSB (Default) ULONG uPortRxMSB; // FALSE ==> LSB (Default) ULONG uPortMaxTxsOutstanding;// read-only - indicates how many txs can be outstanding at a time ULONG uReserved[52]; } FS_PORT_CONFIG, *PFS_PORT_CONFIG; #pragma pack(pop) /*****************************************************************************/ /* Serial config parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetSerialConfig, */ /* IoctlCodeFarSyncGetSerialConfig */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FS_SERIAL_CONFIG { #define FSSERIALCONFIG_VERSION 2 ULONG uVersion; // Version of this structure ULONG uPortInterface; // See fscfg.h for values e.g. FS_LINE_INTERFACE_X21 ULONG uPortRate; ULONG uPortClocking; // See fscfg.h for values e.g. FS_LINE_CLOCKING_INTERNAL ULONG uPortTransparentMode; // HDLC=0 Transparent=1 ULONG uPortInvertRxClock; // TRUE(1) or FALSE(0) ULONG uPortEncoding; // See fscfg for values i.e. FS_PORT_ENCODING_xxx // Currently only supported on M1P // The following fields have been added in Version 2 of this structure and not be // processed if uVersion = 1 ULONG uExtendedClocking; // subsequent fields only used if this is TRUE ULONG uInternalTxClock; ULONG uInternalRxClock; ULONG uTerminalTxClock; ULONG uTerminalRxClock; ULONG uDCDOutput; // TRUE => DCD is an output ULONG uEstimatedLineSpeed; // returned by FarSyncGetSerialConfig for T4E only ULONG uReserved[54]; } FS_SERIAL_CONFIG, *PFS_SERIAL_CONFIG; #pragma pack(pop) /*****************************************************************************/ /* CTBus config parameter block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncSetCTBusConfig */ /* IoctlCodeFarSyncGetCTBusConfig */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FS_CT_BUS_CONFIG { #define FSCTBUSCONFIG_VERSION 2 ULONG uVersion; // Version of this structure ULONG uCTBusMode; // See smcuser.h for values i.e. FS_CT_BUS_MODE_xxx ULONG uCTBusFeed; // See smcuser.h for values i.e. FS_CT_BUS_FEED_xxx ULONG uFallback; // TRUE=>auto switch if clock ref fails ULONG uReserved[60]; } FS_CT_BUS_CONFIG, *PFS_CT_BUS_CONFIG; #pragma pack(pop) /*****************************************************************************/ /* Clocking Status block definition */ /* */ /* For use with: */ /* */ /* IoctlCodeFarSyncGetClockingStatus */ /* */ /*****************************************************************************/ #pragma pack(push, 4) typedef struct _FS_CLOCKING_STATUS { #define FSCLOCKINGSTATUS_VERSION 2 ULONG uVersion; // Version of this structure ULONG uPrimaryClockStatus; // 0=out-of-tolerance, 1=good ULONG uBackupClockStatus; // 0=out-of-tolerance, 1=good ULONG uCurrentConfigReference; // See smcuser.h for values i.e. FS_CT_CONFIG_xxx ULONG uCTAStatus; // 0=out-of-tolerance, 1=good ULONG uCTBStatus; // 0=out-of-tolerance, 1=good USHORT uCurrentStatusSummary; // bit map status word describing clocking state/config // see smcuser.h for bit definitions USHORT uReserved1; ULONG uCTAInUse; // 0=CTB, 1=CTA - shows which CT Bus the card // has selected for the slave clock source ULONG uReserved2[15]; } FS_CLOCKING_STATUS, *PFS_CLOCKING_STATUS; #pragma pack(pop) //###################################################################################################### // // FarSync Error Return Codes // // These return codes will be passed back from the FarSync SDCI driver in the IoStatus.Status field of // the IRP and (if the SDCI client is a user-mode app) will be passed through transparently to the app // via GetLastError(). // #define FS_ERROR_INVALID_INPUT_BUFFER_LENGTH 0xE0000001 #define FS_ERROR_INVALID_OUTPUT_BUFFER_LENGTH 0xE0000002 #define FS_ERROR_PRIMARY_IOCTL 0xE0000003 #define FS_ERROR_CARD_NOT_STARTED 0xE0000004 #define FS_ERROR_INVALID_CT_BUS_MODE 0xE0000005 #define FS_ERROR_INVALID_CT_BUS_FEED 0xE0000006 #define FS_ERROR_INVALID_IOCTL_FOR_PORTTYPE 0xE0000007 #define FS_ERROR_INVALID_LENGTH 0xE0000008 //###################################################################################################### #endif /* __SDCI_H_ */ Opendigitalradio-ODR-DabMux-29c710c/lib/farsync/windows/smcuser.h000066400000000000000000000360411476627344300247050ustar00rootroot00000000000000/******************************************************************************* * * Program : FARSYNC * * File : smcuser.h * * Description : This common header file defines constants used throughout FarSync e.g. in * * 1) onboard software * 2) PC driver (farsynct) * 3) user-mode config apps * 4) higher-level drivers (e.g. fswan) * * Modifications * * Version 2.0.0 18Jan01 WEB Created * Version 2.2.0 18Jul01 WEB Certification candidate * 22Oct01 MJD Added Transparent Mode Support - moved number and * size of buffer definitions her from smc.h. * 19Nov02 MJD Added X.21 Dual clock interface mode X21D - only * for T1U/T2U/T4U. * 28Nov02 MJD Added NOCABLE interface mode - only for T1U/T2U/ * T4U/T4P, used only by CDE when stopping a port. * Version 3.0.0 08Oct03 MJD Added TE1 parameter constants * Version 3.0.1 10Nov03 MJD Added tx/rxBufferMode constants for TE1. * Version 3.0.2 03Nov04 MJD Added DSL #define's * Version 3.0.3 24Feb05 WEB Add FS_CLOCK_SOURCE_xxx and FS_CT_BUS_xxx values * Version 4.1.0 15Jun05 WEB Update TE1 typedefs * Version 4.1.1 24Jun05 WEB Add T4E MkII defs * Version 4.1.2 30Jun05 MJD Add FS_CT_BUS_FEED_FROM_OSCILLATOR define. * Only define FS_CLOCK_SOURCE_PORT_A/B/C/D when * T4EMKI defined (not available on T4EMKII). * Version 4.1.3 07Jul05 MJD Add FS_CONFIG_IN_USE_XXX and FS_INDICATION_CLOCK_XXX * values for T4E MkII. * Version 4.1.4 08Jul05 MJD Added define for ANNEX_TYPE_AB (SHDSL) * Version 4.1.5 14Jul05 WEB Rationalise clock reference defines * Version 4.1.6 15Jul05 MJD Added FS_CCS_MASK_XXX defines * Version 4.1.7 02Aug05 MJD Added FS_INDICATION_CLOCK_SWITCH_TO_PRIMARY define *******************************************************************************/ #ifndef SMCUSER_H #define SMCUSER_H #ifndef UINT8 #define UINT8 unsigned char #define INT8 char #define INT32 long #define UINT16 unsigned short #define UINT32 unsigned long #endif #define MAX_PORTS 4 /* maximum ports on T4P - fixed don't change*/ /* Interface Types */ #define AUTO 0 #define V24 1 #define X21 2 #define V35 3 #define X21D 4 #define NOCABLE 5 #define RS530_449 6 /* Clock Sources */ #define INTCLK 1 #define EXTCLK 0 #define FS_CLOCK_REFERENCE_OSCILLATOR 0 #define FS_CLOCK_REFERENCE_PORT_A 1 #define FS_CLOCK_REFERENCE_PORT_B 2 #define FS_CLOCK_REFERENCE_PORT_C 3 #define FS_CLOCK_REFERENCE_PORT_D 4 #define FS_CLOCK_REFERENCE_CTBUS 5 /* note: this value does not differentiate between CTA or CTB */ #define FS_CLOCK_REFERENCE_CT_A 6 #define FS_CLOCK_REFERENCE_CT_B 7 #define FS_CT_BUS_MODE_SLAVE 0 /* FarSync card will act as SLAVE to the CT_BUS */ #define FS_CT_BUS_MODE_MASTER_A 1 /* FarSync card will act as MASTER to the CT_BUS_A */ #define FS_CT_BUS_MODE_MASTER_B 2 /* FarSync card will act as MASTER to the CT_BUS_B */ #define FS_CT_BUS_MODE_DEFAULT FS_CT_BUS_MODE_SLAVE #define FS_CONFIG_IN_USE_PRIMARY 0 /* Primary clock reference is in use */ #define FS_CONFIG_IN_USE_BACKUP 1 /* Backup clock reference is in use */ #define FS_CONFIG_IN_USE_OSCILLATOR 2 /* Local oscillator clock reference is in use */ #define FS_CONFIG_IN_USE_DEFAULT FS_CONFIG_IN_USE_PRIMARY // Indications to be signalled via the cardNotifications FIFO #define FS_INDICATION_CLOCK_RATE_CHANGED 0x0080 /* 2 LSB's denote port A, B, C or D */ #define FS_INDICATION_CLOCK_OUT_OF_TOLERANCE1 0x0084 #define FS_INDICATION_CLOCK_IN_TOLERANCE1 0x0088 #define FS_INDICATION_CLOCK_SWITCH_TO_BACKUP 0x008c #define FS_INDICATION_CLOCK_OUT_OF_TOLERANCE2 0x0090 #define FS_INDICATION_CLOCK_IN_TOLERANCE2 0x0094 #define FS_INDICATION_CLOCK_SWITCH_TO_OSC 0x0098 #define FS_INDICATION_CLOCK_SWITCH_TO_CTA 0x009c #define FS_INDICATION_CLOCK_SWITCH_TO_CTB 0x00a0 #define FS_INDICATION_CLOCK_SWITCH_TO_PRIMARY 0x00a4 // Bit masks for isolating fields in uCurrentStatusSummary #define FS_CSS_MASK_SLAVE_MASTER 0x0100 /* 0 = Slave, 1 = Master */ #define FS_CSS_MASK_CTA_CTB 0x0080 /* 0 = A, 1 = B */ #define FS_CSS_MASK_MASTER_SOURCE 0x0070 /* 000=A, 001=B, 010=C, 011=D, 100=LO */ #define FS_CSS_MASK_PORT_A_SOURCE 0x0008 /* 0 = CT, 1 = LO */ #define FS_CSS_MASK_PORT_B_SOURCE 0x0004 /* 0 = CT, 1 = LO */ #define FS_CSS_MASK_PORT_C_SOURCE 0x0002 /* 0 = CT, 1 = LO */ #define FS_CSS_MASK_PORT_D_SOURCE 0x0001 /* 0 = CT, 1 = LO */ /* Tx/Rx Start Parameters */ #define START_TX 1 #define START_RX 2 #define START_TX_AND_RX (START_TX | START_RX) #define START_DEFAULT START_TX_AND_RX // constants for t1/e1 service unit config // ======================================= // // dataRate // rate is in bps: 8k - 1536/2048k (t1/e1), though n*64k is more usual // e1: 0 - 1984k => framed (fractional), 2048k => unframed // for e1 framed, bandwidth is allocated from the ninth bit of the frame // sequentially // for e1 unframed all 256 bits in the frame are used for data // t1: 0 - 1536k => framed (fractional), no unframed mode in t1. // #define DATARATE_DEFAULT 64000L // Clocking // #define CLOCKING_SLAVE 0 #define CLOCKING_DEFAULT CLOCKING_SLAVE #define CLOCKING_MASTER 1 // Framing // #define FRAMING_E1 0 #define FRAMING_DEFAULT FRAMING_E1 #define FRAMING_J1 1 #define FRAMING_T1 2 // Structure // #define STRUCTURE_UNFRAMED 0 #define STRUCTURE_E1_DOUBLE 1 #define STRUCTURE_E1_CRC4 2 #define STRUCTURE_E1_DEFAULT STRUCTURE_E1_CRC4 #define STRUCTURE_DEFAULT STRUCTURE_E1_CRC4 #define STRUCTURE_E1_CRC4M 3 #define STRUCTURE_T1_4 4 #define STRUCTURE_T1_12 5 #define STRUCTURE_T1_24 6 #define STRUCTURE_T1_DEFAULT STRUCTURE_T1_24 #define STRUCTURE_T1_72 7 // Interface // RJ48C is available for e1 and t1, BNC is only available for e1 // #define INTERFACE_RJ48C 0 #define INTERFACE_DEFAULT INTERFACE_RJ48C #define INTERFACE_BNC 1 // Coding // hdb3 is the normal coding scheme for e1 // b8zs is the normal coding scheme for t1, though ami is sometimes used // #define CODING_HDB3 0 #define CODING_E1_DEFAULT CODING_HDB3 #define CODING_DEFAULT CODING_HDB3 #define CODING_NRZ 1 #define CODING_CMI 2 #define CODING_CMI_HDB3 3 #define CODING_CMI_B8ZS 4 #define CODING_AMI 5 #define CODING_AMI_ZCS 6 #define CODING_B8ZS 7 #define CODING_T1_DEFAULT CODING_B8ZS // Line Build Out - for long haul t1 > 655ft (200m). Use with EQUALIZER_LONG. // This parameter is ignored in e1 mode and t1/j1 short haul mode. // #define LBO_0dB 0 #define LBO_DEFAULT LBO_0dB #define LBO_7dB5 1 #define LBO_15dB 2 #define LBO_22dB5 3 // Range - for short haul t1 < 655ft (200m). Use with EQUALIZER_SHORT. // This parameter is ignored in e1 mode and t1/j1 long haul mode. // #define RANGE_0_133_FT 0 #define RANGE_DEFAULT RANGE_0_133_FT #define RANGE_0_40_M RANGE_0_133_FT #define RANGE_133_266_FT 1 #define RANGE_40_81_M RANGE_133_266_FT #define RANGE_266_399_FT 2 #define RANGE_81_122_M RANGE_266_399_FT #define RANGE_399_533_FT 3 #define RANGE_122_162_M RANGE_399_533_FT #define RANGE_533_655_FT 4 #define RANGE_162_200_M RANGE_533_655_FT // Receive Equalizer // short haul -10dB // long haul -43dB (E1), -36dB (T1) // only -36dB can be met with 1.2 silicon, requires equalizer parameter RAM // changes for 2.1 silicon // #define EQUALIZER_SHORT 0 #define EQUALIZER_DEFAULT EQUALIZER_SHORT #define EQUALIZER_LONG 1 // Loop Mode // Local Loop transmits normally and loops PCM data on the line side of the // framers. // Payload Loop receives normally and loops TS0-TS31 or TS1-31 (with TS0 // regenerated by FALC-56) to line on the PCM side of the framers. // Remote Loop receives normally and loops line data after clock recovery // the optional Jitter Attenuation is currently not enabled. #define LOOP_NONE 0 #define LOOP_DEFAULT LOOP_NONE #define LOOP_LOCAL 1 #define LOOP_PAYLOAD_EXC_TS0 2 #define LOOP_PAYLOAD_INC_TS0 3 #define LOOP_REMOTE 4 // Buffer Mode // buffer_none bypasses the elastic buffer // #define BUFFER_2_FRAME 0 #define BUFFER_DEFAULT BUFFER_2_FRAME #define BUFFER_1_FRAME 1 #define BUFFER_96_BIT 2 #define BUFFER_NONE 3 // // Starting Timeslot (for fractional) // E1 range 1 to 31, T1/J1 range 0 to 23 // Min/max values enforced by card. Actual speed may also be restricted if // starting timeslot is too late in the frame. // Parameter ignored in unchannelized mode // #define STARTING_DEFAULT 0 // // LOS detection threshold // level 0 allows LOS to be detected with larger signal levels // level 7 allows LOS to be detected with smaller signal levels // the recommended default setting is level 2 - which is selected // by the card if LOS_DEFAULT is configured // #define LOS_DEFAULT 0 #define LOS_LEVEL_0 1 #define LOS_LEVEL_1 2 #define LOS_LEVEL_2 3 #define LOS_LEVEL_3 4 #define LOS_LEVEL_4 5 #define LOS_LEVEL_5 6 #define LOS_LEVEL_6 7 #define LOS_LEVEL_7 8 #define LOS_SHORT 0x20 #define LOS_LONG 0x70 // // Idle code for unused timeslots // #define IDLE_HDLC_FLAG 0x7e #define IDLE_CODE_DEFAULT IDLE_HDLC_FLAG #define TRANSPARENT_MODE_DEFAULT FALSE #define ENABLE_IDLE_CODE_DEFAULT FALSE // constants for dsl service unit config // ======================================= // // dataRate (G.SHDSL) // rate is in bps: 192kbps (3B + 0Z) - 2312kbps (36B + 1Z) // in 8kbps steps, where B is 64kbps and Z is 8kbps // // // Terminal Type // #define TERMINAL_TYPE_REMOTE 0 #define TERMINAL_TYPE_CENTRAL 1 // // Test Mode // // For User Diagnostics: // // Analog Transparent Loop - loops from transmit line driver output to // receive AGC input, bypassing the hybrid. // Analog Non-Transparent Loop - loops transmit DAC back to receive AGC, // bypassing transmit line driver and the hybrid. // // Others test modes are for Compliance Testing only // #define TEST_MODE_NONE 0 #define TEST_MODE_DEFAULT TEST_MODE_NONE #define TEST_MODE_ALTERNATING_SINGLE_PULSE 1 #define TEST_MODE_ANALOG_TRANSPARENT_LOOP 4 #define TEST_MODE_ANALOG_NON_TRANSPARENT_LOOP 8 #define TEST_MODE_TRANSMIT_SC_SR 9 #define TEST_MODE_TRANSMIT_TC_PAM_SCRONE 10 #define TEST_MODE_LINE_DRIVER_NO_SIGNAL 11 #define TEST_MODE_AGC_TO_LINE_DRIVER_LOOP 12 #define TEST_MODE_LOOP_TDM_TO_LINE 16 #define TEST_MODE_LOOP_PAYLOAD_TO_LINE 17 // // Annex Type A (US) or B (EU) // #define ANNEX_TYPE_B 0 #define ANNEX_TYPE_DEFAULT ANNEX_TYPE_B #define ANNEX_TYPE_A 1 #define ANNEX_TYPE_AB 2 /* Small Buffers are used only for diagnostics */ #define NUM_SMALL_TX_BUFFER 2 #define NUM_SMALL_RX_BUFFER 8 #define LEN_SMALL_TX_BUFFER 256 /* max size is 8192 */ #define LEN_SMALL_RX_BUFFER 256 /* Large Buffers are used for SDMA data */ // MAX_TX/RX_BUFFER determines, at compile time, the maximum number of // descriptors (hence buffers) per port for transmitter and receiver. Host can // use any 2**N number of the descriptors between 1 and MAX_TX/RX_BUFFER, so // some descriptors may be unused. Number of Buffers * Size of Buffers <= // TX/RX_BUFFER_SPACE. Fewer buffers mean bigger buffers, more buffers mean // smaller buffers. #define MAX_TX_BUFFER 128 #define MAX_RX_BUFFER 128 // MAX_TX/RX_BUFFER_SPACE determines, at compile time, how much buffer space is // available per port for transmitter and receiver. Buffer space is subdivided // into 2**N buffers by host driver at runtime. // Host can allocate 1 - MAX_TX/RX_BUFFER buffers. // 0x10000 => 64KB for transmit, 64KB for receive; 4 ports => 512KB. // MAX_TX/RX_BUFFER must be a 2**N multiple (for easy logic anlyser trigger). #define TX_BUFFER_SPACE 0x10000 #define RX_BUFFER_SPACE 0x10000 /* Rx Descriptor bits */ #define FS_RX_DESC_ERR 0x4000 #define FS_RX_DESC_FRAM 0x2000 #define FS_RX_DESC_OFLO 0x1000 #define FS_RX_DESC_CRC 0x0800 #define FS_RX_DESC_HBUF 0x0400 #define FS_RX_DESC_ENP 0x0100 #define FS_RX_DESC_FRAM_ENP 0x2100 #define FS_RX_DESC_CRC_ENP 0x0900 #define NO_OF_DMA_CHANNELS 2 // The following structures require 2-byte packing. // This file is used in a number of different environments. // Windows SDCI applications should therefore include this file implicitly by explicitly // including fscfg.h which enables the packing directive to be used. #ifdef SMCUSER_PACKING #if (SMCUSER_PACKING==1) #pragma pack(push, 2) #endif typedef struct SU_CONFIG { UINT32 dataRate; // data rate in bps UINT8 clocking; // master or slave UINT8 framing; // E1, T1 or J1 UINT8 structure; // E1: unframed, double frame, CRC4; T1: F4, F12, F24 or F72 UINT8 iface; // RJ48C or BNC UINT8 coding; // HDB3 or B8ZS + some other less used codes UINT8 lineBuildOut; // 0, -7.5, -15, -22.5dB for t1 long haul only UINT8 equalizer; // short or long haul settings UINT8 transparentMode; // FALSE (hdlc) or TRUE transparent data UINT8 loopMode; // various local, payload and remote loops for test UINT8 range; // 0-133, 133-266, 266-399, 399-533, 533-655ft for t1 short haul only UINT8 txBufferMode; // transmit elastic buffer depth: 0 (bypass), 96 bits, 1 frame or 2 frame UINT8 rxBufferMode; // receive elastic buffer depth: 0 (bypass), 96 bits, 1 frame or 2 frame UINT8 startingTimeSlot;// startingTimeSlot: E1 1-31, T1/J1 0-23 UINT8 losThreshold; // LOS Threshold: E1/T1/J1 0-7 UINT8 enableIdleCode; // Enable idle code for unused timeslots: TRUE, FALSE UINT8 idleCode; // Idle code for unused timeslots: 0x00 - 0xff UINT8 spare[44]; // adjust to keep structure size 64 bytes } FS_TE1_CONFIG, *PFS_TE1_CONFIG; typedef struct SU_STATUS { UINT32 receiveBufferDelay; // delay through receive bufffer time slots (0-63) UINT32 framingErrorCounter; // count of framing errors UINT32 codeViolationCounter; // count of code violations UINT32 crcErrorCounter1; // count of CRC errors INT32 lineAttenuation; // receive line attenuation in dB, -ve => unknown BOOLEAN portStarted; // BOOLEAN lossOfSignal; // LOS alarm BOOLEAN receiveRemoteAlarm; // RRA alarm BOOLEAN alarmIndicationSignal; // AIS alarm UINT8 spare[40]; // adjust to keep structure size 64 bytes } FS_TE1_STATUS, *PFS_TE1_STATUS; #if (SMCUSER_PACKING==1) #pragma pack(pop) #endif #else #pragma message("!!! *** SMCUSER_PACKING not defined. Note: Windows SDCI apps should *** !!!") #pragma message("!!! *** not include smcuser.h directly, use fscfg.h instead *** !!!") #endif #endif Opendigitalradio-ODR-DabMux-29c710c/lib/fec/000077500000000000000000000000001476627344300204455ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/lib/fec/LICENSE000066400000000000000000000635061476627344300214640ustar00rootroot00000000000000GNU 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-DabMux-29c710c/lib/fec/README.md000066400000000000000000000006331476627344300217260ustar00rootroot00000000000000FEC 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-DabMux-29c710c/lib/fec/char.h000066400000000000000000000010321476627344300215270ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/fec/decode_rs.h000066400000000000000000000203751476627344300225540ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/fec/encode_rs.h000066400000000000000000000044511476627344300225630ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/fec/fec.h000066400000000000000000000015451476627344300213600ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/fec/init_rs.h000066400000000000000000000052131476627344300222660ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/fec/init_rs_char.c000066400000000000000000000015201476627344300232530ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/fec/rs-common.h000066400000000000000000000016471476627344300225400ustar00rootroot00000000000000/* 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-DabMux-29c710c/lib/zmq.hpp000066400000000000000000001617151476627344300212430ustar00rootroot00000000000000/* 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-DabMux-29c710c/m4/000077500000000000000000000000001476627344300174625ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/m4/ax_boost_asio.m4000066400000000000000000000075071476627344300225660ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_boost_asio.html # =========================================================================== # # SYNOPSIS # # AX_BOOST_ASIO # # DESCRIPTION # # Test for Asio 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_ASIO_LIB) # # And sets: # # HAVE_BOOST_ASIO # # LICENSE # # Copyright (c) 2008 Thomas Porschberg # Copyright (c) 2008 Pete Greenwell # # 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 AC_DEFUN([AX_BOOST_ASIO], [ AC_ARG_WITH([boost-asio], AS_HELP_STRING([--with-boost-asio@<:@=special-lib@:>@], [use the ASIO library from boost - it is possible to specify a certain library for the linker e.g. --with-boost-asio=boost_system-gcc41-mt-1_34 ]), [ if test "$withval" = "no"; then want_boost="no" elif test "$withval" = "yes"; then want_boost="yes" ax_boost_user_asio_lib="" else want_boost="yes" ax_boost_user_asio_lib="$withval" fi ], [want_boost="yes"] ) if test "x$want_boost" = "xyes"; then AC_REQUIRE([AC_PROG_CC]) CPPFLAGS_SAVED="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" export CPPFLAGS LDFLAGS_SAVED="$LDFLAGS" LDFLAGS="$LDFLAGS $BOOST_LDFLAGS" export LDFLAGS AC_CACHE_CHECK(whether the Boost::ASIO library is available, ax_cv_boost_asio, [AC_LANG_PUSH([C++]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include ]], [[ boost::asio::io_service io; boost::system::error_code timer_result; boost::asio::deadline_timer t(io); t.cancel(); io.run_one(); return 0; ]])], ax_cv_boost_asio=yes, ax_cv_boost_asio=no) AC_LANG_POP([C++]) ]) if test "x$ax_cv_boost_asio" = "xyes"; then AC_DEFINE(HAVE_BOOST_ASIO,,[define if the Boost::ASIO library is available]) BN=boost_system BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` if test "x$ax_boost_user_asio_lib" = "x"; then for ax_lib in `ls $BOOSTLIBDIR/libboost_system*.so* $BOOSTLIBDIR/libboost_system*.dylib* $BOOSTLIBDIR/libboost_system*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_system.*\)\.so.*$;\1;' -e 's;^lib\(boost_system.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_system.*\)\.a.*$;\1;' ` ; do AC_CHECK_LIB($ax_lib, main, [BOOST_ASIO_LIB="-l$ax_lib" AC_SUBST(BOOST_ASIO_LIB) link_thread="yes" break], [link_thread="no"]) done else for ax_lib in $ax_boost_user_asio_lib $BN-$ax_boost_user_asio_lib; do AC_CHECK_LIB($ax_lib, main, [BOOST_ASIO_LIB="-l$ax_lib" AC_SUBST(BOOST_ASIO_LIB) link_asio="yes" break], [link_asio="no"]) done fi if test "x$ax_lib" = "x"; then AC_MSG_ERROR(Could not find a version of the Boost::Asio library!) fi if test "x$link_asio" = "xno"; then AC_MSG_ERROR(Could not link against $ax_lib !) fi fi CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" fi ]) Opendigitalradio-ODR-DabMux-29c710c/m4/ax_boost_base.m4000066400000000000000000000330061476627344300225360ustar00rootroot00000000000000# =========================================================================== # 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-DabMux-29c710c/m4/ax_boost_system.m4000066400000000000000000000101441476627344300231460ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_boost_system.html # =========================================================================== # # SYNOPSIS # # AX_BOOST_SYSTEM # # DESCRIPTION # # Test for System 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_SYSTEM_LIB) # # And sets: # # HAVE_BOOST_SYSTEM # # LICENSE # # Copyright (c) 2008 Thomas Porschberg # Copyright (c) 2008 Michael Tindal # Copyright (c) 2008 Daniel Casimiro # # 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 20 AC_DEFUN([AX_BOOST_SYSTEM], [ AC_ARG_WITH([boost-system], AS_HELP_STRING([--with-boost-system@<:@=special-lib@:>@], [use the System library from boost - it is possible to specify a certain library for the linker e.g. --with-boost-system=boost_system-gcc-mt ]), [ if test "$withval" = "no"; then want_boost="no" elif test "$withval" = "yes"; then want_boost="yes" ax_boost_user_system_lib="" else want_boost="yes" ax_boost_user_system_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::System library is available, ax_cv_boost_system, [AC_LANG_PUSH([C++]) CXXFLAGS_SAVE=$CXXFLAGS CXXFLAGS= AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include ]], [[boost::system::error_category *a = 0;]])], ax_cv_boost_system=yes, ax_cv_boost_system=no) CXXFLAGS=$CXXFLAGS_SAVE AC_LANG_POP([C++]) ]) if test "x$ax_cv_boost_system" = "xyes"; then AC_SUBST(BOOST_CPPFLAGS) AC_DEFINE(HAVE_BOOST_SYSTEM,,[define if the Boost::System library is available]) BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` LDFLAGS_SAVE=$LDFLAGS if test "x$ax_boost_user_system_lib" = "x"; then for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], [link_system="no"]) done if test "x$link_system" != "xyes"; then for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], [link_system="no"]) done fi else for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do AC_CHECK_LIB($ax_lib, exit, [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], [link_system="no"]) done fi if test "x$ax_lib" = "x"; then AC_MSG_ERROR(Could not find a version of the Boost::System library!) fi if test "x$link_system" = "xno"; then AC_MSG_ERROR(Could not link against $ax_lib !) fi fi CPPFLAGS="$CPPFLAGS_SAVED" LDFLAGS="$LDFLAGS_SAVED" fi ]) Opendigitalradio-ODR-DabMux-29c710c/m4/ax_check_compile_flag.m4000066400000000000000000000040701476627344300241730ustar00rootroot00000000000000# =========================================================================== # 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-DabMux-29c710c/m4/ax_cxx_compile_stdcxx.m4000066400000000000000000000456471476627344300243430ustar00rootroot00000000000000# =========================================================================== # 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' (for the C++11 standard) # or '14' (for the C++14 standard). # # 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 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 # # 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 11 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"], [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], [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" for alternative in ${ax_cxx_compile_alternatives}; do for switch in -std=c++${alternative} +std=c++${alternative} "-h std=c++${alternative}"; do 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 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 ) 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 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" #elif __cplusplus < 201103L #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 #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 #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 ]]) Opendigitalradio-ODR-DabMux-29c710c/m4/ax_pthread.m4000066400000000000000000000540461476627344300220540ustar00rootroot00000000000000# =========================================================================== # 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 and PTHREAD_CXX to any special C compiler that is # needed for multi-threaded programs (defaults to the value of CC # respectively CXX otherwise). (This is necessary on e.g. AIX to use the # special cc_r/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 # $PTHREAD_CXX $CXXFLAGS $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" # CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # CXX="$PTHREAD_CXX" # # 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 30 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_TARGET]) 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"]) AS_IF([test "x$PTHREAD_CXX" != "x"], [CXX="$PTHREAD_CXX"]) 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 $target_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 $target_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 $target_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 $target_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"]) AS_IF([test "x${CXX}" != "x"], [AS_IF([AS_EXECUTABLE_P([${CXX}_r])],[PTHREAD_CXX="${CXX}_r"])]) ], [ AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC]) AS_IF([test "x${CXX}" != "x"], [AC_CHECK_PROGS([PTHREAD_CXX],[${CXX}_r],[$CXX])]) ] ) ]) ;; esac fi fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" test -n "$PTHREAD_CXX" || PTHREAD_CXX="$CXX" AC_SUBST([PTHREAD_LIBS]) AC_SUBST([PTHREAD_CFLAGS]) AC_SUBST([PTHREAD_CC]) AC_SUBST([PTHREAD_CXX]) # 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-DabMux-29c710c/m4/ax_zmq.m4000066400000000000000000000050141476627344300212230ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_zmq.html # =========================================================================== # # SYNOPSIS # # AX_ZMQ([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # DESCRIPTION # # Test for the ZMQ libraries of a particular version (or newer). The # default version tested for is 4.0.0. # # The macro tests for ZMQ libraries in the library/include path, and, when # provided, also in the path given by --with-zmq. # # This macro calls: # # AC_SUBST(ZMQ_CPPFLAGS) / AC_SUBST(ZMQ_LDFLAGS) / AC_SUBST(ZMQ_LIBS) # # And sets: # # HAVE_ZMQ # # LICENSE # # Copyright (c) 2016 Jeroen Meijer # # 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 3 AC_DEFUN([AX_ZMQ], [ AC_ARG_WITH([zmq], [AS_HELP_STRING([--with-zmq=],[ZMQ prefix directory])], [ ZMQ_LDFLAGS="-L${with_zmq}/lib" ZMQ_CPPFLAGS="-I${with_zmq}/include" ]) HAVE_ZMQ=0 if test "$with_zmq" != "no"; then LD_FLAGS="$LDFLAGS $ZMQ_LDFLAGS" CPPFLAGS="$CPPFLAGS $ZMQ_CPPFLAGS" AC_LANG_PUSH([C]) AC_CHECK_HEADER(zmq.h, [zmq_h=yes], [zmq_h=no]) AC_LANG_POP([C]) if test "$zmq_h" = "yes"; then version=ifelse([$1], ,4.0.0,$1) AC_MSG_CHECKING([for ZMQ version >= $version]) version=$(echo $version | tr '.' ',') AC_EGREP_CPP([version_ok], [ #include #if defined(ZMQ_VERSION) && ZMQ_VERSION >= ZMQ_MAKE_VERSION($version) version_ok #endif ],[ AC_MSG_RESULT(yes) HAVE_ZMQ=1 ZMQ_LIBS="-lzmq" AC_SUBST(ZMQ_LDFLAGS) AC_SUBST(ZMQ_CPPFLAGS) AC_SUBST(ZMQ_LIBS) ], AC_MSG_RESULT([no valid ZMQ version was found])) else AC_MSG_WARN([no valid ZMQ installation was found]) fi if test $HAVE_ZMQ = 1; then # execute ACTION-IF-FOUND (if present): ifelse([$2], , :, [$2]) else # execute ACTION-IF-NOT-FOUND (if present): ifelse([$3], , :, [$3]) fi else AC_MSG_NOTICE([not checking for ZMQ]) fi AC_DEFINE(HAVE_ZMQ,,[define if the ZMQ library is available]) ]) Opendigitalradio-ODR-DabMux-29c710c/man/000077500000000000000000000000001476627344300177155ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/man/odr-dabmux.1000066400000000000000000000020471476627344300220440ustar00rootroot00000000000000.TH ODR-DABMUX "1" "March 2025" "odr-dabmux 5.1.0" "User Commands" .SH NAME \fBodr\-dabmux\fR \- A software DAB multiplexer .SH SYNOPSIS odr\-dabmux .PP This software requires a configuration file. See doc/example.config for an example format for the configuration file .SH DESCRIPTION odr\-dabmux implements a DAB multiplexer that combines all audio and data inputs into an ETI output. It can be used off-line (i.e. not real-time) to generate ETI data for later processing, or in a real-time streaming scenario (e.g. in a transmitter). .PP odr\-dabmux can read input audio or data from files (".mp2" for DAB, ".dabp" for DAB+), FIFOs (also called "named pipes"), or from a network connection. This network connection can use UDP (STI-D) or EDI. .PP The configuration of the multiplexer is given in a configuration file, whose format is defined in the example files in the doc/ folder inside the ODR-DabMux repository. .SH SEE ALSO odr\-audioenc(1), odr\-dabmod(1) A user guide for the mmbTools is available http://www.opendigitalradio.org/ Opendigitalradio-ODR-DabMux-29c710c/src/000077500000000000000000000000001476627344300177315ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/src/ConfigParser.cpp000066400000000000000000001326461476627344300230330ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 The Configuration parser sets up the ensemble according to the configuration given in a boost property tree, which is directly derived from a config file. The format of the configuration is given in doc/example.mux */ /* 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 "ConfigParser.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "utils.h" #include "input/Edi.h" #include "input/Prbs.h" #include "input/Zmq.h" #include "input/File.h" #include "input/Udp.h" #include "fig/FIG0structs.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using boost::property_tree::ptree; using boost::property_tree::ptree_error; constexpr uint16_t DEFAULT_DATA_BITRATE = 384; constexpr uint16_t DEFAULT_PACKET_BITRATE = 32; constexpr uint32_t DEFAULT_SERVICE_ID = 50; static void setup_subchannel_from_ptree(shared_ptr& subchan, const ptree &pt, std::shared_ptr ensemble, const string& subchanuid); static uint16_t get_announcement_flag_from_ptree(ptree& pt) { uint16_t flags = 0; for (size_t flag = 0; flag < 16; flag++) { std::string announcement_name(annoucement_flags_names[flag]); bool flag_set = pt.get(announcement_name, false); if (flag_set) { flags |= (1 << flag); } } return flags; } static void parse_fig2_label(ptree& pt, DabLabel& label) { label.setFIG2Label(pt.get("fig2_label", "")); uint16_t character_field = hexparse(pt.get("fig2_label_character_flag", "0")); if (character_field) { label.setFIG2CharacterField(character_field); } auto pt_tc = pt.get_child_optional("fig2_label_text_control"); if (pt_tc) { FIG2TextControl tc; tc.bidi_flag = pt_tc->get("bidi", tc.bidi_flag); auto base_direction = pt_tc->get("base_direction", "LTR"); tc.base_direction_is_rtl = (base_direction == "RTL"); tc.contextual_flag = pt_tc->get("contextual", tc.contextual_flag); tc.combining_flag = pt_tc->get("combining", tc.combining_flag); label.setFIG2TextControl(tc); } } // Parse the linkage section static void parse_linkage(ptree& pt, std::shared_ptr ensemble) { auto pt_linking = pt.get_child_optional("linking"); if (pt_linking) { for (const auto& it : *pt_linking) { const string setuid = it.first; const ptree pt_set = it.second; uint16_t lsn = hexparse(pt_set.get("lsn", "0")); if (lsn == 0 or lsn > 0xFFF) { etiLog.level(error) << "LSN for linking set " << setuid << " invalid or missing"; throw runtime_error("Invalid service linking definition"); } bool active = pt_set.get("active", true); bool hard = pt_set.get("hard", true); bool international = pt_set.get("international", false); string service_uid = pt_set.get("keyservice", ""); auto linkageset = make_shared(setuid, lsn, active, hard, international); linkageset->keyservice = service_uid; // TODO check if it exists auto pt_list = pt_set.get_child_optional("list"); if (service_uid.empty()) { if (pt_list) { etiLog.level(error) << "list defined but no keyservice in linking set " << setuid; throw runtime_error("Invalid service linking definition"); } } else { if (not pt_list) { etiLog.level(error) << "list missing in linking set " << setuid; throw runtime_error("Invalid service linking definition"); } for (const auto& it : *pt_list) { const string linkuid = it.first; const ptree pt_link = it.second; ServiceLink link; string link_type = pt_link.get("type", ""); if (link_type == "dab") link.type = ServiceLinkType::DAB; else if (link_type == "fm") link.type = ServiceLinkType::FM; else if (link_type == "drm") link.type = ServiceLinkType::DRM; else if (link_type == "amss") link.type = ServiceLinkType::AMSS; else { etiLog.level(error) << "Invalid type " << link_type << " for link " << linkuid; throw runtime_error("Invalid service linking definition"); } link.id = hexparse(pt_link.get("id", "0")); if (link.id == 0) { etiLog.level(error) << "id for link " << linkuid << " invalid or missing"; throw runtime_error("Invalid service linking definition"); } if (international) { link.ecc = hexparse(pt_link.get("ecc", "0")); if (link.ecc == 0) { etiLog.level(error) << "ecc for link " << linkuid << " invalid or missing"; throw runtime_error("Invalid service linking definition"); } } else { link.ecc = 0; } linkageset->id_list.push_back(link); } } ensemble->linkagesets.push_back(linkageset); } } } // Parse the FI section static void parse_freq_info(ptree& pt, std::shared_ptr ensemble) { auto pt_frequency_information = pt.get_child_optional("frequency_information"); if (pt_frequency_information) { for (const auto& it_fi : *pt_frequency_information) { const string fi_uid = it_fi.first; const ptree pt_fi = it_fi.second; FrequencyInformation fi; fi.uid = fi_uid; fi.other_ensemble = pt_fi.get("oe", false); string rm_str = pt_fi.get("range_modulation", ""); if (rm_str == "dab") { fi.rm = RangeModulation::dab_ensemble; } else if (rm_str == "fm") { fi.rm = RangeModulation::fm_with_rds; } else if (rm_str == "drm") { fi.rm = RangeModulation::drm; } else if (rm_str == "amss") { fi.rm = RangeModulation::amss; } else if (rm_str == "") { throw runtime_error("Missing range_modulation in FI " + fi_uid); } else { throw runtime_error("Invalid range_modulation '" + rm_str + "' in FI " + fi_uid); } fi.continuity = pt_fi.get("continuity"); try { switch (fi.rm) { case RangeModulation::dab_ensemble: { fi.fi_dab.eid = hexparse(pt_fi.get("eid")); for (const auto& list_it : pt_fi.get_child("frequencies")) { const string fi_list_uid = list_it.first; const ptree pt_list_entry = list_it.second; FrequencyInfoDab::ListElement el; el.uid = fi_list_uid; el.frequency = pt_list_entry.get("frequency"); bool signal_mode_1 = pt_list_entry.get("signal_mode_1", false); bool adjacent = pt_list_entry.get("adjacent", false); if (adjacent and signal_mode_1) { el.control_field = FrequencyInfoDab::ControlField_e::adjacent_mode1; } else if (adjacent and not signal_mode_1) { el.control_field = FrequencyInfoDab::ControlField_e::adjacent_no_mode; } if (not adjacent and signal_mode_1) { el.control_field = FrequencyInfoDab::ControlField_e::disjoint_mode1; } else if (not adjacent and not signal_mode_1) { el.control_field = FrequencyInfoDab::ControlField_e::disjoint_no_mode; } fi.fi_dab.frequencies.push_back(el); } if (fi.fi_dab.frequencies.empty()) { throw runtime_error("Empty frequency list in FI " + fi.uid); } } break; case RangeModulation::fm_with_rds: { fi.fi_fm.pi_code = hexparse(pt_fi.get("pi_code")); std::stringstream frequencies_ss; frequencies_ss << pt_fi.get("frequencies"); for (std::string freq; std::getline(frequencies_ss, freq, ' '); ) { fi.fi_fm.frequencies.push_back(std::stof(freq)); } if (fi.fi_fm.frequencies.empty()) { throw runtime_error("Empty frequency list in FI " + fi.uid); } } break; case RangeModulation::drm: { fi.fi_drm.drm_service_id = hexparse(pt_fi.get("drm_id")); std::stringstream frequencies_ss; frequencies_ss << pt_fi.get("frequencies"); for (std::string freq; std::getline(frequencies_ss, freq, ' '); ) { fi.fi_drm.frequencies.push_back(std::stof(freq)); } if (fi.fi_drm.frequencies.empty()) { throw runtime_error("Empty frequency list in FI " + fi.uid); } } break; case RangeModulation::amss: { fi.fi_amss.amss_service_id = hexparse(pt_fi.get("amss_id")); std::stringstream frequencies_ss; frequencies_ss << pt_fi.get("frequencies"); for (std::string freq; std::getline(frequencies_ss, freq, ' '); ) { fi.fi_amss.frequencies.push_back(std::stof(freq)); } if (fi.fi_amss.frequencies.empty()) { throw runtime_error("Empty frequency list in FI " + fi.uid); } } break; } // switch(rm) } catch (const ptree_error &e) { throw runtime_error("invalid configuration for FI " + fi_uid); } ensemble->frequency_information.emplace_back(move(fi)); } // for over fi /* We sort all FI to have the OE=0 first and the OE=1 afterwards, to * avoid having to send FIG0 headers every time it switches. */ std::sort( ensemble->frequency_information.begin(), ensemble->frequency_information.end(), [](const FrequencyInformation& first, const FrequencyInformation& second) { const int oe_first = first.other_ensemble ? 1 : 0; const int oe_second = second.other_ensemble ? 1 : 0; return oe_first < oe_second; } ); } // if FI present } static void parse_other_service_linking(ptree& pt, std::shared_ptr ensemble) { auto pt_other_services = pt.get_child_optional("other-services"); if (pt_other_services) { for (const auto& it_service : *pt_other_services) { const string srv_uid = it_service.first; const ptree pt_srv = it_service.second; ServiceOtherEnsembleInfo info; try { info.service_id = hexparse(pt_srv.get("id")); auto oelist = pt_srv.get("other_ensembles", ""); if (not oelist.empty()) { vector oe_string_list; boost::split(oe_string_list, oelist, boost::is_any_of(",")); for (const auto& oe_string : oe_string_list) { if (oe_string == "") { continue; } try { info.other_ensembles.push_back(hexparse(oe_string)); } catch (const std::exception& e) { etiLog.level(warn) << "Cannot parse '" << oelist << "' other_ensembles for service " << srv_uid << ": " << e.what(); } } ensemble->service_other_ensemble.push_back(move(info)); } } catch (const std::exception &e) { etiLog.level(warn) << "Cannot parse other_ensembles for service " << srv_uid << ": " << e.what(); } } // for over services } // if other-services present } static void parse_general(ptree& pt, std::shared_ptr ensemble) { /******************** READ GENERAL OPTIONS *****************/ ptree pt_general = pt.get_child("general"); /* Dab mode logic */ switch (pt_general.get("dabmode", 1)) { case 1: ensemble->transmission_mode = TransmissionMode_e::TM_I; break; case 2: ensemble->transmission_mode = TransmissionMode_e::TM_II; break; case 3: ensemble->transmission_mode = TransmissionMode_e::TM_III; break; case 4: ensemble->transmission_mode = TransmissionMode_e::TM_IV; break; default: throw runtime_error("Mode must be between 1-4"); } /******************** READ ENSEMBLE PARAMETERS *************/ ptree pt_ensemble = pt.get_child("ensemble"); /* Ensemble ID */ ensemble->id = hexparse(pt_ensemble.get("id", "0")); if (ensemble->id == 0) { etiLog.level(warn) << "Ensemble ID is 0!"; } /* Extended Country Code */ ensemble->ecc = hexparse(pt_ensemble.get("ecc", "0")); if (ensemble->ecc == 0) { etiLog.level(warn) << "ECC is 0!"; } ensemble->international_table = pt_ensemble.get("international-table", ensemble->international_table); bool reconfig_counter_is_hash = false; try { if (pt_ensemble.get("reconfig-counter") == "hash") { reconfig_counter_is_hash = true; } } catch (const ptree_error &e) { } if (reconfig_counter_is_hash) { ensemble->reconfig_counter = -2; } else { ensemble->reconfig_counter = pt_ensemble.get("reconfig-counter", ensemble->reconfig_counter); } string lto_auto = pt_ensemble.get("local-time-offset", ""); if (lto_auto == "auto") { ensemble->lto_auto = true; ensemble->lto = 0; } else { double lto_hours = pt_ensemble.get("local-time-offset", 0.0); if (round(lto_hours * 2) != lto_hours * 2) { etiLog.level(error) << "Ensemble local time offset " << lto_hours << "h cannot be expressed in half-hour blocks."; throw runtime_error("ensemble local-time-offset definition error"); } if (lto_hours > 12 || lto_hours < -12) { etiLog.level(error) << "Ensemble local time offset " << lto_hours << "h out of bounds [-12, +12]."; throw runtime_error("ensemble local-time-offset definition error"); } ensemble->lto_auto = false; ensemble->lto = abs(rint(lto_hours * 2)); } int success = -5; string ensemble_label = pt_ensemble.get("label", ""); string ensemble_short_label(ensemble_label); try { ensemble_short_label = pt_ensemble.get("shortlabel"); success = ensemble->label.setLabel(ensemble_label, ensemble_short_label); } catch (const ptree_error &e) { etiLog.level(warn) << "Ensemble short label undefined, " "truncating label " << ensemble_label; success = ensemble->label.setLabel(ensemble_label); } switch (success) { case 0: break; case -1: etiLog.level(error) << "Ensemble short label " << ensemble_short_label << " is not subset of label '" << ensemble_label << "'"; throw runtime_error("ensemble label definition error"); case -2: etiLog.level(error) << "Ensemble short label " << ensemble_short_label << " is too long (max 8 characters)"; throw runtime_error("ensemble label definition error"); case -3: etiLog.level(error) << "Ensemble label " << ensemble_label << " is too long (max 16 characters)"; throw runtime_error("ensemble label definition error"); default: etiLog.level(error) << "Ensemble short label definition: program error !"; abort(); } parse_fig2_label(pt_ensemble, ensemble->label); try { ptree pt_announcements = pt_ensemble.get_child("announcements"); for (auto announcement : pt_announcements) { string name = announcement.first; ptree pt_announcement = announcement.second; auto cl = make_shared(name); cl->cluster_id = hexparse(pt_announcement.get("cluster")); if (cl->cluster_id == 0) { throw runtime_error("Announcement cluster id " + to_string(cl->cluster_id) + " is not allowed"); } if (cl->cluster_id == 255) { etiLog.level(debug) << "Alarm flag for FIG 0/0 is set 1, because announcement group with cluster id oxFF is found."; ensemble->alarm_flag = 1; } cl->flags = get_announcement_flag_from_ptree( pt_announcement.get_child("flags")); cl->subchanneluid = pt_announcement.get("subchannel"); rcs.enrol(cl.get()); ensemble->clusters.push_back(cl); } } catch (const ptree_error& e) { etiLog.level(info) << "No announcements defined in ensemble because " << e.what(); } } void parse_ptree( boost::property_tree::ptree& pt, std::shared_ptr ensemble) { parse_general(pt, ensemble); /******************** READ SERVICES PARAMETERS *************/ map > allservices; /* For each service, we keep a separate SCIdS counter */ map, int> SCIdS_per_service; ptree pt_services = pt.get_child("services"); for (ptree::iterator it = pt_services.begin(); it != pt_services.end(); ++it) { string serviceuid = it->first; ptree pt_service = it->second; stringstream def_serviceid; def_serviceid << DEFAULT_SERVICE_ID + ensemble->services.size(); uint32_t new_service_id = hexparse(pt_service.get("id", def_serviceid.str())); // Ensure that both UID and service ID are unique for (auto srv : ensemble->services) { if (srv->uid == serviceuid) { throw runtime_error("Duplicate service uid " + serviceuid); } if (srv->id == new_service_id) { throw runtime_error("Duplicate service id " + to_string(new_service_id)); } } auto service = make_shared(serviceuid); ensemble->services.push_back(service); try { /* Parse announcements */ service->ASu = get_announcement_flag_from_ptree( pt_service.get_child("announcements")); auto clusterlist = pt_service.get("announcements.clusters", ""); vector clusters_s; boost::split(clusters_s, clusterlist, boost::is_any_of(",")); for (const auto& cluster_s : clusters_s) { if (cluster_s == "") { continue; } try { auto cluster_id = hexparse(cluster_s); if (cluster_id == 255) { etiLog.level(warn) << "Announcement cluster id " + to_string(cluster_id) + " is not allowed and is ignored in FIG 0/18."; continue; } service->clusters.push_back(cluster_id); } catch (const std::exception& e) { etiLog.level(warn) << "Cannot parse '" << clusterlist << "' announcement clusters for service " << serviceuid << ": " << e.what(); } } if (service->ASu != 0 and service->clusters.empty()) { etiLog.level(warn) << "Cluster list for service " << serviceuid << "is empty, but announcements are defined"; } } catch (const ptree_error& e) { service->ASu = 0; service->clusters.clear(); etiLog.level(info) << "No announcements defined in service " << serviceuid; } int success = -5; string servicelabel = pt_service.get("label", ""); string serviceshortlabel(servicelabel); try { serviceshortlabel = pt_service.get("shortlabel"); success = service->label.setLabel(servicelabel, serviceshortlabel); } catch (const ptree_error &e) { etiLog.level(warn) << "Service short label undefined, " "truncating label " << servicelabel; success = service->label.setLabel(servicelabel); } switch (success) { case 0: break; case -1: etiLog.level(error) << "Service short label " << serviceshortlabel << " is not subset of label '" << servicelabel << "'"; throw runtime_error("service label definition error"); case -2: etiLog.level(error) << "Service short label " << serviceshortlabel << " is too long (max 8 characters)"; throw runtime_error("service label definition error"); case -3: etiLog.level(error) << "Service label " << servicelabel << " is too long (max 16 characters)"; throw runtime_error("service label definition error"); default: etiLog.level(error) << "Service short label definition: program error !"; abort(); } parse_fig2_label(pt_service, service->label); service->id = new_service_id; service->ecc = hexparse(pt_service.get("ecc", "0")); service->pty_settings.pty = hexparse(pt_service.get("pty", "0")); // Default to dynamic for backward compatibility const string dynamic_no_static_str = pt_service.get("pty-sd", "dynamic"); if (dynamic_no_static_str == "dynamic") { service->pty_settings.dynamic_no_static = true; } else if (dynamic_no_static_str == "static") { service->pty_settings.dynamic_no_static = false; } else { throw runtime_error("pty-sd setting for service " + serviceuid + " is invalid"); } service->language = hexparse(pt_service.get("language", "0")); auto oelist = pt_service.get("other_ensembles", ""); if (not oelist.empty()) { throw runtime_error( "You are using the deprecated other_ensembles inside " "'services' specification. Please see doc/servicelinking.mux " "for the new syntax."); } allservices[serviceuid] = service; // Set the service's SCIds to zero SCIdS_per_service[service] = 0; if (allservices.count(serviceuid) != 1) { throw logic_error("Assertion error: duplicated service with uid " + serviceuid); } } /******************** READ SUBCHAN PARAMETERS **************/ map > allsubchans; ptree pt_subchans = pt.get_child("subchannels"); for (ptree::iterator it = pt_subchans.begin(); it != pt_subchans.end(); ++it) { string subchanuid = it->first; auto subchan = make_shared(subchanuid); ensemble->subchannels.push_back(subchan); try { setup_subchannel_from_ptree(subchan, it->second, ensemble, subchanuid); } catch (const runtime_error &e) { etiLog.log(error, "%s\n", e.what()); throw; } // keep subchannels in map, and check for uniqueness of the UID if (allsubchans.count(subchanuid) == 0) { allsubchans[subchanuid] = subchan; } else { stringstream ss; ss << "Subchannel with uid " << subchanuid << " not unique!"; throw runtime_error(ss.str()); } } /******************** READ COMPONENT PARAMETERS ************/ map > allcomponents; ptree pt_components = pt.get_child("components"); for (ptree::iterator it = pt_components.begin(); it != pt_components.end(); ++it) { string componentuid = it->first; ptree pt_comp = it->second; shared_ptr service; try { // Those two uids serve as foreign keys to select the service+subchannel string service_uid = pt_comp.get("service"); if (allservices.count(service_uid) != 1) { stringstream ss; ss << "Component with uid " << componentuid << " is refers to unknown service " << service_uid << " !"; throw runtime_error(ss.str()); } service = allservices[service_uid]; } catch (const ptree_error &e) { stringstream ss; ss << "Component with uid " << componentuid << " is missing service definition!"; throw runtime_error(ss.str()); } shared_ptr subchannel; try { string subchan_uid = pt_comp.get("subchannel"); if (allsubchans.count(subchan_uid) != 1) { stringstream ss; ss << "Component with uid " << componentuid << " is refers to unknown subchannel " << subchan_uid << " !"; throw runtime_error(ss.str()); } subchannel = allsubchans[subchan_uid]; } catch (const ptree_error &e) { stringstream ss; ss << "Component with uid " << componentuid << " is missing subchannel definition!"; throw runtime_error(ss.str()); } uint8_t component_type = hexparse(pt_comp.get("type", "0")); auto component = make_shared(componentuid); component->serviceId = service->id; component->subchId = subchannel->id; component->SCIdS = SCIdS_per_service[service]++; component->type = component_type; int success = -5; string componentlabel = pt_comp.get("label", ""); string componentshortlabel(componentlabel); try { componentshortlabel = pt_comp.get("shortlabel"); success = component->label.setLabel(componentlabel, componentshortlabel); } catch (const ptree_error &e) { if (not componentlabel.empty()) { etiLog.level(warn) << "Component short label undefined, " "truncating label " << componentlabel; } success = component->label.setLabel(componentlabel); } switch (success) { case 0: break; case -1: etiLog.level(error) << "Component short label " << componentshortlabel << " is not subset of label '" << componentlabel << "'"; throw runtime_error("component label definition error"); case -2: etiLog.level(error) << "Component short label " << componentshortlabel << " is too long (max 8 characters)"; throw runtime_error("component label definition error"); case -3: etiLog.level(error) << "Component label " << componentlabel << " is too long (max 16 characters)"; throw runtime_error("component label definition error"); default: etiLog.level(error) << "Component short label definition: program error !"; abort(); } parse_fig2_label(pt_comp, component->label); if (component->SCIdS == 0 and not component->label.long_label().empty()) { etiLog.level(warn) << "Primary component " << component->uid << " has label set. Since V2.1.1 of the specification, only secondary" " components are allowed to have labels."; } auto pt_ua = pt_comp.get_child_optional("user-applications"); if (pt_ua) { for (const auto& ua_entry : *pt_ua) { const string ua_key = ua_entry.first; const string ua_value = ua_entry.second.data(); if (ua_key != "userapp") { etiLog.level(error) << "user-applications should only contain 'userapp' keys"; throw runtime_error("component user-applications definition error"); } userApplication ua; // Values from TS 101 756 Table 16 if (ua_value == "slideshow") { ua.uaType = FIG0_13_APPTYPE_SLIDESHOW; // This was previously hardcoded in FIG0/13 and means "MOT, start of X-PAD data group" ua.xpadAppType = 12; } else if (ua_value == "spi") { ua.uaType = FIG0_13_APPTYPE_SPI; ua.xpadAppType = 16; } else if (ua_value == "website") { ua.uaType = FIG0_13_APPTYPE_WEBSITE; ua.xpadAppType = 12; } else if (ua_value == "journaline") { ua.uaType = FIG0_13_APPTYPE_JOURNALINE; ua.xpadAppType = 12; } if (component->isPacketComponent(ensemble->subchannels)) { component->packet.uaTypes.push_back(ua); } else { component->audio.uaTypes.push_back(ua); } } } else { // Setting only figtype is the old format which allows the definition of a single // user application type only. int figType = hexparse(pt_comp.get("figtype", "-1")); if (figType != -1) { etiLog.level(warn) << "The figtype setting is deprecated in favour of user-applications. Please see example configurations."; if (figType >= (1<<12)) { stringstream ss; ss << "Component with uid " << componentuid << ": figtype '" << figType << "' is too large !"; throw runtime_error(ss.str()); } userApplication ua; ua.uaType = figType; // This was previously hardcoded in FIG0/13 and means "MOT, start of X-PAD data group" ua.xpadAppType = 12; if (component->isPacketComponent(ensemble->subchannels)) { component->packet.uaTypes.push_back(ua); } else { component->audio.uaTypes.push_back(ua); } } } if (component->isPacketComponent(ensemble->subchannels)) { int packet_address = hexparse(pt_comp.get("address", "-1")); if (packet_address != -1) { if (! component->isPacketComponent(ensemble->subchannels)) { stringstream ss; ss << "Component with uid " << componentuid << " is not packet, cannot have address defined !"; throw runtime_error(ss.str()); } component->packet.address = packet_address; } int packet_datagroup = pt_comp.get("datagroup", false); if (packet_datagroup) { if (! component->isPacketComponent(ensemble->subchannels)) { stringstream ss; ss << "Component with uid " << componentuid << " is not packet, cannot have datagroup enabled !"; throw runtime_error(ss.str()); } component->packet.datagroup = packet_datagroup; } } ensemble->components.push_back(component); } parse_linkage(pt, ensemble); parse_freq_info(pt, ensemble); parse_other_service_linking(pt, ensemble); } static Inputs::dab_input_zmq_config_t setup_zmq_input( const ptree &pt, const std::string& subchanuid) { Inputs::dab_input_zmq_config_t zmqconfig; try { zmqconfig.buffer_size = pt.get("zmq-buffer"); } catch (const ptree_error &e) { stringstream ss; ss << "Subchannel " << subchanuid << ": " << "no zmq-buffer defined!"; throw runtime_error(ss.str()); } try { zmqconfig.prebuffering = pt.get("zmq-prebuffering"); } catch (const ptree_error &e) { stringstream ss; ss << "Subchannel " << subchanuid << ": " << "no zmq-prebuffer defined!"; throw runtime_error(ss.str()); } zmqconfig.curve_encoder_keyfile = pt.get("encoder-key",""); zmqconfig.curve_secret_keyfile = pt.get("secret-key",""); zmqconfig.curve_public_keyfile = pt.get("public-key",""); zmqconfig.enable_encryption = pt.get("encryption", false); return zmqconfig; } static void setup_subchannel_from_ptree(shared_ptr& subchan, const ptree &pt, std::shared_ptr ensemble, const string& subchanuid) { string type; /* Read type first */ try { type = pt.get("type"); } catch (const ptree_error &e) { throw runtime_error("Subchannel with uid " + subchanuid + " has no type defined!"); } /* Up to v2.3.1, both inputfile and inputuri are supported, and are * equivalent. inputuri has precedence. * * After that, either inputfile or the (inputproto, inputuri) pair must be given, but not both. */ string inputUri = pt.get("inputuri", ""); string proto = pt.get("inputproto", ""); if (inputUri.empty() and proto.empty()) { try { /* Old approach, derives proto from scheme used in the URL. * This makes it impossible to distinguish between ZMQ tcp:// and * EDI tcp:// */ inputUri = pt.get("inputfile"); size_t protopos = inputUri.find("://"); if (protopos == string::npos) { proto = "file"; } else { proto = inputUri.substr(0, protopos); if (proto == "tcp" or proto == "epgm" or proto == "ipc") { proto = "zmq"; } else if (proto == "sti-rtp") { proto = "sti"; } } } catch (const ptree_error &e) { throw runtime_error("Subchannel with uid " + subchanuid + " has no input defined!"); } } else if (inputUri.empty() or proto.empty()) { throw runtime_error("Must define both inputuri and inputproto for uid " + subchanuid); } subchan->inputUri = inputUri; if (type == "dabplus" or type == "audio") { if(type == "dabplus") { subchan->type = subchannel_type_t::DABPlusAudio; } else { subchan->type = subchannel_type_t::DABAudio; } subchan->bitrate = 0; if (proto == "file") { if (type == "audio") { subchan->input = make_shared(); } else if (type == "dabplus") { subchan->input = make_shared(); } else { throw logic_error("Incomplete handling of file input"); } } else if (proto == "zmq") { auto zmqconfig = setup_zmq_input(pt, subchanuid); if (type == "audio") { auto inzmq = make_shared(subchanuid, zmqconfig); rcs.enrol(inzmq.get()); subchan->input = inzmq; } else if (type == "dabplus") { auto inzmq = make_shared(subchanuid, zmqconfig); rcs.enrol(inzmq.get()); subchan->input = inzmq; } } else if (proto == "edi") { Inputs::dab_input_edi_config_t config; config.buffer_size = pt.get("buffer", config.buffer_size); config.prebuffering = pt.get("prebuffering", config.prebuffering); auto inedi = make_shared(subchanuid, config); rcs.enrol(inedi.get()); subchan->input = inedi; } else if (proto == "stp") { subchan->input = make_shared(); } else { stringstream ss; ss << "Subchannel with uid " << subchanuid << ": Invalid protocol for " << type << " input (" << proto << ")" << endl; throw runtime_error(ss.str()); } } else if (type == "data" and proto == "prbs") { subchan->input = make_shared(); subchan->type = subchannel_type_t::DataDmb; subchan->bitrate = DEFAULT_DATA_BITRATE; } else if (type == "data" or type == "dmb") { if (proto == "udp") { subchan->input = make_shared(); } else if (proto == "file" or proto == "fifo") { subchan->input = make_shared(); } else { stringstream ss; ss << "Subchannel with uid " << subchanuid << ": Invalid protocol for data input (" << proto << ")" << endl; throw runtime_error(ss.str()); } subchan->type = subchannel_type_t::DataDmb; subchan->bitrate = DEFAULT_DATA_BITRATE; if (type == "dmb") { /* The old dmb input took care of interleaving and Reed-Solomon encoding. This * code is unported. * See dabInputDmbFile.cpp */ etiLog.level(warn) << "uid " << subchanuid << " of type Dmb uses RAW input"; } } else if (type == "packet" or type == "enhancedpacket") { subchan->type = subchannel_type_t::Packet; subchan->bitrate = DEFAULT_PACKET_BITRATE; subchan->packet_enhanced = (type == "enhancedpacket"); subchan->input = make_shared(subchan->packet_enhanced); } else { stringstream ss; ss << "Subchannel with uid " << subchanuid << " has unknown type!"; throw runtime_error(ss.str()); } if (pt.get("nonblock", false)) { if (auto filein = dynamic_pointer_cast(subchan->input)) { filein->setNonblocking(true); } else { etiLog.level(warn) << "The nonblock option is not supported"; } } if (pt.get("load_entire_file", false)) { if (auto filein = dynamic_pointer_cast(subchan->input)) { filein->setLoadEntireFile(true); } else { etiLog.level(warn) << "The load_entire_file option is not supported"; } } const string bufferManagement = pt.get("buffer-management", "prebuffering"); if (bufferManagement == "prebuffering") { subchan->input->setBufferManagement(Inputs::BufferManagement::Prebuffering); } else if (bufferManagement == "timestamped") { subchan->input->setBufferManagement(Inputs::BufferManagement::Timestamped); } else { throw runtime_error("Subchannel with uid " + subchanuid + " has invalid buffer-management !"); } const int32_t tist_delay = pt.get("tist-delay", 0); subchan->input->setTistDelay(chrono::milliseconds(tist_delay)); subchan->startAddress = 0; dabProtection* protection = &subchan->protection; if (type == "audio") { protection->form = UEP; protection->level = 2; protection->uep.tableIndex = 0; } else { protection->level = 2; protection->form = EEP; protection->eep.profile = EEP_A; } /* Get bitrate */ try { subchan->bitrate = pt.get("bitrate"); if ((subchan->bitrate & 0x7) != 0) { stringstream ss; ss << "Subchannel with uid " << subchanuid << ": Bitrate (" << subchan->bitrate << " not a multiple of 8!"; throw runtime_error(ss.str()); } } catch (const ptree_error &e) { throw runtime_error("Error, no bitrate defined for subchannel " + subchanuid); } /* Get id */ try { subchan->id = hexparse(pt.get("id")); if (subchan->id >= 64) { throw runtime_error("Invalid subchannel id " + to_string(subchan->id)); } } catch (const ptree_error &e) { for (int i = 0; i < 64; ++i) { // Find first free subchannel auto subchannel = getSubchannel(ensemble->subchannels, i); if (subchannel == ensemble->subchannels.end()) { subchannel = ensemble->subchannels.end() - 1; subchan->id = i; break; } } } /* Get optional protection profile */ string profile = pt.get("protection-profile", ""); if (profile == "EEP_A") { protection->form = EEP; protection->eep.profile = EEP_A; } else if (profile == "EEP_B") { protection->form = EEP; protection->eep.profile = EEP_B; } else if (profile == "UEP") { protection->form = UEP; } else if (profile == "") { /* take defaults */ } else { throw runtime_error("Invalid protection-profile: " + profile); } /* Get protection level */ try { int level = pt.get("protection"); if (protection->form == UEP) { if ((level < 1) || (level > 5)) { stringstream ss; ss << "Subchannel with uid " << subchanuid << ": protection level must be between " "1 to 5 inclusively (current = " << level << " )"; throw runtime_error(ss.str()); } } else if (protection->form == EEP) { if ((level < 1) || (level > 4)) { stringstream ss; ss << "Subchannel with uid " << subchanuid << ": protection level must be between " "1 to 4 inclusively (current = " << level << " )"; throw runtime_error(ss.str()); } } protection->level = level - 1; } catch (const ptree_error &e) { stringstream ss; ss << "Subchannel with uid " << subchanuid << ": protection level undefined!"; throw runtime_error(ss.str()); } } Opendigitalradio-ODR-DabMux-29c710c/src/ConfigParser.h000066400000000000000000000025241476627344300224670ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li The Configuration parser sets up the ensemble according to the configuration given in a boost property tree, which is directly derived from a config file. The format of the configuration is given in doc/example.mux */ /* 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 . */ #pragma once #include "MuxElements.h" #include #include void parse_ptree(boost::property_tree::ptree& pt, std::shared_ptr ensemble); Opendigitalradio-ODR-DabMux-29c710c/src/DabMultiplexer.cpp000066400000000000000000000725261476627344300233720ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2025 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 #include #include #include "DabMultiplexer.h" #include "ConfigParser.h" #include "ManagementServer.h" #include "crc.h" #include "utils.h" using namespace std; static vector split_pipe_separated_string(const std::string& s) { stringstream ss; ss << s; string elem; vector components; while (getline(ss, elem, '|')) { components.push_back(elem); } return components; } uint64_t MuxTime::init(uint32_t tist_at_fct0_us) { /* At startup, derive edi_time, TIST and CIF count such that there is * a consistency across mux restarts. Ensure edi_time and TIST represent * current time. * * FCT and DLFC are directly derived from m_currentFrame. * Every 6s, FCT overflows. DLFC overflows at 5000 every 120s. * * Keep a granularity of 24ms, which corresponds to the duration of an ETI * frame, to get nicer timestamps. */ using Sec = chrono::seconds; const auto now = chrono::system_clock::now(); const auto offset = now - chrono::time_point_cast(now); if (offset >= chrono::seconds(1)) { throw std::logic_error("Invalid startup offset calculation for TIST! " + to_string(chrono::duration_cast(offset).count()) + " ms"); } const time_t t_now = chrono::system_clock::to_time_t(chrono::time_point_cast(now)); m_edi_time = t_now - (t_now % 6); uint64_t currentFrame = 0; time_t edi_time_at_cif0 = t_now - (t_now % 120); while (edi_time_at_cif0 < m_edi_time) { edi_time_at_cif0 += 6; currentFrame += 250; } if (edi_time_at_cif0 != m_edi_time) { throw std::logic_error("Invalid startup offset calculation for CIF!"); } int64_t offset_ms = chrono::duration_cast(offset).count(); offset_ms += 1000 * (t_now - m_edi_time); if (tist_at_fct0_us >= 1000000) { etiLog.level(error) << "tist_at_fct0 may not be larger than 1s"; throw MuxInitException(); } m_timestamp = (uint64_t)tist_at_fct0_us * 16384 / 1000; while (offset_ms >= 24) { increment_timestamp(); currentFrame++; offset_ms -= 24; } return currentFrame; } void MuxTime::increment_timestamp() { m_timestamp += 24 << 14; // Shift 24ms by 14 to Timestamp level 2 if (m_timestamp > 0xf9FFff) { m_timestamp -= 0xfa0000; // Subtract 16384000, corresponding to one second m_edi_time += 1; // Also update MNSC time for next time FP==0 mnsc_increment_time = true; } } std::pair MuxTime::get_time() { // negative tist_offset not supported, because the calculation is annoying if (tist_offset < 0) return {m_timestamp, m_edi_time}; double fractional_part = tist_offset - std::floor(tist_offset); const size_t steps = std::lround(std::floor(fractional_part / 24e-3)); uint32_t timestamp = m_timestamp + (24 << 14) * steps; std::time_t edi_time = m_edi_time + std::lround(std::floor(tist_offset)); if (timestamp > 0xf9FFff) { edi_time += 1; } return {timestamp % 0xfa0000, edi_time}; } DabMultiplexer::DabMultiplexer(boost::property_tree::ptree pt) : RemoteControllable("mux"), m_pt(pt), ensemble(std::make_shared()), m_clock_tai(split_pipe_separated_string(pt.get("general.tai_clock_bulletins", ""))), fig_carousel(ensemble) { RC_ADD_PARAMETER(frames, "Show number of frames generated [read-only]"); RC_ADD_PARAMETER(tist_offset, "Timestamp offset in fractional number of seconds"); rcs.enrol(&m_clock_tai); } DabMultiplexer::~DabMultiplexer() { rcs.remove_controllable(&m_clock_tai); } void DabMultiplexer::set_edi_config(const edi::configuration_t& new_edi_conf) { edi_conf = new_edi_conf; edi_sender = make_shared(edi_conf); } // Run a set of checks on the configuration void DabMultiplexer::prepare(bool require_tai_clock) { parse_ptree(m_pt, ensemble); rcs.enrol(this); rcs.enrol(ensemble.get()); prepare_subchannels(); prepare_services_components(); prepare_data_inputs(); if (not ensemble->validate_linkage_sets()) { etiLog.log(error, "Linkage set definition error"); throw MuxInitException(); } if (ensemble->subchannels.size() == 0) { etiLog.log(error, "can't multiplex no subchannel!"); throw MuxInitException(); } const auto last_subchannel = *(ensemble->subchannels.end() - 1); if (last_subchannel->startAddress + last_subchannel->getSizeCu() > 864) { etiLog.log(error, "Total size in CU exceeds 864"); printSubchannels(ensemble->subchannels); throw MuxInitException(); } const uint32_t tist_at_fct0_us = m_pt.get("general.tist_at_fct0", 0); currentFrame = m_time.init(tist_at_fct0_us); m_time.mnsc_increment_time = false; bool tist_enabled = m_pt.get("general.tist", false); m_time.tist_offset = m_pt.get("general.tist_offset", 0.0); auto tist_edi_time = m_time.get_time(); const auto timestamp = tist_edi_time.first; const auto edi_time = tist_edi_time.second; m_time.mnsc_time = edi_time; etiLog.log(info, "Startup CIF Count %i with timestamp: %d + %f", currentFrame, edi_time, (timestamp & 0xFFFFFF) / 16384000.0); // Try to load offset once m_tai_clock_required = (tist_enabled and edi_conf.enabled()) or require_tai_clock; if (m_tai_clock_required) { try { m_clock_tai.get_offset(); } catch (const std::runtime_error& e) { etiLog.level(error) << "Could not initialise TAI clock properly. " "TAI clock is required when TIST is enabled with an EDI output, " "or when a ZMQ output with metadata is used. " "Error: " << e.what(); throw; } } if (ensemble->reconfig_counter == dabEnsemble::RECONFIG_COUNTER_HASH) { vector data_to_hash; data_to_hash.push_back(ensemble->id); data_to_hash.push_back(ensemble->ecc); for (const auto& srv : ensemble->services) { data_to_hash.push_back(srv->id); data_to_hash.push_back(srv->ecc); } for (const auto& sc : ensemble->components) { data_to_hash.push_back(sc->serviceId); data_to_hash.push_back(sc->subchId); data_to_hash.push_back(sc->type); data_to_hash.push_back(sc->SCIdS); } for (const auto& sub : ensemble->subchannels) { data_to_hash.push_back(sub->id); data_to_hash.push_back(sub->startAddress); data_to_hash.push_back(sub->bitrate); uint32_t t = 0; switch (sub->type) { case subchannel_type_t::DABAudio : t = 1; break; case subchannel_type_t::DABPlusAudio: t = 2; break; case subchannel_type_t::DataDmb: t = 3; break; case subchannel_type_t::Packet: t= 4; break; } data_to_hash.push_back(t); data_to_hash.push_back(sub->protection.to_tpl()); } uint16_t crc_tmp = 0xFFFF; crc_tmp = crc16(crc_tmp, reinterpret_cast(data_to_hash.data()), data_to_hash.size() * sizeof(data_to_hash.data()) / sizeof(uint16_t)); ensemble->reconfig_counter = crc_tmp % 1024; etiLog.level(info) << "Calculated FIG 0/7 Count = " << ensemble->reconfig_counter; } } // Check and adjust subchannels void DabMultiplexer::prepare_subchannels() { set ids; for (auto subchannel : ensemble->subchannels) { if (ids.find(subchannel->id) != ids.end()) { etiLog.log(error, "Subchannel %u is set more than once!", subchannel->id); throw MuxInitException(); } ids.insert(subchannel->id); } } // Check and adjust services and components void DabMultiplexer::prepare_services_components() { set ids; dabProtection* protection = nullptr; vec_sp_component::iterator component; vec_sp_subchannel::iterator subchannel; for (auto service : ensemble->services) { if (ids.find(service->id) != ids.end()) { etiLog.log(error, "Service id 0x%x (%u) is set more than once!", service->id, service->id); throw MuxInitException(); } // Get first component of this service component = getComponent(ensemble->components, service->id); if (component == ensemble->components.end()) { etiLog.log(error, "Service id 0x%x (%u) includes no component!", service->id, service->id); throw MuxInitException(); } rcs.enrol(service.get()); // Adjust components type for DAB+ while (component != ensemble->components.end()) { subchannel = getSubchannel(ensemble->subchannels, (*component)->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "Error, service %u component " "links to the invalid subchannel %u", (*component)->serviceId, (*component)->subchId); throw MuxInitException(); } protection = &(*subchannel)->protection; switch ((*subchannel)->type) { case subchannel_type_t::DABPlusAudio: { if (protection->form == EEP) { /* According to ETSI TS 102 563 Clause 7.1 FIC signalling: * * "AAC audio services are signalled in the same way * as Layer II audio services with the exception * that the ASCTy carried in FIG 0/2 (see EN 300 * 401, clause 6.3.1) is set to the value * 1 1 1 1 1 1." */ (*component)->type = 0x3f; } } break; case subchannel_type_t::DABAudio: { if (protection->form == EEP) { /* ASCTy change to 0x0, because DAB mp2 is using */ (*component)->type = 0x0; } } break; case subchannel_type_t::DataDmb: case subchannel_type_t::Packet: break; default: etiLog.log(error, "Error, unknown subchannel type"); throw MuxInitException(); } component = getComponent(ensemble->components, service->id, component); } } // Init packet components SCId int cur_packetid = 0; for (auto component : ensemble->components) { subchannel = getSubchannel(ensemble->subchannels, component->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "Subchannel %i does not exist for component " "of service %i", component->subchId, component->serviceId); throw MuxInitException(); } if ((*subchannel)->type == subchannel_type_t::Packet) { component->packet.id = cur_packetid++; } rcs.enrol(component.get()); } } void DabMultiplexer::prepare_data_inputs() { dabProtection* protection = nullptr; // Prepare and check the data inputs for (auto subchannel = ensemble->subchannels.begin(); subchannel != ensemble->subchannels.end(); ++subchannel) { protection = &(*subchannel)->protection; if (subchannel == ensemble->subchannels.begin()) { (*subchannel)->startAddress = 0; } else { (*subchannel)->startAddress = (*(subchannel - 1))->startAddress + (*(subchannel - 1))->getSizeCu(); } (*subchannel)->input->open((*subchannel)->inputUri); // TODO Check errors int subch_bitrate = (*subchannel)->input->setBitrate( (*subchannel)->bitrate); if (subch_bitrate <= 0) { etiLog.level(error) << "can't set bitrate for source " << (*subchannel)->inputUri; throw MuxInitException(); } (*subchannel)->bitrate = subch_bitrate; /* Use EEP unless we find a UEP configuration * UEP is only used for MPEG audio, but some bitrates don't * have a UEP profile (EN 300 401 Clause 6.2.1). * For these bitrates, we must switch to EEP. * * AAC audio and data is already EEP */ if (protection->form == UEP) { protection->form = EEP; for (int i = 0; i < 64; i++) { if ( (*subchannel)->bitrate == BitRateTable[i] && protection->level == ProtectionLevelTable[i] ) { protection->form = UEP; protection->uep.tableIndex = i; } } } /* EEP B can only be used for subchannels with bitrates * multiple of 32kbit/s */ if ( protection->form == EEP && protection->eep.profile == EEP_B && subch_bitrate % 32 != 0 ) { etiLog.level(error) << "Cannot use EEP_B protection for subchannel " << (*subchannel)->inputUri << ": bitrate not multiple of 32kbit/s"; throw MuxInitException(); } } } /* Each call creates one ETI frame */ void DabMultiplexer::mux_frame(std::vector >& outputs) { unsigned char etiFrame[6144]; unsigned short index = 0; // FIC Length, DAB Mode I, II, IV -> FICL = 24, DAB Mode III -> FICL = 32 unsigned FICL = (ensemble->transmission_mode == TransmissionMode_e::TM_III ? 32 : 24); // For EDI, save ETI(LI) Management data into a TAG Item DETI edi::TagDETI edi_tagDETI; edi::TagStarPTR edi_tagStarPtr("DETI"); vector edi_est_tags; // The above Tag Items will be assembled into a TAG Packet edi::TagPacket edi_tagpacket(edi_conf.tagpacket_alignment); const bool tist_enabled = m_pt.get("general.tist", false); int tai_utc_offset = 0; if (tist_enabled and m_tai_clock_required) { try { tai_utc_offset = m_clock_tai.get_offset(); } catch (const std::runtime_error& e) { etiLog.level(error) << "Could not get UTC-TAI offset for EDI timestamp"; } } update_dab_time(); auto tist_edi_time = m_time.get_time(); const auto timestamp = tist_edi_time.first; const auto edi_time = tist_edi_time.second; // Initialise the ETI frame memset(etiFrame, 0, 6144); /********************************************************************** ********** Section SYNC of ETI(NI, G703) ************************* **********************************************************************/ // See ETS 300 799 Clause 6 eti_SYNC *etiSync = (eti_SYNC *) etiFrame; etiSync->ERR = edi_tagDETI.stat = 0xFF; // ETS 300 799, 5.2, no error //****** Field FSYNC *****// // See ETS 300 799, 6.2.1.2 if ((currentFrame & 1) == 0) { etiSync->FSYNC = ETI_FSYNC1; } else { etiSync->FSYNC = ETI_FSYNC1 ^ 0xffffff; } /********************************************************************** *********** Section LIDATA of ETI(NI, G703) ********************** **********************************************************************/ // See ETS 300 799 Figure 5 for a better overview of these fields. //****** Section FC ***************************************************/ // 4 octets, starts at offset 4 eti_FC *fc = (eti_FC *) &etiFrame[4]; //****** FCT ******// // Incremente for each frame, overflows at 249 fc->FCT = currentFrame % 250; edi_tagDETI.dlfc = currentFrame % 5000; //****** FICF ******// // Fast Information Channel Flag, 1 bit, =1 if FIC present fc->FICF = edi_tagDETI.ficf = 1; //****** NST ******// /* Number of audio of data sub-channels, 7 bits, 0-64. * In the 15-frame period immediately preceding a multiplex * re-configuration, NST can take the value 0 (see annex E). */ fc->NST = ensemble->subchannels.size(); //****** FP ******// /* Frame Phase, 3 bit counter, tells the COFDM generator * when to insert the TII. Is also used by the MNSC. */ fc->FP = edi_tagDETI.fp = currentFrame & 0x7; //****** MID ******// //Mode Identity, 2 bits, 01 ModeI, 10 modeII, 11 ModeIII, 00 ModeIV switch (ensemble->transmission_mode) { case TransmissionMode_e::TM_I: fc->MID = edi_tagDETI.mid = 1; break; case TransmissionMode_e::TM_II: fc->MID = edi_tagDETI.mid = 2; break; case TransmissionMode_e::TM_III: fc->MID = edi_tagDETI.mid = 3; break; case TransmissionMode_e::TM_IV: fc->MID = edi_tagDETI.mid = 0; break; } //****** FL ******// /* Frame Length, 11 bits, nb of words(4 bytes) in STC, EOH and MST * if NST=0, FL=1+FICL words, FICL=24 or 32 depending on the mode. * The FL is given in words (4 octets), see ETS 300 799 5.3.6 for details */ unsigned short FLtmp = 1 + FICL + (fc->NST); for (auto subchannel = ensemble->subchannels.begin(); subchannel != ensemble->subchannels.end(); ++subchannel) { // Add STLsbch FLtmp += (*subchannel)->getSizeWord(); } fc->setFrameLength(FLtmp); index = 8; /******* Section STC **************************************************/ // Stream Characterization, // number of channels * 4 octets = nb octets total int edi_stream_id = 1; for (auto subchannel : ensemble->subchannels) { eti_STC *sstc = (eti_STC *) & etiFrame[index]; sstc->SCID = subchannel->id; sstc->startAddress_high = subchannel->startAddress / 256; sstc->startAddress_low = subchannel->startAddress % 256; sstc->TPL = subchannel->protection.to_tpl(); // Sub-channel Stream Length, multiple of 64 bits sstc->STL_high = subchannel->getSizeDWord() / 256; sstc->STL_low = subchannel->getSizeDWord() % 256; edi::TagESTn tag_ESTn; tag_ESTn.id = edi_stream_id++; tag_ESTn.scid = subchannel->id; tag_ESTn.sad = subchannel->startAddress; tag_ESTn.tpl = sstc->TPL; tag_ESTn.rfa = 0; // two bits tag_ESTn.mst_length = subchannel->getSizeByte() / 8; tag_ESTn.mst_data = nullptr; assert(subchannel->getSizeByte() % 8 == 0); edi_est_tags.push_back(std::move(tag_ESTn)); index += 4; } /******* Section EOH **************************************************/ // End of Header 4 octets eti_EOH *eoh = (eti_EOH *) & etiFrame[index]; //MNSC Multiplex Network Signalling Channel, 2 octets eoh->MNSC = 0; struct tm time_tm; gmtime_r(&m_time.mnsc_time, &time_tm); switch (fc->FP & 0x3) { case 0: { eti_MNSC_TIME_0 *mnsc = (eti_MNSC_TIME_0 *) &eoh->MNSC; // Set fields according to ETS 300 799 -- 5.5.1 and A.2.2 mnsc->type = 0; mnsc->identifier = 0; mnsc->rfa = 0; } if (m_time.mnsc_increment_time) { m_time.mnsc_increment_time = false; m_time.mnsc_time += 1; } break; case 1: { eti_MNSC_TIME_1 *mnsc = (eti_MNSC_TIME_1 *) &eoh->MNSC; mnsc->setFromTime(&time_tm); mnsc->accuracy = 1; mnsc->sync_to_frame = 1; } break; case 2: { eti_MNSC_TIME_2 *mnsc = (eti_MNSC_TIME_2 *) &eoh->MNSC; mnsc->setFromTime(&time_tm); } break; case 3: { eti_MNSC_TIME_3 *mnsc = (eti_MNSC_TIME_3 *) &eoh->MNSC; mnsc->setFromTime(&time_tm); } break; } edi_tagDETI.mnsc = eoh->MNSC; // CRC Cyclic Redundancy Checksum of the FC, STC and MNSC, 2 octets unsigned short nbBytesCRC = 4 + ((fc->NST) * 4) + 2; unsigned short CRCtmp = 0xFFFF; CRCtmp = crc16(CRCtmp, &etiFrame[4], nbBytesCRC); CRCtmp ^= 0xffff; eoh->CRC = htons(CRCtmp); /******* Section MST **************************************************/ // Main Stream Data, if FICF=1 the first 96 or 128 bytes carry the FIC // (depending on mode) index = ((fc->NST) + 2 + 1) * 4; edi_tagDETI.fic_data = &etiFrame[index]; edi_tagDETI.fic_length = FICL * 4; // Insert all FIBs const bool fib3_present = (ensemble->transmission_mode == TransmissionMode_e::TM_III); index += fig_carousel.write_fibs(&etiFrame[index], currentFrame, fib3_present); /********************************************************************** ****** Input Data Reading ******************************************* **********************************************************************/ for (size_t i = 0; i < ensemble->subchannels.size(); i++) { auto& subchannel = ensemble->subchannels[i]; int sizeSubchannel = subchannel->getSizeByte(); // no need to check enableTist because we always increment the timestamp int result = subchannel->readFrame(&etiFrame[index], sizeSubchannel, edi_time, tai_utc_offset, timestamp); if (result < 0) { etiLog.log(info, "Subchannel %d read failed at ETI frame number: %d", subchannel->id, currentFrame); } // save pointer to Audio or Data Stream into correct TagESTn for EDI edi_est_tags[i].mst_data = &etiFrame[index]; index += sizeSubchannel; } index = (3 + fc->NST + FICL) * 4; for (auto subchannel : ensemble->subchannels) { index += subchannel->getSizeByte(); } /******* Section EOF **************************************************/ // End of Frame, 4 octets index = (FLtmp + 1 + 1) * 4; eti_EOF *eof = (eti_EOF *) & etiFrame[index]; // CRC of Main Stream data (MST), 16 bits index = ((fc->NST) + 2 + 1) * 4; // MST position unsigned short MSTsize = ((FLtmp) - 1 - (fc->NST)) * 4; // data size CRCtmp = 0xffff; CRCtmp = crc16(CRCtmp, &etiFrame[index], MSTsize); CRCtmp ^= 0xffff; eof->CRC = htons(CRCtmp); //RFU, Reserved for future use, 2 bytes, should be 0xFFFF eof->RFU = htons(0xFFFF); /******* Section TIST *************************************************/ // TimeStamps, 24 bits + 1 octet index = (FLtmp + 2 + 1) * 4; eti_TIST *tist = (eti_TIST *) & etiFrame[index]; bool enableTist = m_pt.get("general.tist", false); if (enableTist) { tist->TIST = htonl(timestamp) | 0xff; edi_tagDETI.tsta = timestamp & 0xffffff; } else { tist->TIST = htonl(0xffffff) | 0xff; edi_tagDETI.tsta = 0xffffff; } if (tist_enabled and m_tai_clock_required) { edi_tagDETI.set_edi_time(edi_time, tai_utc_offset); edi_tagDETI.atstf = true; for (auto output : outputs) { shared_ptr md_utco = make_shared(edi_tagDETI.utco); output->setMetadata(md_utco); shared_ptr md_edi_time = make_shared(edi_tagDETI.seconds); output->setMetadata(md_edi_time); shared_ptr md_dlfc = make_shared(currentFrame % 5000); output->setMetadata(md_dlfc); } } /* Coding of the TIST, according to ETS 300 799 Annex C Bit number b0(MSb)..b6 b7..b9 b10..b17 b18..b20 b21..b23(LSb) Bit number b23(MSb)..b17 b16..b14 b13..b6 b5..b3 b2..b0(LSb) as uint32_t Width 7 3 8 3 3 Timestamp level 1 2 3 4 5 Valid range [0..124], 127 [0..7] [0..255] [0..7] [0..7] Approximate 8 ms 1 ms 3,91 us 488 ns 61 ns time resolution */ m_time.increment_timestamp(); /********************************************************************** *********** Section FRPD ***************************************** **********************************************************************/ int frame_size = (FLtmp + 1 + 1 + 1 + 1) * 4; for (auto output : outputs) { auto out_zmq = std::dynamic_pointer_cast(output); if (out_zmq) { // The separator allows the receiver to associate the right // metadata with the right ETI frame, since the output gathers // four ETI frames into one message shared_ptr md_sep = make_shared(); out_zmq->setMetadata(md_sep); } } // Give the data to the outputs for (auto output : outputs) { if (output->Write(etiFrame, frame_size) == -1) { etiLog.level(error) << "Can't write to output " << output->get_info(); } } /********************************************************************** *********** Finalise and send EDI ******************************** **********************************************************************/ if (edi_sender and edi_conf.enabled()) { // put tags *ptr, DETI and all subchannels into one TagPacket edi_tagpacket.tag_items.push_back(&edi_tagStarPtr); edi_tagpacket.tag_items.push_back(&edi_tagDETI); for (auto& tag : edi_est_tags) { edi_tagpacket.tag_items.push_back(&tag); } edi_sender->write(edi_tagpacket); for (const auto& stat : edi_sender->get_tcp_server_stats()) { get_mgmt_server().update_edi_tcp_output_stat( stat.listen_port, stat.stats.size()); } } #if _DEBUG /********************************************************************** *********** Output a small message ********************************* **********************************************************************/ if (m_currentFrame % 100 == 0) { if (enableTist) { etiLog.log(info, "ETI frame number %i Timestamp: %d + %f", m_currentFrame, edi_time, (timestamp & 0xFFFFFF) / 16384000.0); } else { etiLog.log(info, "ETI frame number %i Time: %d, no TIST", m_currentFrame, edi_time); } } #endif currentFrame++; } void DabMultiplexer::print_info() { // Print settings before starting printEnsemble(ensemble); } void DabMultiplexer::set_parameter(const std::string& parameter, const std::string& value) { if (parameter == "frames") { stringstream ss; ss << "Parameter '" << parameter << "' of " << get_rc_name() << " is read-only"; throw ParameterError(ss.str()); } else if (parameter == "tist_offset") { m_time.tist_offset = std::stod(value); } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } /* Getting a parameter always returns a string. */ const std::string DabMultiplexer::get_parameter(const std::string& parameter) const { stringstream ss; if (parameter == "frames") { ss << currentFrame; } else if (parameter == "tist_offset") { ss << m_time.tist_offset; } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t DabMultiplexer::get_all_values() const { json::map_t map; map["frames"].v = currentFrame; map["tist_offset"].v = m_time.tist_offset; return map; } Opendigitalradio-ODR-DabMux-29c710c/src/DabMultiplexer.h000066400000000000000000000070471476627344300230330ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2025 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "dabOutput/dabOutput.h" #include "edioutput/Transport.h" #include "fig/FIGCarousel.h" #include "MuxElements.h" #include "RemoteControl.h" #include "ClockTAI.h" #include #include #include #include #include constexpr uint32_t ETI_FSYNC1 = 0x49C5F8; class MuxTime { private: uint32_t m_timestamp = 0; std::time_t m_edi_time = 0; public: std::pair get_time(); double tist_offset = 0; /* Pre v3 odr-dabmux did the MNSC calculation differently, * which works with the easydabv2. The rework in odr-dabmux, * deriving MNSC time from EDI time broke this. * * That's why we're now tracking MNSC time in separate variables, * to get the same behaviour back. * * I'm not aware of any devices using MNSC time besides the * easydab. ODR-DabMod now considers EDI seconds or ZMQ metadata. */ bool mnsc_increment_time = false; std::time_t mnsc_time = 0; /* Setup the time and return the initial currentFrame counter value */ uint64_t init(uint32_t tist_at_fct0_us); void increment_timestamp(); }; class DabMultiplexer : public RemoteControllable { public: DabMultiplexer(boost::property_tree::ptree pt); DabMultiplexer(const DabMultiplexer& other) = delete; DabMultiplexer& operator=(const DabMultiplexer& other) = delete; ~DabMultiplexer(); void prepare(bool require_tai_clock); uint64_t getCurrentFrame() const { return currentFrame; } void mux_frame(std::vector >& outputs); void print_info(void); void set_edi_config(const edi::configuration_t& new_edi_conf); /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; private: void prepare_subchannels(void); void prepare_services_components(void); void prepare_data_inputs(void); boost::property_tree::ptree m_pt; MuxTime m_time; uint64_t currentFrame = 0; edi::configuration_t edi_conf; std::shared_ptr edi_sender; std::shared_ptr ensemble; bool m_tai_clock_required = false; ClockTAI m_clock_tai; /* New FIG Carousel */ FIC::FIGCarousel fig_carousel; }; Opendigitalradio-ODR-DabMux-29c710c/src/DabMux.cpp000066400000000000000000000455271476627344300216320ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for basename #include #include #include #include #include #include #include #include #ifdef _WIN32 # include # include # include # include # include // For types... typedef u_char uint8_t; typedef WORD uint16_t; typedef DWORD32 uint32_t; # ifndef __MINGW32__ # include "xgetopt.h" # endif # define read _read # define snprintf _snprintf # define sleep(a) Sleep((a) * 1000) #else # include # include # include # include # include # include # include # include #endif #include #ifdef _WIN32 # pragma warning ( disable : 4103 ) # include "Eti.h" # pragma warning ( default : 4103 ) #else # include "Eti.h" #endif #include "input/Prbs.h" #include "input/Zmq.h" #include "dabOutput/dabOutput.h" #include "crc.h" #include "Socket.h" #include "PcDebug.h" #include "DabMux.h" #include "MuxElements.h" #include "utils.h" #include "ConfigParser.h" #include "ManagementServer.h" #include "Log.h" #include "RemoteControl.h" using namespace std; using boost::property_tree::ptree; volatile sig_atomic_t running = 1; /* We are not allowed to use etiLog in the signal handler, * because etiLog uses mutexes */ void signalHandler(int signum) { #ifdef _WIN32 fprintf(stderr, "\npid: %i\n", _getpid()); #else fprintf(stderr, "\npid: %i, ppid: %i\n", getpid(), getppid()); #endif #define SIG_MSG "Signal received: " switch (signum) { #ifndef _WIN32 case SIGHUP: fprintf(stderr, SIG_MSG "SIGHUP\n"); break; case SIGQUIT: fprintf(stderr, SIG_MSG "SIGQUIT\n"); break; case SIGPIPE: fprintf(stderr, SIG_MSG "SIGPIPE\n"); return; break; #endif case SIGINT: fprintf(stderr, SIG_MSG "SIGINT\n"); break; case SIGTERM: fprintf(stderr, SIG_MSG "SIGTERM\n"); fprintf(stderr, "Exiting software\n"); exit(0); break; default: fprintf(stderr, SIG_MSG "number %i\n", signum); } #ifndef _WIN32 killpg(0, SIGPIPE); #endif running = 0; } int main(int argc, char *argv[]) { // 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; } header_message(); struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = &signalHandler; const int sigs[] = {SIGHUP, SIGQUIT, SIGINT, SIGTERM}; for (int sig : sigs) { if (sigaction(sig, &sa, nullptr) == -1) { perror("sigaction"); return EXIT_FAILURE; } } #ifdef _WIN32 if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0) { etiLog.log(warn, "Can't increase priority: %s\n", strerror(errno)); } #else // Use the lowest real-time priority for this thread, and switch to real-time scheduling const int policy = SCHED_RR; sched_param sp; sp.sched_priority = sched_get_priority_min(policy); int thread_prio_ret = pthread_setschedparam(pthread_self(), policy, &sp); if (thread_prio_ret != 0) { etiLog.level(error) << "Could not set real-time priority for thread:" << thread_prio_ret; } #endif int returnCode = 0; ptree pt; std::vector > outputs; try { string conf_file = ""; if (argc == 2) { // Assume the only argument is a config file conf_file = argv[1]; if (conf_file == "-h") { printUsage(argv[0], stdout); throw MuxInitException("Nothing to do"); } } else if (argc > 1 && strncmp(argv[1], "-e", 2) == 0) { // use external config file try { if (argc != 3) { printUsage(argv[0], stderr); throw MuxInitException(); } conf_file = argv[2]; read_info(conf_file, pt); } catch (runtime_error &e) { throw MuxInitException(e.what()); } } if (conf_file.empty()) { printUsage(argv[0], stderr); throw MuxInitException("No configuration file specified"); } try { if (stringEndsWith(conf_file, ".json")) { read_json(conf_file, pt); } else { read_info(conf_file, pt); } } catch (runtime_error &e) { throw MuxInitException(e.what()); } /* Enable Logging to syslog conditionally */ if (pt.get("general.syslog", false)) { etiLog.register_backend(std::make_shared()); } const auto startupcheck = pt.get("general.startupcheck", ""); if (not startupcheck.empty()) { etiLog.level(info) << "Running startup check '" << startupcheck << "'"; int wstatus = system(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; } } int mgmtserverport = pt.get("general.managementport", pt.get("general.statsserverport", 0) ); /* Management: stats and config server */ get_mgmt_server().open(mgmtserverport); /************** READ REMOTE CONTROL PARAMETERS *************/ int telnetport = pt.get("remotecontrol.telnetport", 0); if (telnetport != 0) { auto rc = std::make_shared(telnetport); rcs.add_controller(rc); } auto zmqendpoint = pt.get("remotecontrol.zmqendpoint", ""); if (not zmqendpoint.empty()) { auto rc = std::make_shared(zmqendpoint); rcs.add_controller(rc); } DabMultiplexer mux(pt); etiLog.level(info) << PACKAGE_NAME << " " << #if defined(GITVERSION) GITVERSION << #else PACKAGE_VERSION << #endif " starting up"; edi::configuration_t edi_conf; /******************** READ OUTPUT PARAMETERS ***************/ set all_output_names; bool output_require_tai_clock = false; ptree pt_outputs = pt.get_child("outputs"); for (auto ptree_pair : pt_outputs) { string outputuid = ptree_pair.first; // check for uniqueness of the uid if (all_output_names.count(outputuid) == 0) { all_output_names.insert(outputuid); } else { stringstream ss; ss << "output with uid " << outputuid << " not unique!"; throw runtime_error(ss.str()); } if (outputuid == "edi") { ptree pt_edi = pt_outputs.get_child("edi"); for (auto pt_edi_dest : pt_edi.get_child("destinations")) { const auto proto = pt_edi_dest.second.get("protocol", "udp"); if (proto == "udp") { auto dest = make_shared(); dest->dest_addr = pt_edi_dest.second.get("destination"); dest->ttl = pt_edi_dest.second.get("ttl", 1); dest->source_addr = pt_edi_dest.second.get("source", ""); dest->source_port = pt_edi_dest.second.get("sourceport"); dest->dest_port = pt_edi_dest.second.get("port", 0); if (dest->dest_port == 0) { // Compatiblity: we have removed the transport and addressing in the // PFT layer, which removed the requirement that all outputs must share // the same destination port. If missing from the destination specification, // we read it from the parent block, where it was before. dest->dest_port = pt_edi.get("port"); } edi_conf.destinations.push_back(dest); } else if (proto == "tcp") { auto dest = make_shared(); dest->listen_port = pt_edi_dest.second.get("listenport"); dest->max_frames_queued = pt_edi_dest.second.get("max_frames_queued", 500); double preroll = pt_edi_dest.second.get("preroll-burst", 0.0); dest->tcp_server_preroll_buffers = ceil(preroll / 24e-3); edi_conf.destinations.push_back(dest); } else { throw runtime_error("Unknown EDI protocol " + proto); } } edi_conf.dump = pt_edi.get("dump", false); edi_conf.enable_pft = pt_edi.get("enable_pft", false); edi_conf.verbose = pt_edi.get("verbose", false); edi_conf.fec = pt_edi.get("fec", 3); edi_conf.chunk_len = pt_edi.get("chunk_len", 207); int spread_percent = pt_edi.get("packet_spread", 95); if (spread_percent < 0) { throw std::runtime_error("EDI output: negative packet_spread value is invalid."); } edi_conf.fragment_spreading_factor = (double)spread_percent / 100.0; if (edi_conf.fragment_spreading_factor > 30000) { throw std::runtime_error("EDI output: interleaving set for more than 30 seconds!"); } edi_conf.tagpacket_alignment = pt_edi.get("tagpacket_alignment", 8); mux.set_edi_config(edi_conf); } else if (outputuid == "zeromq") { #if defined(HAVE_OUTPUT_ZEROMQ) ptree pt_zeromq = pt_outputs.get_child("zeromq"); shared_ptr output; string endpoint = pt_zeromq.get("endpoint"); bool allow_metadata = pt_zeromq.get("allowmetadata"); output_require_tai_clock |= allow_metadata; size_t proto_pos = endpoint.find("://"); if (proto_pos == std::string::npos) { stringstream ss; ss << "zeromq output endpoint '" << endpoint << "' has incorrect format!"; throw runtime_error(ss.str()); } string proto = endpoint.substr(0, proto_pos); string location = endpoint.substr(proto_pos + 3); output = make_shared(proto, allow_metadata); if (not output) { etiLog.level(error) << "Unable to init zeromq output " << endpoint; return -1; } if (output->Open(location) == -1) { etiLog.level(error) << "Unable to open zeromq output " << endpoint; return -1; } outputs.push_back(output); #else throw runtime_error("ZeroMQ output not compiled in"); #endif } else { string uri = pt_outputs.get(outputuid); size_t proto_pos = uri.find("://"); if (proto_pos == std::string::npos) { stringstream ss; ss << "Output with uid " << outputuid << " no protocol defined!"; throw runtime_error(ss.str()); } string proto = uri.substr(0, proto_pos); string location = uri.substr(proto_pos + 3); std::shared_ptr output; if (0) { #if defined(HAVE_OUTPUT_FILE) } else if (proto == "file") { output = make_shared(); #endif // defined(HAVE_OUTPUT_FILE) #if defined(HAVE_OUTPUT_FIFO) } else if (proto == "fifo") { output = make_shared(); #endif // !defined(HAVE_OUTPUT_FIFO) #if defined(HAVE_OUTPUT_RAW) } else if (proto == "raw") { output = make_shared(); #endif // defined(HAVE_OUTPUT_RAW) #if defined(HAVE_OUTPUT_UDP) } else if (proto == "udp") { output = make_shared(); #endif // defined(HAVE_OUTPUT_UDP) #if defined(HAVE_OUTPUT_TCP) } else if (proto == "tcp") { output = make_shared(); #endif // defined(HAVE_OUTPUT_TCP) #if defined(HAVE_OUTPUT_SIMUL) } else if (proto == "simul") { output = make_shared(); #endif // defined(HAVE_OUTPUT_SIMUL) #if defined(HAVE_OUTPUT_ZEROMQ) /* The legacy configuration setting will not enable metadata, * to keep backward compatibility */ } else if (proto == "zmq+tcp") { output = make_shared("tcp", false); } else if (proto == "zmq+ipc") { output = make_shared("ipc", false); } else if (proto == "zmq+pgm") { output = make_shared("pgm", false); } else if (proto == "zmq+epgm") { output = make_shared("epgm", false); #endif // defined(HAVE_OUTPUT_ZEROMQ) } else { etiLog.level(error) << "Output protocol unknown: " << proto; throw MuxInitException(); } if (not output) { etiLog.level(error) << "Unable to init output " << uri; return -1; } if (output->Open(location) == -1) { etiLog.level(error) << "Unable to open output " << uri; return -1; } outputs.push_back(output); } } if (outputs.size() == 0) { etiLog.log(alert, "no output defined"); throw MuxInitException(); } mux.prepare(output_require_tai_clock); mux.print_info(); etiLog.log(info, "--- Output list ---"); printOutputs(outputs); if (edi_conf.enabled()) { edi_conf.print(); } size_t limit = pt.get("general.nbframes", 0); etiLog.level(info) << "Start loop"; /* Each iteration of the main loop creates one ETI frame */ size_t currentFrame; for (currentFrame = 0; running; currentFrame++) { mux.mux_frame(outputs); if (limit && currentFrame >= limit) { break; } /* Check every six seconds if the remote control is still working */ if (currentFrame % 250 == 249) { rcs.check_faults(); } /* Same for statistics server */ if (currentFrame % 10 == 0) { ManagementServer& mgmt_server = get_mgmt_server(); if (mgmt_server.fault_detected()) { etiLog.level(warn) << "Detected Management Server fault, restarting it"; mgmt_server.restart(); } mgmt_server.update_ptree(pt); } } if (limit) { etiLog.level(info) << "Max number of ETI frames reached: " << currentFrame; } } catch (const MuxInitException& except) { etiLog.level(error) << "Multiplex initialisation aborted: " << except.what(); returnCode = 1; } catch (const std::invalid_argument& except) { etiLog.level(error) << "Caught invalid argument : " << except.what(); returnCode = 1; } catch (const std::out_of_range& except) { etiLog.level(error) << "Caught out of range exception : " << except.what(); returnCode = 1; } catch (const std::logic_error& except) { etiLog.level(error) << "Caught logic error : " << except.what(); returnCode = 2; } catch (const std::runtime_error& except) { etiLog.level(error) << "Caught runtime error : " << except.what(); returnCode = 2; } etiLog.log(debug, "exiting...\n"); fflush(stderr); outputs.clear(); if (returnCode != 0) { etiLog.log(alert, "...aborting\n"); } else { etiLog.log(debug, "...done\n"); } return returnCode; } Opendigitalradio-ODR-DabMux-29c710c/src/DabMux.h000066400000000000000000000024771476627344300212740ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 This file declares several structures used in the multiplexer, and defines default values for some parameters. */ /* 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 . */ #pragma once #include #include #include #include "DabMultiplexer.h" #include "RemoteControl.h" #include "dabOutput/dabOutput.h" #include "input/inputs.h" #include "Eti.h" #include "MuxElements.h" #ifdef _WIN32 # include #else # include #endif Opendigitalradio-ODR-DabMux-29c710c/src/Dmb.cpp000066400000000000000000000207661476627344300211520ustar00rootroot00000000000000/* Copyright (C) 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 "Dmb.h" #include #ifdef _WIN32 # define bzero(a, b) memset((a), 0, (b)) #endif // _WIN32 DmbStats::DmbStats() { reset(); } void DmbStats::reset() { memset(this, 0, sizeof(*this)); } Dmb::Dmb(bool reverse) : interleaver(12, 17), encoder(204, 188) { buffer = nullptr; bufferSize = 0; setReverse(reverse); } Dmb::~Dmb() { if (buffer != nullptr) { delete[] buffer; } } void Dmb::setReverse(bool state) { reverse = state; reset(); interleaver.setReverse(state); encoder.setReverse(state); stats.reset(); } void Dmb::reset() { bufferOffset = 0; bufferLength = 0; inputOffset = 0; outputOffset = 0; // padding = 0; } #include int Dmb::encode( void* dataIn, unsigned long sizeIn, void* dataOut, unsigned long sizeOut ) { int ret = 0; char* input = reinterpret_cast(dataIn); char* output = reinterpret_cast(dataOut); if (bufferSize < sizeIn + 204) { if (buffer != nullptr) { delete[] buffer; } unsigned char* oldBuffer = buffer; bufferSize = sizeIn + 204; buffer = new unsigned char[bufferSize]; memcpy(buffer, oldBuffer, bufferLength); } if (reverse) { // fprintf(stderr, "*** Decode ***\n"); // fprintf(stderr, " sizeIn: %i\n", sizeIn); // fprintf(stderr, " inputOffset: %i\n", inputOffset); // fprintf(stderr, " bufferLength: %i\n", bufferLength); // fprintf(stderr, " bufferOffset: %i\n", bufferOffset); // fprintf(stderr, " outputOffset: %i\n", outputOffset); if (inputOffset == 0) { ++stats.dmbFrame; stats.rcvBytes += sizeIn; } memcpy(&buffer[bufferLength], &input[inputOffset], sizeIn - inputOffset); interleaver.encode(&buffer[bufferLength], sizeIn - inputOffset); bufferLength += sizeIn - inputOffset; inputOffset += sizeIn - inputOffset; while (bufferLength - bufferOffset >= 204) { if (buffer[bufferOffset] != 0x47) { int offset = sync(&buffer[bufferOffset], bufferLength - bufferOffset); if (offset == 0) { bufferLength = 0; bufferOffset = 0; } else { bufferOffset += offset; } } else { if (outputOffset + 188 <= sizeOut) { // If enough place int error = encoder.encode(&buffer[bufferOffset], 204); if (error != -1) { // if (true) { if (error > 0) { stats.corBytes += error; } memcpy(&output[outputOffset], &buffer[bufferOffset], 188); if (((buffer[bufferOffset + 1] & 0x1f) == 0x1f) && (buffer[bufferOffset + 2] == 0xff)) { stats.nulBytes += 188; } else { ret += 188; stats.sndBytes += 188; outputOffset += 188; } bufferOffset += 204; ++stats.mpgFrame; } else { // interleaver.reset(); // padding = 0; // bufferLength = 0; // inputOffset = 0; // stats.errBytes += 188; bufferOffset += 12; stats.errBytes += 12; ++stats.synFrame; int offset = sync (&buffer[bufferOffset], bufferLength - bufferOffset); if (offset == 0) { stats.errBytes += bufferLength - bufferOffset; bufferOffset = 0; bufferLength = 0; } else { stats.errBytes += offset; bufferOffset += offset; } } } else { outputOffset = 0; goto ENCODE_END; } } } memmove(buffer, &buffer[bufferOffset], bufferLength - bufferOffset); bufferLength -= bufferOffset; bufferOffset = 0; if (ret == 0) { inputOffset = 0; } else { outputOffset = 0; } // fprintf(stderr, " ret: %i\n\n", ret); } else { if (sizeIn == 0) { bzero(&output[outputOffset], sizeOut - outputOffset); interleaver.encode(&output[outputOffset], sizeOut - outputOffset); outputOffset = 0; ++stats.dmbFrame; stats.sndBytes += sizeOut; goto ENCODE_END; } if (bufferLength == 204) { // If buffer is not empty // If there's more data in buffer than place in output if (204 - bufferOffset > sizeOut - outputOffset) { memcpy(&output[outputOffset], &buffer[bufferOffset], sizeOut - outputOffset); bufferOffset += sizeOut - outputOffset; outputOffset = 0; ++stats.dmbFrame; stats.sndBytes += sizeOut; goto ENCODE_END; } memcpy(&output[outputOffset], &buffer[bufferOffset], 204 - bufferOffset); outputOffset += 204 - bufferOffset; bufferOffset = 0; bufferLength = 0; } while (true) { // If there's not enought input data if (sizeIn - inputOffset < 188 - bufferLength) { memcpy(&buffer[bufferLength], &input[inputOffset], sizeIn - inputOffset); bufferLength += sizeIn - inputOffset; ret = outputOffset; inputOffset = 0; goto ENCODE_END; } if (buffer[bufferLength] == 0x47) { ++stats.mpgFrame; } // fprintf(stderr, "++mpg: %i, sizeIn: %i, inputOffset: %i\n", // stats.mpgFrame, sizeIn, inputOffset); stats.rcvBytes += 188; memcpy(&buffer[bufferLength], &input[inputOffset], 188 - bufferLength); inputOffset += 188 - bufferLength; encoder.encode(buffer, 188); interleaver.encode(buffer, 204); bufferLength = 204; if (sizeOut - outputOffset < 204) { memcpy(&output[outputOffset], buffer, sizeOut - outputOffset); bufferOffset += sizeOut - outputOffset; outputOffset = 0; ++stats.dmbFrame; stats.sndBytes += sizeOut; goto ENCODE_END; } memcpy(&output[outputOffset], buffer, 204); outputOffset += 204; bufferLength = 0; } } ENCODE_END: return ret; } int Dmb::sync(void* dataIn, unsigned long sizeIn) { char* input = reinterpret_cast(dataIn); char* sync = input; unsigned long size = sizeIn; while (sync != nullptr) { sync = (char*)memchr(sync, 0x47, size); if (sync != nullptr) { int offset = sync - input; if (offset % 12 != 0) { offset = ((offset / 12) * 12) + 12; sync = &input[offset]; size = sizeIn - offset; } else { return offset; } } } return 0; } Opendigitalradio-ODR-DabMux-29c710c/src/Dmb.h000066400000000000000000000036231476627344300206100ustar00rootroot00000000000000/* Copyright (C) 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 _DMB #define _DMB #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "Interleaver.h" #include "ReedSolomon.h" class DmbStats { public: DmbStats(); void reset(); unsigned dmbFrame; unsigned mpgFrame; unsigned synFrame; unsigned rcvBytes; unsigned nulBytes; unsigned sndBytes; unsigned errBytes; unsigned corBytes; }; class Dmb { public: Dmb(bool reverse = false); Dmb(const Dmb& clone); virtual ~Dmb(); void setReverse(bool state); int encode(void* dataIn, unsigned long sizeIn, void* dataOut, unsigned long sizeOut); void reset(); DmbStats getStats() { return stats; }; private: int sync(void* dataIn, unsigned long sizeIn); Interleaver interleaver; ReedSolomon encoder; unsigned char* buffer; unsigned bufferSize; unsigned bufferOffset; // Encoded data written to output unsigned bufferLength; // Encoded data unsigned inputOffset; unsigned outputOffset; // unsigned padding; // Padding data written bool reverse; DmbStats stats; }; #endif // _DMB Opendigitalradio-ODR-DabMux-29c710c/src/Eti.cpp000066400000000000000000000050601476627344300211570ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Includes modifications 2012, Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #ifdef _WIN32 # pragma warning ( disable : 4103 ) # include "Eti.h" # pragma warning ( default : 4103 ) #else # include "Eti.h" # include #endif //definitions des structures des champs du ETI(NI, G703) unsigned short eti_FC::getFrameLength() { return (unsigned short)((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); } /* Helper functions for eti_MNSC_TIME_x which fill the time-relevant * fields for the MNSC */ void eti_MNSC_TIME_1::setFromTime(struct tm *time_tm) { second_unit = time_tm->tm_sec % 10; second_tens = time_tm->tm_sec / 10; minute_unit = time_tm->tm_min % 10; minute_tens = time_tm->tm_min / 10; } void eti_MNSC_TIME_2::setFromTime(struct tm *time_tm) { hour_unit = time_tm->tm_hour % 10; hour_tens = time_tm->tm_hour / 10; day_unit = time_tm->tm_mday % 10; day_tens = time_tm->tm_mday / 10; } void eti_MNSC_TIME_3::setFromTime(struct tm *time_tm) { month_unit = (time_tm->tm_mon + 1) % 10; month_tens = (time_tm->tm_mon + 1) / 10; // They didn't see the y2k bug coming, did they ? year_unit = (time_tm->tm_year - 100) % 10; year_tens = (time_tm->tm_year - 100) / 10; } Opendigitalradio-ODR-DabMux-29c710c/src/Eti.h000066400000000000000000000054451476627344300206330ustar00rootroot00000000000000/* Copyright (C) 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Includes modifications 2012, Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 ETI_ #define ETI_ #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 # include # define PACKED __attribute__ ((packed)) #endif //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; void setFromTime(struct tm *time_tm); } 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; void setFromTime(struct tm *time_tm); } 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; void setFromTime(struct tm *time_tm); } PACKED; #endif // ETI_ Opendigitalradio-ODR-DabMux-29c710c/src/Interleaver.cpp000066400000000000000000000064171476627344300227250ustar00rootroot00000000000000/* Copyright (C) 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 "Interleaver.h" #include #ifdef _WIN32 # define bzero(a, b) memset((a), 0, (b)) #endif // _WIN32 Interleaver::Interleaver(unsigned short I, unsigned short M, bool reverse) : I(I), M(M), N(I * M), memSize((this->N * I) - 1) { mem.resize(memSize); setReverse(reverse); } void Interleaver::setReverse(bool state) { reverse = state; reset(); } void Interleaver::reset() { j = 0; index = 0; memset(mem.data(), 0, memSize * sizeof(mem[0])); } void Interleaver::encode(void* data, unsigned long size) { encode(data, data, size); } void Interleaver::encode(const void* inData, void* outData, unsigned long size) { const char* input = reinterpret_cast(inData); char* output = reinterpret_cast(outData); unsigned long i; if (reverse) { for (i = 0; i < size; ++i) { mem[(index + ((I - 1 - j) * N)) % memSize] = *input; *output = mem[index]; ++input; ++output; if (++j == I) { j = 0; } if (++index == memSize) { index = 0; } } } else { for (i = 0; i < size; ++i) { if (j) { mem[(index + (j * N)) % memSize] = *input; *output = mem[index]; } else { *output = *input; } ++input; ++output; if (++j == I) { j = 0; } if (++index == memSize) { index = 0; } } } } unsigned long Interleaver::sync(void* data, unsigned long size, char padding) { char* input = reinterpret_cast(data); unsigned long index; if (reverse) { for (index = 0; index < size; ++index) { mem[(index + ((I - 1 - j) * N)) % memSize] = padding; *input = mem[index]; ++input; if (++index == memSize) { index = 0; } if (++j == I) { j = 0; break; } } } else { for (index = 0; index < size; ++index) { mem[(index + (j * N)) % memSize] = padding; *input = mem[index]; ++input; if (++index == memSize) { index = 0; } if (++j == I) { j = 0; break; } } } return index; } Opendigitalradio-ODR-DabMux-29c710c/src/Interleaver.h000066400000000000000000000030051476627344300223600ustar00rootroot00000000000000/* Copyright (C) 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 _INTERLEAVER #define _INTERLEAVER #ifdef HAVE_CONFIG_H # include "config.h" #endif #include class Interleaver { public: Interleaver(unsigned short I, unsigned short M, bool reverse = false); void setReverse(bool state); void encode(void* data, unsigned long size); void encode(const void* inData, void* outData, unsigned long size); unsigned long sync(void* data, unsigned long size, char padding = 0); void reset(); void flush(char padding = 0); private: unsigned short I; unsigned short M; unsigned long N; unsigned long j; unsigned long index; unsigned long memSize; std::vector mem; bool reverse; }; #endif // _INTERLEAVER Opendigitalradio-ODR-DabMux-29c710c/src/ManagementServer.cpp000066400000000000000000000464361476627344300237150ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2025 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org A TCP Socket server that serves state information and statistics for monitoring purposes, and also serves the internal configuration property tree. */ /* 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 #include #include #include #include #include #include #include "ManagementServer.h" #include "Log.h" using namespace std; #define MIN_FILL_BUFFER_UNDEF (-1) /* For silence detection, we count the number of occurrences the audio level * falls below a threshold. * * The counter is decreased for each frame that has good audio level. * * The counter saturates, and this value defines for how long the * input will be considered silent after a cut. * * If the count reaches a certain value, the input changes state * to Silence. */ #define INPUT_AUDIO_LEVEL_THRESHOLD -50 // dB #define INPUT_AUDIO_LEVEL_SILENCE_COUNT 100 // superframes (120ms) #define INPUT_AUDIO_LEVEL_COUNT_SATURATION 500 // superframes (120ms) /* An example of how the state changes work. * The timeout is set to expire in 30 minutes * at each under-/overrun. * * The glitch counter is increased by one for each glitch (can be a * saturating counter), and set to zero when the counter timeout expires. * * The state is then simply depending on the glitch counter value. * * Graphical example: state STREAMING | UNSTABLE | STREAMING xruns U U U glitch counter 0 1 2 3 0 reset timeout \ |\ |\ |\ \ | \ | \ | \ \ | \ | \ | \ \| \ | \| \ ` \| ` \ ` \ \ \ \ \ timeout expires ___________________\ <--30min--> */ /* The delay after which the glitch counter is reset */ static constexpr auto INPUT_COUNTER_RESET_TIME = std::chrono::minutes(30); /* How many glitches we tolerate in Streaming state before * we consider the input Unstable */ static constexpr int INPUT_UNSTABLE_THRESHOLD = 3; /* For how long the input buffers must be empty before we move an input to the * NoData state. */ static constexpr auto INPUT_NODATA_TIMEOUT = std::chrono::seconds(30); /* Keep 30s of min/max buffer fill information so that we can catch meaningful * values even if we have a slow poller */ static constexpr auto BUFFER_STATS_KEEP_DURATION = std::chrono::seconds(30); /* Audio level information changes faster than buffer levels, so it makes sense * to poll much faster. If we take the peak over too much data, we will hide * the interesting short-time fluctuations. At the same time, we want to have a * statistic that also catches the rare peaks, for slow pollers. */ static constexpr auto PEAK_STATS_SHORT_WINDOW = std::chrono::milliseconds(500); static constexpr auto PEAK_STATS_KEEP_DURATION = std::chrono::minutes(5); ManagementServer& get_mgmt_server() { static ManagementServer mgmt_server; return mgmt_server; /* Warning, do not use the mgmt_server in the destructor * of another global object: you don't know which one * gets destroyed first */ } void ManagementServer::register_input(InputStat* is) { unique_lock lock(m_statsmutex); std::string id(is->get_name()); if (m_input_stats.count(id) == 1) { etiLog.level(error) << "Double registration in MGMT Server with id '" << id << "'"; return; } m_input_stats[id] = is; } void ManagementServer::unregister_input(std::string id) { unique_lock lock(m_statsmutex); if (m_input_stats.count(id) == 1) { m_input_stats.erase(id); } } // outputs will never disappear, no need to have a "remove" logic void ManagementServer::update_edi_tcp_output_stat(uint16_t listen_port, size_t num_connections) { m_output_stats[listen_port] = num_connections; } bool ManagementServer::isInputRegistered(std::string& id) { unique_lock lock(m_statsmutex); if (m_input_stats.count(id) == 0) { etiLog.level(error) << "Management Server: id '" << id << "' does was not registered"; return false; } return true; } std::string ManagementServer::get_input_config_json() { unique_lock lock(m_statsmutex); std::ostringstream ss; ss << "{ \"config\" : [\n"; std::map::iterator iter; int i = 0; for (iter = m_input_stats.begin(); iter != m_input_stats.end(); ++iter, i++) { std::string id = iter->first; if (i > 0) { ss << ", "; } ss << " \"" << id << "\" "; } ss << "] }\n"; return ss.str(); } std::string ManagementServer::get_input_values_json() { unique_lock lock(m_statsmutex); std::ostringstream ss; ss << "{ \"values\" : {\n"; int i = 0; for (auto iter = m_input_stats.begin(); iter != m_input_stats.end(); ++iter, i++) { const std::string& id = iter->first; InputStat* stats = iter->second; if (i > 0) { ss << " ,\n"; } ss << " \"" << id << "\" : "; ss << stats->encodeValuesJSON(); } ss << "}\n}\n"; return ss.str(); } std::string ManagementServer::get_output_values_json() { unique_lock lock(m_statsmutex); std::ostringstream ss; ss << "{ \"output_values\" : {\n"; int i = 0; for (auto iter = m_output_stats.begin(); iter != m_output_stats.end(); ++iter, i++) { auto listen_port = iter->first; auto num_connections = iter->second; if (i > 0) { ss << " ,\n"; } ss << " \"edi_tcp_" << listen_port << "\" : { \"num_connections\": " << num_connections << "} "; } ss << "}\n}\n"; return ss.str(); } ManagementServer::ManagementServer() : m_zmq_context(), m_zmq_sock(m_zmq_context, ZMQ_REP), m_running(false), m_fault(false) { } ManagementServer::~ManagementServer() { m_running = false; if (m_thread.joinable()) { m_thread.join(); m_fault = false; } } void ManagementServer::open(int listenport) { m_listenport = listenport; if (m_listenport > 0) { m_thread = std::thread(&ManagementServer::serverThread, this); } } void ManagementServer::restart() { m_restarter_thread = thread(&ManagementServer::restart_thread, this, 0); } // This runs in a separate thread, because // it would take too long to be done in the main loop // thread. void ManagementServer::restart_thread(long) { m_running = false; if (m_thread.joinable()) { m_thread.join(); m_fault = false; } m_thread = thread(&ManagementServer::serverThread, this); } void ManagementServer::serverThread() { m_running = true; m_fault = false; try { std::string bind_addr = "tcp://127.0.0.1:" + to_string(m_listenport); m_zmq_sock.bind(bind_addr.c_str()); zmq::pollitem_t pollItems[] = { {m_zmq_sock, 0, ZMQ_POLLIN, 0} }; while (m_running) { zmq::poll(pollItems, 1, 1000); if (pollItems[0].revents & ZMQ_POLLIN) { zmq::message_t zmq_message; const auto r = m_zmq_sock.recv(zmq_message); if (r.has_value()) { handle_message(zmq_message); } } } } catch (const exception &e) { etiLog.level(error) << "Exception in ManagementServer: " << e.what(); } m_fault = true; } void ManagementServer::handle_message(zmq::message_t& zmq_message) { std::stringstream answer; std::string data((char*)zmq_message.data(), zmq_message.size()); try { if (data == "info") { answer << "{ " << "\"service\": \"" << PACKAGE_NAME << " " << #if defined(GITVERSION) GITVERSION << #else PACKAGE_VERSION << #endif " MGMT Server\", " << "\"version\": \"" << #if defined(GITVERSION) GITVERSION << #else PACKAGE_VERSION << #endif "\" " << "}\n"; } else if (data == "config") { answer << get_input_config_json(); } else if (data == "values") { answer << get_input_values_json(); } else if (data == "output_values") { answer << get_output_values_json(); } else if (data == "getptree") { unique_lock lock(m_configmutex); boost::property_tree::json_parser::write_json(answer, m_pt); } else { etiLog.level(warn) << "ManagementServer: Invalid request '" << data << "'"; answer << "Invalid command"; } std::string answerstr(answer.str()); zmq::const_buffer message(answerstr.data(), answerstr.size()); m_zmq_sock.send(message, zmq::send_flags::none); } catch (const std::exception& e) { etiLog.level(error) << "MGMT server caught exception: " << e.what(); } } void ManagementServer::update_ptree(const boost::property_tree::ptree& pt) { if (m_running) { unique_lock lock(m_configmutex); m_pt = pt; } } /************************************************/ InputStat::InputStat(const std::string& name) : m_name(name), m_time_last_event(std::chrono::steady_clock::now()) { } InputStat::~InputStat() { get_mgmt_server().unregister_input(m_name); } void InputStat::registerAtServer() { get_mgmt_server().register_input(this); } void InputStat::notifyBuffer(long bufsize) { unique_lock lock(m_mutex); using namespace std::chrono; const auto time_now = steady_clock::now(); m_buffer_fill_stats.push_front({time_now, bufsize}); prune_statistics(time_now); } void InputStat::notifyTimestampOffset(double offset) { unique_lock lock(m_mutex); m_last_tist_offset = offset; } void InputStat::notifyPeakLevels(int peak_left, int peak_right) { unique_lock lock(m_mutex); using namespace std::chrono; const auto time_now = steady_clock::now(); m_peak_stats.push_front({time_now, peak_left, peak_right}); prune_statistics(time_now); if (m_peak_stats.size() >= 2) { // Calculate the peak over the short window vector short_peaks; copy_if(m_peak_stats.begin(), m_peak_stats.end(), back_inserter(short_peaks), [&](const peak_stat_t& ps) { return ps.timestamp + PEAK_STATS_SHORT_WINDOW >= time_now; }); const auto& short_left_peak_max = max_element( short_peaks.begin(), short_peaks.end(), [](const peak_stat_t& lhs, const peak_stat_t& rhs) { return lhs.peak_left < rhs.peak_left; }); const auto& short_right_peak_max = max_element( short_peaks.begin(), short_peaks.end(), [](const peak_stat_t& lhs, const peak_stat_t& rhs) { return lhs.peak_right < rhs.peak_right; }); // Using the lower of the two channels allows us to detect if only one // channel is silent. const int lower_peak = min( short_left_peak_max->peak_left, short_right_peak_max->peak_right); // State const int16_t int16_max = std::numeric_limits::max(); int peak_dB = lower_peak ? round(20*log10((double)lower_peak / int16_max)) : -90; if (peak_dB < INPUT_AUDIO_LEVEL_THRESHOLD) { if (m_silence_counter < INPUT_AUDIO_LEVEL_COUNT_SATURATION) { m_silence_counter++; } } else { if (m_silence_counter > 0) { m_silence_counter--; } } } } void InputStat::notifyUnderrun(void) { unique_lock lock(m_mutex); // Statistics m_num_underruns++; // State m_time_last_event = std::chrono::steady_clock::now(); if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) { m_glitch_counter++; } else { // As we don't receive level notifications anymore, clear the // audio level information m_peak_stats.clear(); } } void InputStat::notifyOverrun(void) { unique_lock lock(m_mutex); // Statistics m_num_overruns++; // State m_time_last_event = std::chrono::steady_clock::now(); if (m_glitch_counter < INPUT_UNSTABLE_THRESHOLD) { m_glitch_counter++; } } void InputStat::notifyVersion(const std::string& version, uint32_t uptime_s) { unique_lock lock(m_mutex); m_version = version; m_uptime_s = uptime_s; } std::string InputStat::encodeValuesJSON() { std::ostringstream ss; const int16_t int16_max = std::numeric_limits::max(); unique_lock lock(m_mutex); int peak_left_short = 0; int peak_right_short = 0; int peak_left = 0; int peak_right = 0; if (not m_peak_stats.empty()) { peak_left = max_element(m_peak_stats.begin(), m_peak_stats.end(), [](const peak_stat_t& lhs, const peak_stat_t& rhs) { return lhs.peak_left < rhs.peak_left; })->peak_left; peak_right = max_element(m_peak_stats.begin(), m_peak_stats.end(), [](const peak_stat_t& lhs, const peak_stat_t& rhs) { return lhs.peak_right < rhs.peak_right; })->peak_right; if (m_peak_stats.size() > m_short_window_length) { peak_left_short = max_element(m_peak_stats.begin(), m_peak_stats.begin() + m_short_window_length, [](const peak_stat_t& lhs, const peak_stat_t& rhs) { return lhs.peak_left < rhs.peak_left; })->peak_left; peak_right_short = max_element(m_peak_stats.begin(), m_peak_stats.begin() + m_short_window_length, [](const peak_stat_t& lhs, const peak_stat_t& rhs) { return lhs.peak_right < rhs.peak_right; })->peak_right; } else { peak_left_short = peak_left; peak_right_short = peak_right; } } long min_fill_buffer = MIN_FILL_BUFFER_UNDEF; long max_fill_buffer = 0; if (not m_buffer_fill_stats.empty()) { const auto& buffer_min_max_fill = minmax_element( m_buffer_fill_stats.begin(), m_buffer_fill_stats.end(), [](const fill_stat_t& lhs, const fill_stat_t& rhs) { return lhs.bufsize < rhs.bufsize; }); min_fill_buffer = buffer_min_max_fill.first->bufsize; max_fill_buffer = buffer_min_max_fill.second->bufsize; } /* convert to dB */ auto to_dB = [](int p) { int dB = -90; if (p) { dB = round(20*log10((double)p / int16_max)); } return dB; }; auto version = m_version; size_t pos = 0; while ((pos = version.find("\"", pos)) != std::string::npos) { version.replace(pos, 1, "\\\""); pos++; } ss << "{ \"inputstat\" : {" "\"min_fill\": " << min_fill_buffer << ", " "\"max_fill\": " << max_fill_buffer << ", " "\"peak_left\": " << to_dB(peak_left_short) << ", " "\"peak_right\": " << to_dB(peak_right_short) << ", " "\"peak_left_slow\": " << to_dB(peak_left) << ", " "\"peak_right_slow\": " << to_dB(peak_right) << ", " "\"num_underruns\": " << m_num_underruns << ", " "\"num_overruns\": " << m_num_overruns << ", " "\"last_tist_offset\": " << m_last_tist_offset << ", " "\"version\": \"" << version << "\", " "\"uptime\": " << m_uptime_s << ", " ; ss << "\"state\": "; switch (determineState()) { case input_state_t::NoData: ss << "\"NoData (1)\""; break; case input_state_t::Unstable: ss << "\"Unstable (2)\""; break; case input_state_t::Silence: ss << "\"Silent (3)\""; break; case input_state_t::Streaming: ss << "\"Streaming (4)\""; break; } ss << " } }"; return ss.str(); } input_state_t InputStat::determineState() { const auto now = std::chrono::steady_clock::now(); prune_statistics(now); input_state_t state; /* if the last event was more that INPUT_COUNTER_RESET_TIME * ago, the timeout has expired. We can reset our * glitch counter. */ if (now - m_time_last_event > INPUT_COUNTER_RESET_TIME) { m_glitch_counter = 0; } // STATE CALCULATION /* If the buffer has been empty for more than * INPUT_NODATA_TIMEOUT, we go to the NoData state. * * Consider an empty deque to be NoData too. */ if (std::all_of( m_buffer_fill_stats.begin(), m_buffer_fill_stats.end(), [](const fill_stat_t& fs) { return fs.bufsize == 0; }) ) { state = input_state_t::NoData; } /* Otherwise, the state depends on the glitch counter */ else if (m_glitch_counter >= INPUT_UNSTABLE_THRESHOLD) { state = input_state_t::Unstable; } else { /* The input is streaming, check if the audio level is too low */ if (m_silence_counter > INPUT_AUDIO_LEVEL_SILENCE_COUNT) { state = input_state_t::Silence; } else { state = input_state_t::Streaming; } } return state; } void InputStat::prune_statistics(const std::chrono::time_point& time_now) { // Keep only stats whose timestamp are more recent than // BUFFER_STATS_KEEP_DURATION ago m_buffer_fill_stats.erase(remove_if( m_buffer_fill_stats.begin(), m_buffer_fill_stats.end(), [&](const fill_stat_t& fs) { return fs.timestamp + BUFFER_STATS_KEEP_DURATION < time_now; }), m_buffer_fill_stats.end()); // Keep only stats whose timestamp are more recent than // BUFFER_STATS_KEEP_DURATION ago m_peak_stats.erase(remove_if( m_peak_stats.begin(), m_peak_stats.end(), [&](const peak_stat_t& ps) { return ps.timestamp + PEAK_STATS_KEEP_DURATION < time_now; }), m_peak_stats.end()); } Opendigitalradio-ODR-DabMux-29c710c/src/ManagementServer.h000066400000000000000000000166541476627344300233610ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2025 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org A server that serves state information and statistics for monitoring purposes, and also serves the internal configuration property tree. This statistics server is very easy to integrate with munin http://munin-monitoring.org/ but is not specific to it. The responds in JSON, and accepts the commands: - config - values Inspired by the munin equivalent, returns the configuration and the statistics values for every exported stat. - getptree Returns the internal boost property_tree that contains the multiplexer configuration DB. The server is using REQ/REP ZeroMQ sockets. */ /* 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "Socket.h" #include "zmq.hpp" #include #include #include #include #include #include #include // Suppress an deprecation warning from boost #define BOOST_BIND_GLOBAL_PLACEHOLDERS #include #include #include /*** State handing ***/ /* An input can be in one of the following three states: */ enum class input_state_t { /* The input is waiting for data, all buffers are empty */ NoData, /* The input is running, but has seen many underruns or overruns recently */ Unstable, /* The input is running, but the audio level is too low, or has * been too low recently */ Silence, /* The input is running stable */ Streaming }; /* InputStat takes care of * - saving the statistics for graphing * - calculating the state of the input for monitoring */ class InputStat { public: InputStat(const std::string& name); InputStat(const InputStat& other) = delete; InputStat& operator=(const InputStat& other) = delete; ~InputStat(); void registerAtServer(void); std::string get_name(void) const { return m_name; } /* This function is called for every frame read by * the multiplexer */ void notifyBuffer(long bufsize); void notifyTimestampOffset(double offset); void notifyPeakLevels(int peak_left, int peak_right); void notifyUnderrun(void); void notifyOverrun(void); void notifyVersion(const std::string& version, uint32_t uptime_s); std::string encodeValuesJSON(void); input_state_t determineState(void); private: std::string m_name; // Remove all expired fill and peak stats void prune_statistics(const std::chrono::time_point& timestamp); /************ STATISTICS ***********/ // Keep track of buffer fill with timestamps, so that we // can calculate the correct state from it. struct fill_stat_t { std::chrono::time_point timestamp; long bufsize; }; std::deque m_buffer_fill_stats; // counter of number of overruns and underruns since startup uint32_t m_num_underruns = 0; uint32_t m_num_overruns = 0; // last measured timestamp offset double m_last_tist_offset = 0; // Peak audio levels (linear 16-bit PCM) for the two channels. // Keep a FIFO of values from the last minutes, apply // a short window to also see short-term fluctuations. struct peak_stat_t { std::chrono::time_point timestamp; int peak_left; int peak_right; }; std::deque m_peak_stats; size_t m_short_window_length = 0; std::string m_version; uint32_t m_uptime_s = 0; /************* STATE ***************/ /* Variables used for determining the input state */ int m_glitch_counter = 0; // saturating counter int m_silence_counter = 0; // saturating counter std::chrono::time_point m_time_last_event; // The mutex that has to be held during all notify and readout mutable std::mutex m_mutex; }; class ManagementServer { public: ManagementServer(); ManagementServer(const ManagementServer& other) = delete; ManagementServer& operator=(const ManagementServer& other) = delete; ~ManagementServer(); void open(int listenport); /* Un-/Register a statistics data source */ void register_input(InputStat* is); void unregister_input(std::string id); void update_edi_tcp_output_stat(uint16_t listen_port, size_t num_connections); /* Load a ptree given by the management server. * * Returns true if the ptree was updated */ bool retrieve_new_ptree(boost::property_tree::ptree& pt); /* Update the copy of the configuration property tree and notify the * update to the internal server thread. */ void update_ptree(const boost::property_tree::ptree& pt); bool fault_detected() const { return m_fault; } void restart(void); private: void restart_thread(long); /******* Server ******/ zmq::context_t m_zmq_context; zmq::socket_t m_zmq_sock; void serverThread(void); void handle_message(zmq::message_t& zmq_message); bool isInputRegistered(std::string& id); int m_listenport = 0; // serverThread runs in a separate thread std::atomic m_running; std::atomic m_fault; std::thread m_thread; std::thread m_restarter_thread; /******* Statistics Data ********/ std::map m_input_stats; // Holds information about EDI/TCP outputs std::map m_output_stats; /* Return a description of the configuration that will * allow to define what graphs to be created * * returns: a JSON encoded configuration */ std::string get_input_config_json(); /* Return the values for the statistics as defined in the configuration * * returns: JSON encoded statistics */ std::string get_input_values_json(); std::string get_output_values_json(); // mutex for accessing the map std::mutex m_statsmutex; /******** Configuration Data *******/ std::mutex m_configmutex; boost::property_tree::ptree m_pt; }; // If necessary construct the management server singleton and return // a reference to it ManagementServer& get_mgmt_server(); Opendigitalradio-ODR-DabMux-29c710c/src/MuxElements.cpp000066400000000000000000000604101476627344300227040ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 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 #include #include #include "MuxElements.h" #include "lib/charset/charset.h" #include #include const unsigned short Sub_Channel_SizeTable[64] = { 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 }; static CharsetConverter charset_converter; using namespace std; std::string AnnouncementCluster::tostring() const { stringstream ss; ss << "cluster id(" << (int)cluster_id; ss << ", flags 0x" << boost::format("%04x") % flags; ss << ", subchannel " << subchanneluid; { lock_guard lock(m_active_mutex); if (m_active) { ss << " active "; } if (m_deferred_start_time) { ss << " start pending"; } if (m_deferred_stop_time) { ss << " stop pending"; } } ss << " )"; return ss.str(); } bool AnnouncementCluster::is_active() { lock_guard lock(m_active_mutex); if (m_deferred_start_time) { const auto now = std::chrono::steady_clock::now(); if (*m_deferred_start_time <= now) { m_active = true; m_deferred_start_time.reset(); } } if (m_deferred_stop_time) { const auto now = std::chrono::steady_clock::now(); if (*m_deferred_stop_time <= now) { m_active = false; m_deferred_stop_time.reset(); } } return m_active; } void AnnouncementCluster::set_parameter(const string& parameter, const string& value) { if (parameter == "active") { stringstream ss; ss << value; lock_guard lock(m_active_mutex); ss >> m_active; } else if (parameter == "start_in") { stringstream ss; ss << value; int start_in_ms; ss >> start_in_ms; lock_guard lock(m_active_mutex); using namespace std::chrono; m_deferred_start_time = steady_clock::now() + milliseconds(start_in_ms); } else if (parameter == "stop_in") { stringstream ss; ss << value; int stop_in_ms; ss >> stop_in_ms; lock_guard lock(m_active_mutex); using namespace std::chrono; m_deferred_stop_time = steady_clock::now() + milliseconds(stop_in_ms); } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string AnnouncementCluster::get_parameter(const string& parameter) const { using namespace std::chrono; stringstream ss; if (parameter == "active") { lock_guard lock(m_active_mutex); ss << m_active; } else if (parameter == "start_in") { lock_guard lock(m_active_mutex); if (m_deferred_start_time) { const auto diff = *m_deferred_start_time - steady_clock::now(); ss << duration_cast(diff).count(); } else { ss << "Not set"; } } else if (parameter == "stop_in") { lock_guard lock(m_active_mutex); if (m_deferred_stop_time) { const auto diff = *m_deferred_stop_time - steady_clock::now(); ss << duration_cast(diff).count(); } else { ss << "Not set"; } } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t AnnouncementCluster::get_all_values() const { json::map_t map; lock_guard lock(m_active_mutex); map["active"].v = m_active; using namespace std::chrono; if (m_deferred_start_time) { const auto diff = *m_deferred_start_time - steady_clock::now(); map["start_in"].v = duration_cast(diff).count(); } else { map["start_in"].v = nullopt; } if (m_deferred_stop_time) { const auto diff = *m_deferred_stop_time - steady_clock::now(); map["stop_in"].v = duration_cast(diff).count(); } else { map["stop_in"].v = nullopt; } return map; } int DabLabel::setLabel(const std::string& label) { try { auto ebu_label = charset_converter.utf8_to_ebu(label, false); size_t len = ebu_label.length(); if (len > DABLABEL_LENGTH) { return -3; } m_fig1_label = ebu_label; } catch (const utf8::exception& e) { etiLog.level(warn) << "Failed to convert label '" << label << "' to EBU Charset"; size_t len = label.length(); if (len > DABLABEL_LENGTH) { return -3; } m_fig1_label = label; } m_fig1_flag = 0xFF00; // truncate the label to the eight first characters return 0; } int DabLabel::setLabel(const std::string& label, const std::string& short_label) { DabLabel newlabel; newlabel.m_fig1_flag = 0xFF00; try { newlabel.m_fig1_label = charset_converter.utf8_to_ebu(label, false); size_t len = newlabel.m_fig1_label.length(); if (len > DABLABEL_LENGTH) { return -3; } int flag = newlabel.setFIG1ShortLabel( charset_converter.utf8_to_ebu(short_label, false)); if (flag < 0) { return flag; } m_fig1_flag = flag & 0xFFFF; } catch (const utf8::exception& e) { etiLog.level(warn) << "Failed to convert label '" << label << " or short label '" << short_label << "' to EBU Charset"; // Use label as-is size_t len = label.length(); if (len > DABLABEL_LENGTH) { return -3; } newlabel.m_fig1_label = label; newlabel.m_fig1_flag = 0xFF00; int result = newlabel.setLabel(label); if (result < 0) { return result; } /* First check if we can actually create the short label */ int flag = newlabel.setFIG1ShortLabel(short_label); if (flag < 0) { return flag; } m_fig1_flag = flag & 0xFFFF; } // short label is valid. m_fig1_label = newlabel.m_fig1_label; return 0; } /* The label.flag is a 16bit mask that tells which label * characters are to be used for the short label * * From EN 300 401, clause 5.2.2.2.1: * * Character flag field: this 16-bit flag field shall indicate which of the * characters of the character field are to be * displayed in an abbreviated form of the label, as follows: * bi: (i = 0, ... ,15); * 0: not to be displayed in abbreviated label; * 1: to be displayed in abbreviated label. * NOTE: Not more than 8 of the bi may be set to "1". * * returns: the flag (16 bits) on success * -1 if the short_label is not a representable * -2 if the short_label is too long */ int DabLabel::setFIG1ShortLabel(const std::string& slabel) { const char* slab = slabel.c_str(); uint16_t flag = 0x0; /* Iterate over the label and set the bits in the flag * according to the characters in the slabel */ for (size_t i = 0; i < m_fig1_label.size(); ++i) { if (*slab == m_fig1_label[i]) { flag |= 0x8000 >> i; if (*(++slab) == '\0') { break; } } } /* If we have remaining characters in the slabel after * we went through the whole label, the short label * cannot be represented */ if (*slab != '\0') { return -1; } /* Count the number of bits in the flag */ int count = 0; for (int i = 0; i < 16; ++i) { if (flag & (1 << i)) { ++count; } } if (count > 8) { return -2; } return flag; } const string DabLabel::long_label() const { return charset_converter.ebu_to_utf8(m_fig1_label); } const string DabLabel::short_label() const { stringstream shortlabel; for (size_t i = 0; i < m_fig1_label.size(); ++i) { if (m_fig1_flag & 0x8000 >> i) { shortlabel << m_fig1_label[i]; } } return charset_converter.ebu_to_utf8(shortlabel.str()); } const string DabLabel::fig2_label() const { return m_fig2_label; } int DabLabel::setFIG2Label(const std::string& label) { m_fig2_label = label; return 0; } void DabLabel::setFIG2CharacterField(uint16_t character_field) { m_fig2_use_text_control = false; m_fig2_character_field = character_field; } void DabLabel::setFIG2TextControl(FIG2TextControl tc) { m_fig2_use_text_control = true; m_fig2_text_control = tc; } void DabLabel::writeLabel(uint8_t* buf) const { memset(buf, ' ', DABLABEL_LENGTH); if (m_fig1_label.size() <= DABLABEL_LENGTH) { std::copy(m_fig1_label.begin(), m_fig1_label.end(), (char*)buf); } } vec_sp_subchannel::iterator getSubchannel( vec_sp_subchannel& subchannels, int id) { return find_if( subchannels.begin(), subchannels.end(), [&](shared_ptr& s){ return s->id == id; } ); } vec_sp_component::iterator getComponent( vec_sp_component& components, uint32_t serviceId, vec_sp_component::iterator current) { if (current == components.end()) { current = components.begin(); } else { ++current; } while (current != components.end()) { if ((*current)->serviceId == serviceId) { return current; } ++current; } return components.end(); } uint8_t dabProtection::to_tpl() const { if (form == UEP) { return 0x10 | ProtectionLevelTable[uep.tableIndex]; } else if (form == EEP) { return 0x20 | (eep.GetOption() << 2) | level; } throw logic_error("Invalid protection form"); } vec_sp_component::iterator getComponent( vec_sp_component& components, uint32_t serviceId) { return getComponent(components, serviceId, components.end()); } vec_sp_service::iterator getService( std::shared_ptr component, vec_sp_service& services) { size_t i = 0; for (const auto& service : services) { if (service->id == component->serviceId) { return services.begin() + i; } i++; } throw std::runtime_error("Service not included in any component"); } bool DabComponent::isPacketComponent(vec_sp_subchannel& subchannels) const { if (subchId > 63) { etiLog.log(error, "You must define subchannel id in the " "packet component before defining packet "); return false; } const auto subch_it = getSubchannel(subchannels, subchId); if (subch_it == subchannels.cend()) { etiLog.log(error, "Invalid subchannel id in the packet component " "for defining packet "); return false; } return (*subch_it)->type == subchannel_type_t::Packet; } void DabComponent::set_parameter(const string& parameter, const string& value) { if (parameter == "label") { vector fields; boost::split(fields, value, boost::is_any_of(",")); if (fields.size() != 2) { throw ParameterError("Parameter 'label' must have format" " 'label,shortlabel'"); } int success = this->label.setLabel(fields[0], fields[1]); stringstream ss; switch (success) { case 0: break; case -1: ss << m_rc_name << " short label " << fields[1] << " is not subset of label '" << fields[0] << "'"; etiLog.level(warn) << ss.str(); throw ParameterError(ss.str()); case -2: ss << m_rc_name << " short label " << fields[1] << " is too long (max 8 characters)"; etiLog.level(warn) << ss.str(); throw ParameterError(ss.str()); case -3: ss << m_rc_name << " label " << fields[0] << " is too long (max 16 characters)"; etiLog.level(warn) << ss.str(); throw ParameterError(ss.str()); default: ss << m_rc_name << " short label definition: program error !"; etiLog.level(alert) << ss.str(); throw ParameterError(ss.str()); } } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string DabComponent::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "label") { ss << label.long_label() << "," << label.short_label(); } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t DabComponent::get_all_values() const { json::map_t map; // It's cleaner to have it separate in JSON, but we // need the comma separated variant for setting map["label"].v = label.long_label(); map["shortlabel"].v = label.short_label(); return map; } subchannel_type_t DabService::getType( const std::shared_ptr ensemble) const { auto component = getComponent(ensemble->components, id); if (component == ensemble->components.end()) { throw std::runtime_error("No component found for service"); } auto subchannel = getSubchannel(ensemble->subchannels, (*component)->subchId); if (subchannel == ensemble->subchannels.end()) { throw std::runtime_error("Could not find subchannel associated with service"); } return (*subchannel)->type; } bool DabService::isProgramme(const std::shared_ptr& ensemble) const { switch (getType(ensemble)) { case subchannel_type_t::DABAudio: case subchannel_type_t::DABPlusAudio: return true; case subchannel_type_t::DataDmb: case subchannel_type_t::Packet: return false; default: etiLog.log(error, "Error, unknown service type: %u", getType(ensemble)); throw logic_error("DabService::isProgramme unknown service type"); } } unsigned char DabService::nbComponent(const vec_sp_component& components) const { size_t count = std::count_if(components.begin(), components.end(), [&](const shared_ptr& c) { return c->serviceId == id;} ); if (count > 0xFF) { throw std::logic_error("Invalid number of components in service"); } return count; } void DabService::set_parameter(const string& parameter, const string& value) { if (parameter == "label") { vector fields; boost::split(fields, value, boost::is_any_of(",")); if (fields.size() != 2) { throw ParameterError("Parameter 'label' must have format" " 'label,shortlabel'"); } int success = this->label.setLabel(fields[0], fields[1]); stringstream ss; switch (success) { case 0: break; case -1: ss << "Short label " << fields[1] << " is not subset of label '" << fields[0] << "'"; throw ParameterError(ss.str()); case -2: ss << "Short label " << fields[1] << " is too long (max 8 characters)"; throw ParameterError(ss.str()); case -3: ss << "Label " << fields[0] << " is too long (max 16 characters)"; throw ParameterError(ss.str()); default: ss << m_rc_name << " short label definition: program error !"; etiLog.level(error) << ss.str(); throw ParameterError(ss.str()); } } else if (parameter == "pty") { int newpty = std::stoi(value); // International code, 5 bits if (newpty >= 0 and newpty < (1<<5)) { pty_settings.pty = newpty; } else { throw ParameterError("PTy value is out of bounds"); } } else if (parameter == "ptysd") { if (value == "static") { pty_settings.dynamic_no_static = false; } else if (value == "dynamic") { pty_settings.dynamic_no_static = true; } else { throw ParameterError("Invalid value for ptysd, use static or dynamic"); } } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string DabService::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "label") { ss << label.long_label() << "," << label.short_label(); } else if (parameter == "pty") { ss << (int)pty_settings.pty; } else if (parameter == "ptysd") { ss << (pty_settings.dynamic_no_static ? "dynamic" : "static"); } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t DabService::get_all_values() const { json::map_t map; map["label"].v = label.long_label(); map["shortlabel"].v = label.short_label(); map["pty"].v = (int)pty_settings.pty; map["ptysd"].v = (pty_settings.dynamic_no_static ? "dynamic" : "static"); return map; } void dabEnsemble::set_parameter(const string& parameter, const string& value) { if (parameter == "localtimeoffset") { if (value == "auto") { lto_auto = true; } else { lto_auto = false; int new_lto = atol(value.c_str()); if (new_lto < -24) { throw ParameterError("Desired local time offset too small." " Minimum -24" ); } else if (new_lto > 24) { throw ParameterError("Desired local time offset too large." " Maximum 24" ); } this->lto = new_lto; } } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string dabEnsemble::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "localtimeoffset") { if (this->lto_auto) { ss << "auto(" << this->lto << ")"; } else { ss << this->lto; } } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t dabEnsemble::get_all_values() const { json::map_t map; map["localtimeoffset_auto"].v = lto_auto; map["localtimeoffset"].v = lto; return map; } bool dabEnsemble::validate_linkage_sets() { for (const auto& ls : linkagesets) { const std::string keyserviceuid = ls->keyservice; if (keyserviceuid.empty()) { if (not ls->id_list.empty()) { etiLog.log(error, "Linkage set 0x%04x with empty key service " "should have an empty list.", ls->lsn); return false; } } else { const auto& keyservice = std::find_if( services.cbegin(), services.cend(), [&](const std::shared_ptr& srv) { return srv->uid == keyserviceuid; }); if (keyservice == services.end()) { etiLog.log(error, "Invalid key service %s in linkage set 0x%04x", keyserviceuid.c_str(), ls->lsn); return false; } // need to add key service to num_ids const size_t num_ids = 1 + ls->id_list.size(); if (num_ids > 0x0F) { etiLog.log(error, "Too many links for linkage set 0x%04x", ls->lsn); return false; } } } return true; } unsigned short DabSubchannel::getSizeCu() const { if (protection.form == UEP) { return Sub_Channel_SizeTable[protection.uep.tableIndex]; } else if (protection.form == EEP) { switch (protection.eep.profile) { case EEP_A: switch (protection.level) { case 0: return (bitrate * 12) >> 3; break; case 1: return bitrate; break; case 2: return (bitrate * 6) >> 3; break; case 3: return (bitrate >> 1); break; default: // Should not happens etiLog.log(error, "Bad protection level on subchannel"); return 0; } break; case EEP_B: switch (protection.level) { case 0: return (bitrate * 27) >> 5; break; case 1: return (bitrate * 21) >> 5; break; case 2: return (bitrate * 18) >> 5; break; case 3: return (bitrate * 15) >> 5; break; default: // Should not happens etiLog.log(error, "Bad protection level on subchannel"); return 0; } break; default: etiLog.log(error, "Invalid protection option"); return 0; } } return 0; } unsigned short DabSubchannel::getSizeByte() const { return bitrate * 3; } unsigned short DabSubchannel::getSizeWord() const { return (bitrate * 3) >> 2; } unsigned short DabSubchannel::getSizeDWord() const { return (bitrate * 3) >> 3; } size_t DabSubchannel::readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) { switch (input->getBufferManagement()) { case Inputs::BufferManagement::Prebuffering: return input->readFrame(buffer, size); case Inputs::BufferManagement::Timestamped: return input->readFrame(buffer, size, seconds, utco, tsta); } throw logic_error("Unhandled case"); } LinkageSet::LinkageSet(const std::string& name, uint16_t lsn, bool active, bool hard, bool international) : lsn(lsn), active(active), hard(hard), international(international), m_name(name) {} LinkageSet LinkageSet::filter_type(const ServiceLinkType type) { LinkageSet lsd(m_name, lsn, active, hard, international); lsd.active = active; lsd.keyservice = keyservice; for (const auto& link : id_list) { if (link.type == type) { lsd.id_list.push_back(link); } } return lsd; } Opendigitalradio-ODR-DabMux-29c710c/src/MuxElements.h000066400000000000000000000466001476627344300223560ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 file defines all data structures used in DabMux to represent and save ensemble data. */ /* 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 . */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "dabOutput/dabOutput.h" #include "input/inputs.h" #include "RemoteControl.h" #include "Eti.h" // Protection levels and bitrates for UEP. const unsigned char ProtectionLevelTable[64] = { 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 2, 1, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 2, 1, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 2, 1, 0, 4, 3, 1, 4, 2, 0 }; const unsigned short BitRateTable[64] = { 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 }; class MuxInitException : public std::exception { public: MuxInitException(const std::string m = "ODR-DabMux initialisation error") throw() : msg(m) {} ~MuxInitException(void) throw() {} const char* what() const throw() { return msg.c_str(); } private: std::string msg; }; enum class subchannel_type_t { DABAudio, DABPlusAudio, DataDmb, Packet }; /* Announcement switching flags, * define in ETSI TS 101 756 Table 15 */ const char * const annoucement_flags_names[] = { "Alarm", "Traffic", "Travel", "Warning", "News", "Weather", "Event", "Special", "ProgrammeInfo", "Sports", "Finance", "tba1", "tba2", "tba3", "tba4", "tba5" }; /* Class representing an announcement cluster for FIG 0/19 */ class AnnouncementCluster : public RemoteControllable { public: AnnouncementCluster(const std::string& name) : RemoteControllable(name) { RC_ADD_PARAMETER(active, "Signal this announcement [0 or 1]"); /* This supports deferred start/stop to allow the user * to compensate for audio encoding delay */ RC_ADD_PARAMETER(start_in, "Start signalling this announcement after a delay [ms]"); RC_ADD_PARAMETER(stop_in, "Stop signalling this announcement after a delay [ms]"); } uint8_t cluster_id = 0; uint16_t flags = 0; std::string subchanneluid; std::string tostring(void) const; /* Check if the activation/deactivation timeout occurred, * and return of if the Announcement is active */ bool is_active(void); private: mutable std::mutex m_active_mutex; bool m_active = false; std::optional< std::chrono::time_point< std::chrono::steady_clock> > m_deferred_start_time; std::optional< std::chrono::time_point< std::chrono::steady_clock> > m_deferred_stop_time; /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; }; struct dabOutput { dabOutput(const char* proto, const char* name) : outputProto(proto), outputName(name), output(NULL) { } // outputs are specified with outputProto://outputName // during config parsing std::string outputProto; std::string outputName; // later, the corresponding output is then created DabOutput* output; }; #define DABLABEL_LENGTH 16 struct FIG2TextControl { bool bidi_flag = false; bool base_direction_is_rtl = false; bool contextual_flag = false; bool combining_flag = false; }; class DabLabel { public: /* Set a new label and short label. If the label parses as valid UTF-8, it * will be converted to EBU Latin. If utf-8 decoding fails, the label * will be used as is. Characters that cannot be converted are replaced * by a space. * * returns: 0 on success * -1 if the short_label is not a representable * -2 if the short_label is too long * -3 if the text is too long */ int setLabel(const std::string& label, const std::string& short_label); /* Same as above, but sets the flag to 0xff00, truncating at 8 * characters. * * returns: 0 on success * -3 if the text is too long */ int setLabel(const std::string& label); /* Set the FIG2 label. label must be UTF-8. * * returns: 0 on success */ int setFIG2Label(const std::string& label); /* FIG2 can either be sent with a character field (old spec) * or with a text control (draftETSI TS 103 176 v2.2.1). * * Setting one clears the other, and selects the value of the * Rfu bit in the FIG 2 Data Field */ void setFIG2CharacterField(uint16_t character_field); void setFIG2TextControl(FIG2TextControl tc); // For FIG 1 /* Write the label to the 16-byte buffer given in buf * In the DAB standard, the label is 16 bytes long, and is * padded using spaces. */ void writeLabel(uint8_t* buf) const; bool has_fig1_label() const { return not m_fig1_label.empty(); } uint16_t flag() const { return m_fig1_flag; } const std::string long_label() const; const std::string short_label() const; // For FIG 2 bool has_fig2_label() const { return not m_fig2_label.empty(); } bool fig2_uses_text_control() const { return m_fig2_use_text_control; } FIG2TextControl fig2_text_control() const { return m_fig2_text_control; } uint16_t fig2_character_field() const { return m_fig2_character_field; } const std::string fig2_label() const; /* FIG 2 labels are either in UCS-2 or in UTF-8. Because there are upcoming * changes in the spec regarding the encoding of FIG2 (currently in draft * ETSI TS 103 176 v2.2.1), the character flag is not implemented yet. * * Both FIG 1 and FIG 2 labels can be sent, and receiver will show the one * they support. */ private: /* The m_fig1_label is not padded in any way. Stored in EBU Latin Charset */ std::string m_fig1_label; /* The flag field selects which label characters make * up the short label */ uint16_t m_fig1_flag = 0xFFFF; /* FIG2 label, stored in UTF-8. TODO: support UCS-2 */ std::string m_fig2_label; bool m_fig2_use_text_control = true; // Default to the new variant uint16_t m_fig2_character_field = 0xFF00; FIG2TextControl m_fig2_text_control; /* Checks and calculates the flag. slabel must be EBU Latin Charset */ int setFIG1ShortLabel(const std::string& slabel); }; class DabService; class DabComponent; class DabSubchannel; class LinkageSet; struct FrequencyInformation; struct ServiceOtherEnsembleInfo; using vec_sp_component = std::vector >; using vec_sp_service = std::vector >; using vec_sp_subchannel = std::vector >; enum class TransmissionMode_e { TM_I, TM_II, TM_III, TM_IV }; class dabEnsemble : public RemoteControllable { public: dabEnsemble() : RemoteControllable("ensemble") { RC_ADD_PARAMETER(localtimeoffset, "local time offset, 'auto' or -24 to +24 [half-hours]"); } /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; /* Check if the Linkage Sets are valid */ bool validate_linkage_sets(void); /* all fields are public, since this was a struct before */ uint16_t id = 0; uint8_t ecc = 0; DabLabel label; TransmissionMode_e transmission_mode = TransmissionMode_e::TM_I; /* Use the local time to calculate the lto */ bool lto_auto = true; int lto = 0; // local time offset in half-hours // range: -24 to +24 // 1 corresponds to the PTy used in RDS // 2 corresponds to program types used in north america int international_table = 1; // Modulo-1024 counter for FIG0/7. // Set to RECONFIG_COUNTER_DISABLED to disable FIG0/7. // Set to RECONFIG_COUNTER_HASH to calculate the counter value using a hash function // on relevant ensemble configuration parameters. static constexpr int RECONFIG_COUNTER_DISABLED = -1; static constexpr int RECONFIG_COUNTER_HASH = -2; int reconfig_counter = RECONFIG_COUNTER_DISABLED; // alarm flag is use for AL flag in FIG 0/0. // set to true if one announcement group with cluster ID 0xFF is available in multiplex file bool alarm_flag = 0; vec_sp_service services; vec_sp_component components; vec_sp_subchannel subchannels; std::vector > clusters; std::vector > linkagesets; std::vector frequency_information; std::vector service_other_ensemble; }; struct dabProtectionUEP { unsigned char tableIndex; }; enum dab_protection_eep_profile { EEP_A, EEP_B }; struct dabProtectionEEP { dab_protection_eep_profile profile; // option is a 3-bit field where 000 and 001 are used to // select EEP profile A and B. // Other values are for future use, see // EN 300 401 Clause 6.2.1 "Basic sub-channel organisation" uint8_t GetOption(void) const { return (this->profile == EEP_A) ? 0 : 1; } }; enum dab_protection_form_t { UEP, // implies FIG0/1 Short form EEP // Long form }; struct dabProtection { uint8_t level; dab_protection_form_t form; union { dabProtectionUEP uep; dabProtectionEEP eep; }; // According to ETSI EN 300 799 5.4.1.2 uint8_t to_tpl() const; }; class DabSubchannel { public: DabSubchannel(const std::string& uid) : uid(uid), protection() { } // Calculate subchannel size in number of CU unsigned short getSizeCu(void) const; // Calculate subchannel size in number of bytes unsigned short getSizeByte(void) const; // Calculate subchannel size in number of uint32_t unsigned short getSizeWord(void) const; // Calculate subchannel size in number of uint64_t unsigned short getSizeDWord(void) const; // Read from the input, using the correct buffer management size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); std::string uid; std::string inputUri; std::shared_ptr input; unsigned char id = 0; subchannel_type_t type = subchannel_type_t::DABAudio; bool packet_enhanced = false; // Enables additional FEC and FIG0/14 uint16_t startAddress = 0; uint16_t bitrate = 0; struct dabProtection protection; }; /* For FIG 0/13 (EN 300 401 Clause 6.3.6) */ struct userApplication { /* This 11-bit field identifies the user application that shall be used to decode the data in the channel identified * by SId and SCIdS. The interpretation of this field shall be as defined in ETSI TS 101 756 [3], table 16. */ uint16_t uaType = 0xFFFF; /* X-PAD Application Type: this 5-bit field shall specify the lowest numbered application type used to transport * this user application (see clause 7.4.3). * Also See EN 300 401 Table 11 "X-PAD Application types" */ uint8_t xpadAppType = 0; }; struct dabAudioComponent { std::vector uaTypes; }; struct dabDataComponent { }; struct dabPacketComponent { uint16_t id = 0; uint16_t address = 0; std::vector uaTypes; bool datagroup = false; }; class DabComponent : public RemoteControllable { public: DabComponent(std::string& uid) : RemoteControllable(uid), uid(uid) { RC_ADD_PARAMETER(label, "Label and shortlabel [label,short]"); } std::string uid; DabLabel label; uint32_t serviceId = 0; uint8_t subchId = 0; uint8_t type = 0; uint8_t SCIdS = 0; dabAudioComponent audio; dabDataComponent data; dabPacketComponent packet; bool isPacketComponent(vec_sp_subchannel& subchannels) const; /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; }; class DabService : public RemoteControllable { public: DabService(std::string& uid) : RemoteControllable(uid), uid(uid), label() { RC_ADD_PARAMETER(label, "Label and shortlabel [label,short]"); RC_ADD_PARAMETER(pty, "Programme Type"); RC_ADD_PARAMETER(ptysd, "PTy Static/Dynamic [static,dynamic]"); } std::string uid; uint32_t id = 0; /* Services with different ECC than the ensemble must be signalled in FIG0/9. * here, leave at 0 if they have the same as the ensemble */ uint8_t ecc = 0; struct pty_settings_t { uint8_t pty = 0; // 0 means disabled bool dynamic_no_static = false; }; pty_settings_t pty_settings; unsigned char language = 0; /* ASu (Announcement support) flags: this 16-bit flag field shall * specify the type(s) of announcements by which it is possible to * interrupt the reception of the service. The interpretation of this * field shall be as defined in TS 101 756, table 14. */ uint16_t ASu = 0; std::vector clusters; subchannel_type_t getType(const std::shared_ptr ensemble) const; bool isProgramme(const std::shared_ptr& ensemble) const; unsigned char nbComponent(const vec_sp_component& components) const; DabLabel label; /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; }; /* Represent an entry for FIG0/24 */ struct ServiceOtherEnsembleInfo { uint32_t service_id = 0; // Ensembles in which this service is also available std::vector other_ensembles; }; enum class ServiceLinkType {DAB, FM, DRM, AMSS}; /* Represent one link inside a linkage set */ struct ServiceLink { ServiceLinkType type; uint16_t id; uint8_t ecc; }; /* Represents a linkage set linkage sets according to * TS 103 176 Clause 5.2.3 "Linkage sets". This information will * be encoded in FIG 0/6. */ class LinkageSet { public: LinkageSet(const std::string& name, uint16_t lsn, bool active, bool hard, bool international); std::string get_name(void) const { return m_name; } std::list id_list; /* Linkage Set Number is a 12-bit number that identifies the linkage * set in a country (requires coordination between multiplex operators * in a country) */ uint16_t lsn; bool active; // TODO: Remote-controllable bool hard; bool international; std::string keyservice; // TODO replace by pointer to service /* Return a LinkageSet with id_list filtered to include * only those links of a given type */ LinkageSet filter_type(const ServiceLinkType type); private: std::string m_name; }; // FIG 0/21 enum class RangeModulation { dab_ensemble = 0, drm = 6, fm_with_rds = 8, amss = 14 }; struct FrequencyInfoDab { enum class ControlField_e { adjacent_no_mode = 0, adjacent_mode1 = 2, disjoint_no_mode = 1, disjoint_mode1 = 3}; struct ListElement { std::string uid; ControlField_e control_field; float frequency; }; uint16_t eid; std::vector frequencies; }; struct FrequencyInfoDrm { uint32_t drm_service_id; std::vector frequencies; }; struct FrequencyInfoFm { uint16_t pi_code; std::vector frequencies; }; struct FrequencyInfoAmss { uint32_t amss_service_id; std::vector frequencies; }; /* One FI database entry. DB key are oe, rm, and idfield, which is * inside the fi_XXX field */ struct FrequencyInformation { std::string uid; bool other_ensemble = false; // The latest draft spec does not specify the RegionId anymore, it's // now a reserved field. RangeModulation rm = RangeModulation::dab_ensemble; bool continuity = false; // Only one of those must contain information, which // must be consistent with RangeModulation FrequencyInfoDab fi_dab; FrequencyInfoDrm fi_drm; FrequencyInfoFm fi_fm; FrequencyInfoAmss fi_amss; }; vec_sp_subchannel::iterator getSubchannel( vec_sp_subchannel& subchannels, int id); vec_sp_component::iterator getComponent( vec_sp_component& components, uint32_t serviceId, vec_sp_component::iterator current); vec_sp_component::iterator getComponent( vec_sp_component& components, uint32_t serviceId); vec_sp_service::iterator getService( std::shared_ptr component, vec_sp_service& services); Opendigitalradio-ODR-DabMux-29c710c/src/PcDebug.h000066400000000000000000000070521476627344300214170ustar00rootroot00000000000000/* 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 PC_DEBUG_ #define PC_DEBUG_ #ifdef HAVE_CONFIG_H # include "config.h" #endif #define STRINGIFY(x) XSTRINGIFY(x) #define XSTRINGIFY(x) #x #include #define LOG stderr #if !defined(_WIN32) || defined(__MINGW32__) # ifndef PDEBUG # ifdef DEBUG # define PDEBUG(fmt, args...) fprintf (LOG, fmt , ## args) # else # define PDEBUG(fmt, args...) # endif # endif # ifdef DEBUG # define PDEBUG_VERBOSE(level, verbosity, fmt, args...) if (level <= verbosity) { fprintf(LOG, fmt, ## args); fflush(LOG); } # define PDEBUG0_VERBOSE(level, verbosity, txt) if (level <= verbosity) { fprintf(LOG, txt); fflush(LOG); } # define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) if (level <= verbosity) { fprintf(LOG, txt, arg0); fflush(LOG); } # define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) if (level <= verbosity) { fprintf(LOG, txt, arg0, arg1); fflush(LOG); } # define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) if (level <= verbosity) { fprintf(LOG, txt, arg0, arg1, arg2); fflush(LOG); } # define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) if (level <= verbosity) { fprintf(LOG, txt, arg0, arg1, arg2, arg3); fflush(LOG); } # else # define PDEBUG_VERBOSE(level, verbosity, fmt, args...) # define PDEBUG0_VERBOSE(level, verbosity, txt) # define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) # define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) # define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) # define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) # endif // DEBUG #else // _WIN32 # ifdef _DEBUG # define PDEBUG # define PDEBUG_VERBOSE # define PDEBUG0_VERBOSE(level, verbosity, txt) if (level <= verbosity) { fprintf(LOG, txt); fflush(LOG); } # define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) if (level <= verbosity) { fprintf(LOG, txt, arg0); fflush(LOG); } # define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) if (level <= verbosity) { fprintf(LOG, txt, arg0, arg1); fflush(LOG); } # define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) if (level <= verbosity) { fprintf(LOG, txt, arg0, arg1, arg2); fflush(LOG); } # define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) if (level <= verbosity) { fprintf(LOG, txt, arg0, arg1, arg2, arg3); fflush(LOG); } # else # define PDEBUG # define PDEBUG_VERBOSE # define PDEBUG0_VERBOSE(level, verbosity, txt) # define PDEBUG1_VERBOSE(level, verbosity, txt, arg0) # define PDEBUG2_VERBOSE(level, verbosity, txt, arg0, arg1) # define PDEBUG3_VERBOSE(level, verbosity, txt, arg0, arg1, arg2) # define PDEBUG4_VERBOSE(level, verbosity, txt, arg0, arg1, arg2, arg3) # endif #endif #endif // PC_DEBUG_ Opendigitalradio-ODR-DabMux-29c710c/src/PrbsGenerator.cpp000066400000000000000000000066521476627344300232230ustar00rootroot00000000000000/* Copyright (C) 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 "PrbsGenerator.h" #include #include /* * Generate a parity check for a 32-bit word. */ static uint32_t parity_check(uint32_t prbs_accum) { uint32_t mask = 1; uint32_t parity = 0; for (int i = 0; i < 32; ++i) { parity ^= ((prbs_accum & mask) != 0); mask <<= 1; } return parity; } void PrbsGenerator::setup(uint32_t polynomial) { this->polynomial = polynomial; this->accum = 0; gen_prbs_table(); gen_weight_table(); } uint8_t PrbsGenerator::step() { accum = update_prbs(); return accum & 0xff; } void PrbsGenerator::rewind() { while (accum < polynomial) { accum <<= 1; accum |= 1; } } void PrbsGenerator::gen_prbs_table() { for (int i = 0; i < 4; ++i) { for (int j = 0; j < 256; ++j) { uint32_t prbs_accum = ((uint32_t)j << (i * 8)); for (int k = 0; k < 8; ++k) { prbs_accum = (prbs_accum << 1) ^ parity_check(prbs_accum & polynomial); } prbs_table[i][j] = (prbs_accum & 0xff); } } } uint32_t PrbsGenerator::update_prbs() { uint8_t acc_lsb = 0; for (int i = 0; i < 4; ++i ) { acc_lsb ^= prbs_table [i] [ (accum >> (i * 8) ) & 0xff ]; } return (accum << 8) ^ ((uint32_t)acc_lsb); } void PrbsGenerator::gen_weight_table() { for (int i = 0; i < 256; ++i) { uint8_t mask = 1; uint8_t ones_count = 0; for (int j = 0; j < 8; ++j) { ones_count += ((i & mask) != 0); mask = mask << 1; } weight[i] = ones_count; } } size_t PrbsGenerator::error_count( uint8_t *rx_data, size_t rx_data_length) { uint32_t error_count = 0; uint32_t prbs_accum = 0; /* seed the PRBS accumulator */ for (int i = 0; i < 4; ++i) { prbs_accum = (prbs_accum << 8) ^ (rx_data[i] ^ polarity_mask); } /* check the received data */ for (size_t i = 0; i < rx_data_length; ++i) { uint8_t error_pattern = (prbs_accum >> 24) ^ (rx_data[i] ^ polarity_mask); if (error_pattern != 0) { error_count += weight[error_pattern]; } prbs_accum = update_prbs(); } return error_count; } void PrbsGenerator::gen_sequence( uint8_t *tx_data, size_t tx_data_length, uint32_t polynomial) { uint32_t prbs_accum = 0; while (prbs_accum < polynomial) { prbs_accum <<= 1; prbs_accum |= 1; } for (size_t i = 0; i < tx_data_length; i++) { prbs_accum = update_prbs(); tx_data[i] = (uint8_t)(prbs_accum & 0xff); } } Opendigitalradio-ODR-DabMux-29c710c/src/PrbsGenerator.h000066400000000000000000000040421476627344300226570ustar00rootroot00000000000000/* Copyright (C) 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 . */ #pragma once #include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif class PrbsGenerator { public: void setup(uint32_t polynomial); uint8_t step(void); void rewind(void); private: /* Generate a table of matrix products to update a 32-bit PRBS * generator. */ void gen_prbs_table(void); /* Update a 32-bit PRBS generator eight bits at a time. */ uint32_t update_prbs(void); /* Generate the weight table. */ void gen_weight_table(void); /* Count the number of errors in a block of received data. */ size_t error_count( uint8_t *rx_data, size_t rx_data_length); void gen_sequence( uint8_t *tx_data, size_t tx_data_length, uint32_t polynomial); // table of matrix products used to update a 32-bit PRBS generator uint32_t prbs_table [4] [256]; // table of weights for 8-bit bytes uint8_t weight[256]; // PRBS polynomial generator uint32_t polynomial; // PRBS generator polarity mask uint8_t polarity_mask; // PRBS accumulator uint32_t accum; }; Opendigitalradio-ODR-DabMux-29c710c/src/TestStatsServer.cpp000066400000000000000000000014041476627344300235610ustar00rootroot00000000000000#include #include "StatsServer.h" #define NUMOF(x) (sizeof(x) / sizeof(*x)) int main(int argc, char **argv) { int stats_example[] = {25, 24, 22, 21, 56, 56, 54, 53, 51, 45, 42, 39, 34, 30, 24, 15, 8, 4, 1, 0}; StatsServer serv(2720); serv.registerInput("foo"); serv.registerInput("bar"); while (true) { for (int i = 0; i < NUMOF(stats_example); i++) { usleep(400000); serv.notifyBuffer("foo", stats_example[i]); fprintf(stderr, "give %d\n", stats_example[i]); if (stats_example[i] == 0) { serv.notifyUnderrun("foo"); } if (stats_example[i] == 56) { serv.notifyOverrun("foo"); } } } return 0; } Opendigitalradio-ODR-DabMux-29c710c/src/dabInputDmbFile.cpp000066400000000000000000000105111476627344300234240ustar00rootroot00000000000000/* Copyright (C) 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 "dabInputDmbFile.h" #include "dabInputFifo.h" #include "Dmb.h" #include "UdpSocket.h" #include #include #ifdef HAVE_FORMAT_DMB struct dabInputDmbFileData { FILE* file; Dmb* dmb; dabInputFifoStats stats; unsigned char buffer[188]; unsigned bufferLength; }; struct dabInputOperations dabInputDmbFileOperations = { dabInputDmbFileInit, dabInputDmbFileOpen, dabInputSetbuf, nullptr, nullptr, nullptr, dabInputDmbFileRead, dabInputSetbitrate, dabInputDmbFileClose, dabInputDmbFileClean, nullptr }; int dabInputDmbFileInit(void** args) { dabInputDmbFileData* input = new dabInputDmbFileData; memset(&input->stats, 0, sizeof(input->stats)); input->stats.id = dabInputFifoData::nb++; input->file = nullptr; input->bufferLength = 0; input->dmb = new Dmb(); *args = input; return 0; } int dabInputDmbFileOpen(void* args, const char* inputName) { int returnCode = 0; dabInputDmbFileData* input = (dabInputDmbFileData*)args; input->file = fopen(inputName, "r"); if (input->file == nullptr) { perror(inputName); returnCode = -1; } return returnCode;; } int dabInputDmbFileRead(dabInputOperations* ops, void* args, void* buffer, int size) { int nbBytes = 0; dabInputDmbFileData* input = (dabInputDmbFileData*)args; dabInputFifoStats* stats = (dabInputFifoStats*)&input->stats; input->stats.frameRecords[input->stats.frameCount].curSize = 0; input->stats.frameRecords[input->stats.frameCount].maxSize = size; if (input->bufferLength == 0) { input->bufferLength = fread(input->buffer, 188, 1, input->file); } /* while ((nbBytes = writePacket(input->packet->getData(), input->packet->getLength(), buffer, size, input->info)) != 0) { input->stats.frameRecords[input->stats.frameCount].curSize = nbBytes; input->socket->receive(*input->packet); }*/ while ((nbBytes = input->dmb->encode(input->buffer, input->bufferLength * 188, buffer, size)) != 0) { input->stats.frameRecords[input->stats.frameCount].curSize = nbBytes; input->bufferLength = fread(input->buffer, 188, 1, input->file); if (input->bufferLength == 0) { etiLog.log(info, "reach end of file -> rewinding\n"); if (fseek(input->file, 0, SEEK_SET) == 0) { input->bufferLength = fread(input->buffer, 188, 1, input->file); } } //++mpgFrameNb; } //++dmbFrameNb; if (input->bufferLength != 0) { input->stats.frameRecords[input->stats.frameCount].curSize = size; } if (++stats->frameCount == NB_RECORDS) { etiLog.log(info, "Data subchannel usage: (%i)", stats->id); for (int i = 0; i < stats->frameCount; ++i) { etiLog.log(info, " %i/%i", stats->frameRecords[i].curSize, stats->frameRecords[i].maxSize); } etiLog.log(info, "\n"); stats->frameCount = 0; } return size; } int dabInputDmbFileClose(void* args) { dabInputDmbFileData* input = (dabInputDmbFileData*)args; if (input->file != nullptr) { if (fclose(input->file)) { perror(""); return -1; } } return 0; } int dabInputDmbFileClean(void** args) { dabInputDmbFileData* input = (dabInputDmbFileData*)(*args); dabInputDmbFileClose(args); delete input->dmb; delete input; return 0; } #endif //HAVE_FORMAT_DMB Opendigitalradio-ODR-DabMux-29c710c/src/dabInputDmbFile.h000066400000000000000000000025161476627344300230770ustar00rootroot00000000000000/* Copyright (C) 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 DAB_INPUT_DMB_FILE_H #define DAB_INPUT_DMB_FILE_H #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "dabInput.h" #ifdef HAVE_FORMAT_DMB # ifdef HAVE_INPUT_FILE extern struct dabInputOperations dabInputDmbFileOperations; int dabInputDmbFileInit(void** args); int dabInputDmbFileOpen(void* args, const char* inputName); int dabInputDmbFileRead(dabInputOperations* ops, void* args, void* buffer, int size); int dabInputDmbFileClose(void* args); int dabInputDmbFileClean(void** args); # endif #endif #endif // DAB_INPUT_DMB_FILE_H Opendigitalradio-ODR-DabMux-29c710c/src/dabInputDmbUdp.cpp000066400000000000000000000125751476627344300233110ustar00rootroot00000000000000/* Copyright (C) 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 "dabInputDmbUdp.h" #include "dabInputFifo.h" #include "Dmb.h" #include "UdpSocket.h" #include #include #ifdef HAVE_FORMAT_DMB # ifdef HAVE_INPUT_UDP struct dabInputDmbUdpData { UdpSocket* socket; UdpPacket* packet; Dmb* dmb; dabInputFifoStats stats; }; struct dabInputOperations dabInputDmbUdpOperations = { dabInputDmbUdpInit, dabInputDmbUdpOpen, dabInputSetbuf, NULL, NULL, NULL, dabInputDmbUdpRead, dabInputSetbitrate, dabInputDmbUdpClose, dabInputDmbUdpClean, NULL }; int dabInputDmbUdpInit(void** args) { dabInputDmbUdpData* input = new dabInputDmbUdpData; memset(&input->stats, 0, sizeof(input->stats)); input->stats.id = dabInputFifoData::nb++; input->socket = new UdpSocket(); input->packet = new UdpPacket(2048); input->dmb = new Dmb(); *args = input; return 0; } int dabInputDmbUdpOpen(void* args, const char* inputName) { int returnCode = 0; char* address; char* ptr; long port; dabInputDmbUdpData* input = (dabInputDmbUdpData*)args; // Skip the udp:// part if it is present if (strncmp(inputName, "udp://", 6) == 0) { address = strdup(inputName + 6); } else { address = strdup(inputName); } ptr = strchr(address, ':'); if (ptr == NULL) { etiLog.log(error, "\"%s\" is an invalid format for udp address: " "should be [address]:port - > aborting\n", address); returnCode = -1; goto dmbudpopen_ptr_null_out; } *(ptr++) = 0; port = strtol(ptr, (char **)NULL, 10); if ((port == LONG_MIN) || (port == LONG_MAX)) { etiLog.log(error, "can't convert port number in udp address %s\n", address); returnCode = -1; } if (port == 0) { etiLog.log(error, "can't use port number 0 in udp address\n"); returnCode = -1; } if (input->socket->create(port) == -1) { etiLog.log(error, "can't set port %i on Dmb input (%s: %s)\n", port, inetErrDesc, inetErrMsg); returnCode = -1; } if (*address != 0) { if (input->socket->joinGroup(address) == -1) { etiLog.log(error, "can't join multicast group %s (%s: %s)\n", address, inetErrDesc, inetErrMsg); returnCode = -1; } } if (input->socket->setBlocking(false) == -1) { etiLog.log(error, "can't set Dmb input socket in blocking mode " "(%s: %s)\n", inetErrDesc, inetErrMsg); returnCode = -1; } dmbudpopen_ptr_null_out: free(address); etiLog.log(debug, "check return code of create\n"); return returnCode;; } int dabInputDmbUdpRead(dabInputOperations* ops, void* args, void* buffer, int size) { int nbBytes = 0; dabInputDmbUdpData* input = (dabInputDmbUdpData*)args; dabInputFifoStats* stats = (dabInputFifoStats*)&input->stats; input->stats.frameRecords[input->stats.frameCount].curSize = 0; input->stats.frameRecords[input->stats.frameCount].maxSize = size; if (input->packet->getLength() == 0) { input->socket->receive(*input->packet); } /* while ((nbBytes = writePacket(input->packet->getData(), input->packet->getLength(), buffer, size, input->info)) != 0) { input->stats.frameRecords[input->stats.frameCount].curSize = nbBytes; input->socket->receive(*input->packet); }*/ while ((nbBytes = input->dmb->encode(input->packet->getData(), input->packet->getLength(), buffer, size)) != 0) { input->stats.frameRecords[input->stats.frameCount].curSize = nbBytes; input->socket->receive(*input->packet); //++mpgFrameNb; } //++dmbFrameNb; if (input->packet->getLength() != 0) { input->stats.frameRecords[input->stats.frameCount].curSize = size; } if (++stats->frameCount == NB_RECORDS) { etiLog.log(info, "Data subchannel usage: (%i)", stats->id); for (int i = 0; i < stats->frameCount; ++i) { etiLog.log(info, " %i/%i", stats->frameRecords[i].curSize, stats->frameRecords[i].maxSize); } etiLog.log(info, "\n"); stats->frameCount = 0; } return size; } int dabInputDmbUdpClose(void* args) { return 0; } int dabInputDmbUdpClean(void** args) { dabInputDmbUdpData* input = (dabInputDmbUdpData*)(*args); delete input->socket; delete input->packet; delete input->dmb; delete input; return 0; } # endif #endif Opendigitalradio-ODR-DabMux-29c710c/src/dabInputDmbUdp.h000066400000000000000000000025041476627344300227450ustar00rootroot00000000000000/* Copyright (C) 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 DAB_INPUT_DMB_UDP_H #define DAB_INPUT_DMB_UDP_H #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "dabInput.h" #ifdef HAVE_FORMAT_DMB # ifdef HAVE_INPUT_UDP extern struct dabInputOperations dabInputDmbUdpOperations; int dabInputDmbUdpInit(void** args); int dabInputDmbUdpOpen(void* args, const char* inputName); int dabInputDmbUdpRead(dabInputOperations* ops, void* args, void* buffer, int size); int dabInputDmbUdpClose(void* args); int dabInputDmbUdpClean(void** args); # endif #endif #endif // DAB_INPUT_DMB_UDP_H Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/000077500000000000000000000000001476627344300217005ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutput.h000066400000000000000000000167231476627344300240310ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org An object-oriented version of the output channels. */ /* 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 . */ #pragma once #include "Socket.h" #include "Log.h" #include "string.h" #include #include #include #include #include #include #include #ifndef O_BINARY # define O_BINARY 0 #endif // O_BINARY #ifdef HAVE_OUTPUT_ZEROMQ # include "zmq.hpp" #endif #include "dabOutput/metadata.h" // Abstract base class for all outputs class DabOutput { public: virtual int Open(const char* name) = 0; int Open(std::string name) { return Open(name.c_str()); } // Return -1 on failure virtual int Write(void* buffer, int size) = 0; virtual int Close() = 0; virtual ~DabOutput() {} virtual std::string get_info() const = 0; virtual void setMetadata(std::shared_ptr &md) = 0; }; // ----- used in File and Fifo outputs enum EtiFileType { ETI_FILE_TYPE_NONE = 0, ETI_FILE_TYPE_RAW, ETI_FILE_TYPE_STREAMED, ETI_FILE_TYPE_FRAMED }; // ---------- File output ------------ class DabOutputFile : public DabOutput { public: DabOutputFile() { nbFrames_ = 0; file_ = -1; type_ = ETI_FILE_TYPE_FRAMED; } int Open(const char* filename); int Write(void* buffer, int size); int Close(); std::string get_info() const { return "file://" + filename_; } virtual void setMetadata(std::shared_ptr &md) {} protected: /* Set ETI type according to filename, and return * filename without the &type=foo part */ std::string SetEtiType(const std::string& filename); std::string filename_; int file_; EtiFileType type_; unsigned long nbFrames_; }; // ---------- FIFO output ------------ class DabOutputFifo : public DabOutputFile { public: DabOutputFifo() : DabOutputFile() {} int Open(const char* filename); int Write(void* buffer, int size); std::string get_info() const { return "fifo://" + filename_; } }; // -------------- RAW socket ----------- class DabOutputRaw : public DabOutput { public: int Open(const char* name); int Write(void* buffer, int size); int Close(); std::string get_info() const { return "raw://" + filename_; } virtual void setMetadata(std::shared_ptr &md) {} private: std::string filename_; int socket_ = -1; bool isCyclades_ = false; }; // -------------- UDP ------------------ class DabOutputUdp : public DabOutput { public: DabOutputUdp(); int Open(const char* name); int Write(void* buffer, int size); int Close() { return 0; } std::string get_info() const { return "udp://" + uri_; } virtual void setMetadata(std::shared_ptr &md) {} private: // make sure we don't copy this output around // the UdpPacket and UdpSocket do not support // copying either DabOutputUdp(const DabOutputUdp& other) = delete; DabOutputUdp operator=(const DabOutputUdp& other) = delete; std::string uri_; Socket::UDPSocket socket_; Socket::UDPPacket packet_; }; // -------------- TCP ------------------ class DabOutputTcp : public DabOutput { public: int Open(const char* name); int Write(void* buffer, int size); int Close(); std::string get_info() const { return "tcp://" + uri_; } virtual void setMetadata(std::shared_ptr &md) {} private: std::string uri_; std::shared_ptr dispatcher_; }; // -------------- Simul ------------------ class DabOutputSimul : public DabOutput { public: int Open(const char* name); int Write(void* buffer, int size); int Close() { return 0; } std::string get_info() const { return "simul://" + name_; } virtual void setMetadata(std::shared_ptr &md) {} private: std::string name_; std::chrono::steady_clock::time_point startTime_; }; #if defined(HAVE_OUTPUT_ZEROMQ) #define NUM_FRAMES_PER_ZMQ_MESSAGE 4 /* A concatenation of four ETI frames, * whose maximal size is 6144. * * If we transmit four frames in one zmq message, * we do not risk breaking ETI vs. transmission frame * phase. * * The frames are concatenated in buf, and * their sizes is given in the buflen array. * * Most of the time, the buf will not be completely * filled */ struct zmq_dab_message_t { zmq_dab_message_t() : buf() { /* set buf lengths to invalid */ buflen[0] = -1; buflen[1] = -1; buflen[2] = -1; buflen[3] = -1; version = 1; } uint32_t version; int16_t buflen[NUM_FRAMES_PER_ZMQ_MESSAGE]; /* The head stops here. Use the macro below to calculate * the head size. */ uint8_t buf[NUM_FRAMES_PER_ZMQ_MESSAGE*6144]; /* The packet is then followed with metadata appended to it, * according to dabOutput/metadata.h */ }; #define ZMQ_DAB_MESSAGE_HEAD_LENGTH (4 + NUM_FRAMES_PER_ZMQ_MESSAGE*2) // -------------- ZeroMQ message queue ------------------ class DabOutputZMQ : public DabOutput { public: DabOutputZMQ(const std::string &zmq_proto, bool allow_metadata) : endpoint_(""), zmq_proto_(zmq_proto), zmq_context_(1), zmq_pub_sock_(zmq_context_, ZMQ_PUB), zmq_message_ix(0), m_allow_metadata(allow_metadata) { } DabOutputZMQ(const DabOutputZMQ& other) = delete; DabOutputZMQ& operator=(const DabOutputZMQ& other) = delete; virtual ~DabOutputZMQ() { zmq_pub_sock_.close(); } std::string get_info(void) const { return "zmq: " + zmq_proto_ + "://" + endpoint_; } int Open(const char* endpoint); int Write(void* buffer, int size); int Close(); void setMetadata(std::shared_ptr &md); private: std::string endpoint_; std::string zmq_proto_; zmq::context_t zmq_context_; // handle for the zmq context zmq::socket_t zmq_pub_sock_; // handle for the zmq publisher socket zmq_dab_message_t zmq_message; int zmq_message_ix; bool m_allow_metadata; std::vector > meta_; }; #endif Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputFifo.cpp000066400000000000000000000064431476627344300251660ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org Fifo output is very similar to file, except it doesn't seek */ /* 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 #include #include #include #include #include #include #include #include "dabOutput.h" int DabOutputFifo::Open(const char* filename) { filename_ = SetEtiType(filename); // Create the fifo if it does not already exist struct stat s = {0}; int ret = stat(filename_.c_str(), &s); if (ret == -1) { if (errno == ENOENT) { ret = mkfifo(filename_.c_str(), 0666); if (ret == -1) { etiLog.level(error) << "Could not create fifo " << filename_ << " : " << strerror(errno); return -1; } } else { etiLog.level(error) << "Could not stat fifo " << filename_ << " : " << strerror(errno); return -1; } } this->file_ = open(filename_.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (this->file_ == -1) { perror(filename_.c_str()); return -1; } return 0; } int DabOutputFifo::Write(void* buffer, int size) { uint8_t padding[6144]; switch (this->type_) { case ETI_FILE_TYPE_FRAMED: if (this->nbFrames_ == 0) { uint32_t nbFrames = (uint32_t)-1; // Writing nb frames if (write(this->file_, &nbFrames, 4) == -1) goto FIFO_WRITE_ERROR; } case ETI_FILE_TYPE_STREAMED: // Writing frame length if (write(this->file_, &size, 2) == -1) goto FIFO_WRITE_ERROR; // Appending data if (write(this->file_, buffer, size) == -1) goto FIFO_WRITE_ERROR; break; case ETI_FILE_TYPE_RAW: // Appending data if (write(this->file_, buffer, size) == -1) goto FIFO_WRITE_ERROR; // Appending padding memset(padding, 0x55, 6144 - size); if (write(this->file_, padding, 6144 - size) == -1) goto FIFO_WRITE_ERROR; break; case ETI_FILE_TYPE_NONE: default: etiLog.log(error, "File type is not supported.\n"); return -1; } return size; FIFO_WRITE_ERROR: perror("Error while writing to fifo"); return -1; } Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputFile.cpp000066400000000000000000000110051476627344300251500ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org File output */ /* 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 #include #include #include #include #include "dabOutput.h" int DabOutputFile::Open(const char* filename) { filename_ = SetEtiType(filename); this->file_ = open(filename_.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666); if (this->file_ == -1) { perror(filename_.c_str()); return -1; } return 0; } int DabOutputFile::Write(void* buffer, int size) { uint8_t padding[6144]; const uint16_t frame_size = size; ++nbFrames_; switch (this->type_) { case ETI_FILE_TYPE_FRAMED: // Writing nb of frames at beginning of file if (lseek(this->file_, 0, SEEK_SET) == -1) goto FILE_WRITE_ERROR; if (write(this->file_, &this->nbFrames_, 4) == -1) goto FILE_WRITE_ERROR; // Writing nb frame length at end of file if (lseek(this->file_, 0, SEEK_END) == -1) goto FILE_WRITE_ERROR; if (write(this->file_, &frame_size, 2) == -1) goto FILE_WRITE_ERROR; // Appending data if (write(this->file_, buffer, size) == -1) goto FILE_WRITE_ERROR; break; case ETI_FILE_TYPE_STREAMED: // Writing nb frame length at end of file if (write(this->file_, &frame_size, 2) == -1) goto FILE_WRITE_ERROR; // Appending data if (write(this->file_, buffer, size) == -1) goto FILE_WRITE_ERROR; break; case ETI_FILE_TYPE_RAW: // Appending data if (write(this->file_, buffer, size) == -1) goto FILE_WRITE_ERROR; // Appending padding memset(padding, 0x55, 6144 - size); if (write(this->file_, padding, 6144 - size) == -1) goto FILE_WRITE_ERROR; break; case ETI_FILE_TYPE_NONE: default: etiLog.log(error, "File type is not supported.\n"); return -1; } return size; FILE_WRITE_ERROR: perror("Error while writing to file"); return -1; } int DabOutputFile::Close() { if (close(this->file_) == 0) { this->file_ = -1; return 0; } perror("Can't close file"); return -1; } std::string DabOutputFile::SetEtiType(const std::string& filename) { size_t ix = filename.find('?'); const std::string filename_before_q = filename.substr(0, ix); if (ix != std::string::npos) { do { const size_t ix_key = ix + 1; const size_t ix_eq = filename.find('=', ix); if (ix_eq == std::string::npos) { // no equals sign, not a valid query. Return up to the question mark break; } const size_t ix_val = ix_eq + 1; const std::string key = filename.substr(ix_key, ix_eq - ix_key); ix = filename.find('&', ix); const size_t len_value = (ix == std::string::npos) ? std::string::npos : ix - ix_val; if (key == "type") { const std::string value = filename.substr(ix_val, len_value); if (value == "raw") { this->type_ = ETI_FILE_TYPE_RAW; break; } else if (value == "framed") { this->type_ = ETI_FILE_TYPE_FRAMED; break; } else if (value == "streamed") { this->type_ = ETI_FILE_TYPE_STREAMED; break; } else { std::stringstream ss; ss << "File type '" << value << "' is not supported."; throw std::runtime_error(ss.str()); } } } while (ix != std::string::npos); } return filename_before_q; } Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputRaw.cpp000066400000000000000000000345651476627344300250420ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org RAW output used for farsync */ /* 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_OUTPUT_RAW) #include "dabOutput.h" #ifdef _WIN32 # include # include #else # include # include # include # include /* Careful, including boost here can lead to errors because of conflicting struct definitions */ # include # include # include # include "farsync.h" #endif #include #include #include const unsigned char revTable[] = { 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff }; /* Takes an URI with some parameters in the form * proto://something/etc?param1=foo¶m2=bar * and returns a map of * { "__basename": "proto://something/etc", "param1": "foo", "param2": "bar } */ static std::map tokeniseURIParam(const std::string& uri) { std::map m; size_t ix = uri.find('?'); m["__basename"] = uri.substr(0, ix); if (ix != std::string::npos) { do { const size_t ix_key = ix + 1; const size_t ix_eq = uri.find('=', ix); if (ix_eq == std::string::npos) { // no equals sign, not a valid query. break; } const size_t ix_val = ix_eq + 1; const std::string key = uri.substr(ix_key, ix_eq - ix_key); ix = uri.find('&', ix_eq); const size_t len_value = (ix == std::string::npos) ? std::string::npos : ix - ix_val; const std::string value = uri.substr(ix_val, len_value); if (m.count(key) != 0) { throw std::runtime_error("Key " + key + " was given twice in parameter list"); } m[key] = value; } while (ix != std::string::npos); } return m; } int DabOutputRaw::Open(const char* name) { if (name == nullptr) { etiLog.log(error, "Socket name must be provided!"); return -1; } const std::string name_with_params(name); const auto param_map = tokeniseURIParam(name_with_params); filename_ = param_map.at("__basename"); const char* filename = filename_.c_str(); #ifdef _WIN32 // Opening device socket_ = CreateFile(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (socket_ == INVALID_HANDLE_VALUE) { etiLog.log(error, "Can't open raw device '%s': %i", filename, GetLastError()); return -1; } // Configuring device DWORD result; FS_TE1_CONFIG config; if (!DeviceIoControl(socket_, IoctlCodeFarSyncGetTE1Config, NULL, 0, &config, sizeof(config), &result, NULL)) { etiLog.log(error, "Can't get raw device '%s' config: %i", filename, GetLastError()); return -1; } config.dataRate = 2048000; config.clocking = CLOCKING_MASTER; config.framing = FRAMING_E1; config.structure = STRUCTURE_UNFRAMED; config.iface = INTERFACE_BNC; config.coding = CODING_HDB3; config.lineBuildOut = LBO_0dB; config.equalizer = EQUALIZER_SHORT; config.transparentMode = TRUE; config.loopMode = LOOP_NONE; config.range = RANGE_0_40_M; config.txBufferMode = BUFFER_2_FRAME; config.rxBufferMode = BUFFER_2_FRAME; config.startingTimeSlot = 0; config.losThreshold = 2; config.enableIdleCode = TRUE; config.idleCode = 0xff; if (!DeviceIoControl(socket_, IoctlCodeFarSyncSetTE1Config, &config, sizeof(config), NULL, 0, &result, NULL)) { etiLog.log(error, "Can't set raw device '%s' config: %i", filename, GetLastError()); return -1; } // Starting device if (!DeviceIoControl(socket_, IoctlCodeFarSyncQuickStart, NULL, 0, NULL, 0, &result, NULL)) { etiLog.log(error, "Can't start raw device '%s': %i", filename, GetLastError()); return -1; } #else socket_ = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); if (socket_ == -1) { etiLog.log(error, "Failed to create socket"); perror(filename); return -1; } struct ifreq ifr; struct sockaddr_ll saddr; memset(&ifr, 0, sizeof(struct ifreq)); if (sizeof(ifr.ifr_name) > 0) { strncpy(ifr.ifr_name, filename, sizeof(ifr.ifr_name) - 1); } // Get current Farsync configuration struct fstioc_info info; memset(&info, 0, sizeof(info)); ifr.ifr_data = (char*)&info; if (ioctl(socket_, FSTGETCONF, &ifr) == -1) { etiLog.log(debug, "Cyclades card identified."); isCyclades_ = true; // Set the interface MTU if needed if (ioctl(socket_, SIOCGIFMTU, &ifr) == -1) { etiLog.log(error, "Can't get raw device MTU!"); perror(filename); return -1; } else { if (ifr.ifr_mtu != 6143) { ifr.ifr_mtu = 6143; if (ioctl(socket_, SIOCSIFMTU, &ifr) == -1) { etiLog.log(error, "Can't Cyclades device MTU!"); perror(filename); return -1; } } } } else { etiLog.log(debug, "Farsync card identified. " "Using version %s of the FarSync driver", FST_USER_VERSION); isCyclades_ = false; info.lineInterface = E1; info.proto = FST_RAW; info.internalClock = EXTCLK; info.lineSpeed = 2048000; //info.debug = DBG_INIT | DBG_OPEN | DBG_PCI | DBG_IOCTL | DBG_TX; info.transparentMode = 1; info.ignoreCarrier = 1; info.numTxBuffers = 8; info.numRxBuffers = 8; info.txBufferSize = 6144; info.rxBufferSize = 6144; // E1 specific config info.clockSource = CLOCKING_SLAVE; if (param_map.count("clocking") != 0) { if (param_map.at("clocking") == "master") { info.clockSource = CLOCKING_MASTER; } else if (param_map.at("clocking") == "slave") { info.clockSource = CLOCKING_SLAVE; } else { throw std::runtime_error("The param clocking of the RAW output " "accepts master and slave as values only"); } } info.structure = STRUCTURE_UNFRAMED; info.interface = INTERFACE_BNC; //RJ48C; info.coding = CODING_HDB3; info.txBufferMode = BUFFER_2_FRAME; info.idleCode = 0xff; info.valid = FSTVAL_ALL; if (param_map.count("extsyncclock") != 0) { long long int rate = std::stoi(param_map.at("extsyncclock")); info.extSyncClockEnable = 1; info.extSyncClockRate = rate; } else { info.extSyncClockEnable = 0; } // Setting configuration ifr.ifr_data = (char*)&info; if (ioctl(socket_, FSTSETCONF, &ifr) == -1) { etiLog.log(error, "Can't set Farsync configuration!"); perror(filename); return -1; } // Disabling notify int notify = 0; ifr.ifr_data = (char*)¬ify; if (ioctl(socket_, FSTSNOTIFY, &ifr) == -1) { etiLog.log(error, "Can't disable Farsync notify!"); perror(filename); return -1; } // Apply the new configuration // Set the interface down if needed if (ioctl(socket_, SIOCGIFFLAGS, &ifr) == -1) { etiLog.log(error, "Can't get Farsync flags!"); perror(filename); return -1; } else { if (ifr.ifr_flags & IFF_UP) { ifr.ifr_flags &= ~IFF_UP; if (ioctl(socket_, SIOCSIFFLAGS, &ifr) == -1) { etiLog.log(error, "Can't turn down Farsync device!"); perror(filename); return -1; } } } // Set the interface MTU if needed if (ioctl(socket_, SIOCGIFMTU, &ifr) == -1) { etiLog.log(error, "Can't get Farsync MTU!"); perror(filename); return -1; } else { if (ifr.ifr_mtu != 6144) { ifr.ifr_mtu = 6144; if (ioctl(socket_, SIOCSIFMTU, &ifr) == -1) { etiLog.log(error, "Can't set Farsync MTU!"); perror(filename); return -1; } } } } // Set the interface up if needed if (ioctl(socket_, SIOCGIFFLAGS, &ifr) == -1) { etiLog.log(error, "Can't get raw device flags!"); perror(filename); return -1; } else { if (!(ifr.ifr_flags & IFF_UP)) { ifr.ifr_flags |= IFF_UP; if (ioctl(socket_, SIOCSIFFLAGS, &ifr) == -1) { etiLog.log(error, "Can't turn up raw device!"); perror(filename); return -1; } } } close(socket_); //////////////////// // Opening device // //////////////////// if ((socket_ = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_CUST))) == -1) { etiLog.log(error, "Are you logged as root?"); perror(filename); return -1; } // ioctl to read the interface number memset(&ifr, 0, sizeof(struct ifreq)); if (sizeof(ifr.ifr_name) > 0) { strncpy(ifr.ifr_name, filename, sizeof(ifr.ifr_name) - 1); } if (ioctl(socket_, SIOCGIFINDEX, (char *) &ifr) == -1) { perror(filename); return -1; } // Bind to the interface name memset(&saddr, 0, sizeof(struct sockaddr_ll)); saddr.sll_family = AF_PACKET; saddr.sll_protocol = ARPHRD_RAWHDLC; saddr.sll_ifindex = ifr.ifr_ifindex; if (bind(socket_, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) { etiLog.log(error, "Can't bind raw device!"); perror(filename); return -1; } #endif return 0; } int DabOutputRaw::Write(void* buffer, int size) { std::vector buffer_(6144); if ((size_t)size > buffer_.size()) { throw std::logic_error("DabOutputRaw::Write size exceeded"); } // Encode data, extend our frame with 0x55 padding int i = 0; for (; i < size; i++) { buffer_[i] = revTable[reinterpret_cast(buffer)[i]]; } for (; i < 6144; i++) { buffer_[i] = revTable[0x55]; } // Write data #ifdef _WIN32 DWORD result; if(!DeviceIoControl(socket_, IoctlCodeTxFrame, buffer_.data(), 6144, NULL, 0, &result, NULL)) { goto RAW_WRITE_ERROR; } #else if (isCyclades_) { if (write(socket_, buffer_.data() + 1, 6143) != 6143) { goto RAW_WRITE_ERROR; } } else { int ret = send(socket_, buffer_.data(), 6144, 0); if (ret != 6144) { etiLog.log(error, "%i/6144 bytes written", ret); return -1; } } #endif return size; RAW_WRITE_ERROR: #ifdef _WIN32 DWORD err = GetLastError(); LPSTR errMsg; if(FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0, (LPTSTR)&errMsg, 0, NULL) == 0) { fprintf(stderr, "Error while writing to raw socket: %i", err); } else { fprintf(stderr, "Error while writing to raw socket: %s", errMsg); LocalFree(errMsg); } #else etiLog.level(error) << "Error while writing to raw socket: " << strerror(errno); #endif return -1; } int DabOutputRaw::Close() { #ifdef _WIN32 CancelIo(socket_); CloseHandle(socket_); return 0; #else if (close(socket_) == 0) { socket_ = -1; return 0; } perror("Can't close raw socket"); #endif return -1; } #endif // defined(HAVE_OUTPUT_RAW) Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputSimul.cpp000066400000000000000000000031641476627344300253710ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 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 SIMUL throttling output. It guarantees correct frame generation rate */ /* 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 "dabOutput.h" #include #include #include #include #include #include int DabOutputSimul::Open(const char* name) { startTime_ = std::chrono::steady_clock::now(); return 0; } int DabOutputSimul::Write(void* buffer, int size) { auto curTime = std::chrono::steady_clock::now(); const auto frameinterval = std::chrono::milliseconds(24); auto diff = curTime - startTime_; auto waiting = frameinterval - diff; if (diff < frameinterval) { std::this_thread::sleep_for(waiting); } startTime_ += frameinterval; return size; } Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputTcp.cpp000066400000000000000000000064451476627344300250330ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 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 TCP output */ /* 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 #include #include #include #include "dabOutput.h" #include #include #include #include #include #include #include "ThreadsafeQueue.h" using namespace std; using vec_u8 = std::vector; // In ETI one element would be an ETI frame of 6144 bytes. // 250 frames correspond to 6 seconds. This is mostly here // to ensure we do not accumulate data for faulty sockets, delay // management has to be done on the receiver end. const size_t MAX_QUEUED_ETI_FRAMES = 250; static bool parse_uri(const char *uri, long *port, string& addr) { char* const hostport = strdup(uri); // the uri is actually an tuple host:port char* address; address = strchr((char*)hostport, ':'); if (address == NULL) { etiLog.log(error, "\"%s\" is an invalid format for tcp address: " "should be [address]:port - > aborting\n", hostport); goto tcp_open_fail; } // terminate string hostport after the host, and advance address to the port number *(address++) = 0; *port = strtol(address, (char **)NULL, 10); if ((*port == LONG_MIN) || (*port == LONG_MAX)) { etiLog.log(error, "can't convert port number in tcp address %s\n", address); goto tcp_open_fail; } if (*port == 0) { etiLog.log(error, "can't use port number 0 in tcp address\n"); goto tcp_open_fail; } addr = hostport; free(hostport); return true; tcp_open_fail: free(hostport); return false; } int DabOutputTcp::Open(const char* name) { long port = 0; string address; bool success = parse_uri(name, &port, address); uri_ = name; if (success) { dispatcher_ = make_shared(MAX_QUEUED_ETI_FRAMES, 0); dispatcher_->start(port, address); } else { throw runtime_error(string("Could not parse TCP output address ") + name); } return 0; } int DabOutputTcp::Write(void* buffer, int size) { vec_u8 data(6144); uint8_t* buffer_u8 = (uint8_t*)buffer; std::copy(buffer_u8, buffer_u8 + size, data.begin()); // Pad to 6144 bytes std::fill(data.begin() + size, data.end(), 0x55); dispatcher_->write(data); return size; } int DabOutputTcp::Close() { return 0; } Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputUdp.cpp000066400000000000000000000070621476627344300250310ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org UDP output */ /* 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_OUTPUT_UDP) #include #include #include #include #include #include #include "dabOutput.h" #include "Socket.h" DabOutputUdp::DabOutputUdp() : socket_(), packet_(6144) { } int DabOutputUdp::Open(const char* name) { using namespace std; const string uri_without_proto(name); regex re_url("([^:]+):([0-9]+)(.*)"); regex re_query("[?](?:src=([^&,]+))(?:[&,]ttl=([0-9]+))?"); smatch what; if (regex_match(uri_without_proto, what, re_url, regex_constants::match_default)) { string address = what[1]; string port_str = what[2]; long port = std::strtol(port_str.c_str(), nullptr, 0); if ((port <= 0) || (port >= 65536)) { etiLog.level(error) << "can't convert port number in UDP address " << uri_without_proto; return -1; } packet_.address.resolveUdpDestination(address, port); string query_params = what[3]; smatch query_what; if (regex_match(query_params, query_what, re_query, regex_constants::match_default)) { string src = query_what[1]; try { socket_.setMulticastSource(src.c_str()); string ttl_str = query_what[2]; if (not ttl_str.empty()) { long ttl = std::strtol(ttl_str.c_str(), nullptr, 0); if ((ttl <= 0) || (ttl >= 255)) { etiLog.level(error) << "Invalid TTL setting in " << uri_without_proto; return -1; } socket_.setMulticastTTL(ttl); } } catch (const std::runtime_error& e) { etiLog.level(error) << "Failed to set UDP output settings" << e.what(); } } else if (not query_params.empty()) { etiLog.level(error) << "UDP output: could not parse parameters " << query_params; return -1; } } else { etiLog.level(error) << uri_without_proto << " is an invalid format for UDP address: " "expected ADDRESS:PORT[?src=SOURCE&ttl=TTL]"; return -1; } return 0; } int DabOutputUdp::Write(void* buffer, int size) { const uint8_t *buf = reinterpret_cast(buffer); packet_.buffer.resize(0); std::copy(buf, buf + size, std::back_inserter(packet_.buffer)); socket_.send(packet_); return 0; } #endif // defined(HAVE_OUTPUT_UDP) Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/dabOutputZMQ.cpp000066400000000000000000000074161476627344300247530ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2014 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org ZeroMQ output. see www.zeromq.org for more info From the ZeroMQ manpage 'zmq': The 0MQ lightweight messaging kernel is a library which extends the standard socket interfaces with features traditionally provided by specialised messaging middleware products. 0MQ sockets provide an abstraction of asynchronous message queues, multiple messaging patterns, message filtering (subscriptions), seamless access to multiple transport protocols and more. */ /* 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #if defined(HAVE_OUTPUT_ZEROMQ) #include #include #include #include #include #include #include #include "zmq.hpp" #include "dabOutput.h" using namespace std; int DabOutputZMQ::Open(const char* endpoint) // the format for endpoint shall be as defined in the zmq_bind manpage // but without specifying the protocol. The protocol has been given in // the constructor { // bind to uri string proto_endpoint = zmq_proto_ + "://" + std::string(endpoint); zmq_pub_sock_.bind(proto_endpoint.c_str()); endpoint_ = endpoint; return 0; } int DabOutputZMQ::Write(void* buffer, int size) { int offset = 0; // Increment the offset by the accumulated frame offsets for (int i = 0; i < zmq_message_ix; i++) { offset += zmq_message.buflen[i]; } if (offset + size > NUM_FRAMES_PER_ZMQ_MESSAGE*6144) { throw std::runtime_error("FAULT: invalid ETI frame size!"); } // Append the new frame to our message memcpy(zmq_message.buf + offset, buffer, size); zmq_message.buflen[zmq_message_ix] = size; zmq_message_ix++; // As soon as we have NUM_FRAMES_PER_ZMQ_MESSAGE frames, we transmit if (zmq_message_ix == NUM_FRAMES_PER_ZMQ_MESSAGE) { // Size of the header: size_t full_length = ZMQ_DAB_MESSAGE_HEAD_LENGTH; for (int i = 0; i < NUM_FRAMES_PER_ZMQ_MESSAGE; i++) { full_length += zmq_message.buflen[i]; } vector msg(full_length); memcpy(msg.data(), (uint8_t*)&zmq_message, full_length); // metadata gets appended at the end for (const auto& md : meta_) { vector md_data(md->getLength()); md->write(md_data.data()); copy(md_data.begin(), md_data.end(), back_inserter(msg)); } const int flags = 0; zmq_send(zmq_pub_sock_, msg.data(), msg.size(), flags); meta_.clear(); zmq_message_ix = 0; for (int i = 0; i < NUM_FRAMES_PER_ZMQ_MESSAGE; i++) { zmq_message.buflen[i] = -1; } } return size; } int DabOutputZMQ::Close() { return zmq_close(zmq_pub_sock_); } void DabOutputZMQ::setMetadata(std::shared_ptr &md) { if (m_allow_metadata) { meta_.push_back(md); } } #endif Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/metadata.cpp000066400000000000000000000044001476627344300241620ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org The metadata support for the outputs. */ /* 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 "dabOutput/metadata.h" #include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif template size_t write_meta(output_metadata_id_e md, uint8_t *buf, const T value) { buf[0] = static_cast(md); const int16_t len_value = sizeof(T); const uint16_t data_length = htons(len_value); memcpy(buf + 1, &data_length, sizeof(data_length)); if (len_value == 1) { buf[3] = value; } else if (len_value == 2) { const uint16_t val = htons(value); memcpy(buf + 3, &val, sizeof(val)); } else if (len_value == 4) { const uint32_t val = htonl(value); memcpy(buf + 3, &val, sizeof(val)); } else { throw std::runtime_error("Unsupported metadata len " + std::to_string(len_value)); } return 3 + len_value; } size_t OutputMetadataSeparation::write(uint8_t *buf) { buf[0] = static_cast(getId()); // Length 0 buf[1] = 0; buf[2] = 0; return 3; } size_t OutputMetadataUTCO::write(uint8_t *buf) { return write_meta(getId(), buf, utco); } size_t OutputMetadataEDITime::write(uint8_t *buf) { return write_meta(getId(), buf, seconds); } size_t OutputMetadataDLFC::write(uint8_t *buf) { return write_meta(getId(), buf, dlfc); } Opendigitalradio-ODR-DabMux-29c710c/src/dabOutput/metadata.h000066400000000000000000000064451476627344300236420ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org The metadata support for the outputs. */ /* 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 . */ #pragma once #include "Log.h" /* Some outputs support additional metadata to be carried * next to the main ETI stream. This metadata always has the * following format: * Field: | id | len | value | * Length: 1 2 depending on id * * Multi-byte values are transmitted in network byte order. */ enum class output_metadata_id_e { // Contains no value, can be used to group fields separation_marker = 0, // TAI-UTC offset, value is int16_t. utc_offset = 1, /* EDI Time is the number of SI seconds since 2000-01-01 T 00:00:00 UTC. * value is an uint32_t */ edi_time = 2, /* The DLFC field from the EDI TAG deti. value is uint16_t */ dlfc = 3, }; struct OutputMetadata { virtual ~OutputMetadata() {}; virtual output_metadata_id_e getId(void) const = 0; virtual size_t getLength(void) const = 0; /* Write the value in the metadata format to the buffer. * Returns number of bytes written. The buffer buf needs * to be large enough to contain the value; */ virtual size_t write(uint8_t *buf) = 0; }; struct OutputMetadataSeparation : public OutputMetadata { explicit OutputMetadataSeparation() {} output_metadata_id_e getId(void) const { return output_metadata_id_e::separation_marker; } virtual size_t getLength(void) const { return 3; } virtual size_t write(uint8_t *buf); }; struct OutputMetadataUTCO : public OutputMetadata { explicit OutputMetadataUTCO(int16_t utco) : utco(utco) {} output_metadata_id_e getId(void) const { return output_metadata_id_e::utc_offset; } virtual size_t getLength(void) const { return 5; } virtual size_t write(uint8_t *buf); int16_t utco; }; struct OutputMetadataEDITime : public OutputMetadata { explicit OutputMetadataEDITime(uint32_t seconds) : seconds(seconds) {} output_metadata_id_e getId(void) const { return output_metadata_id_e::edi_time; } virtual size_t getLength(void) const { return 7; } virtual size_t write(uint8_t *buf); uint32_t seconds; }; struct OutputMetadataDLFC : public OutputMetadata { explicit OutputMetadataDLFC(uint16_t dlfc) : dlfc(dlfc) {} output_metadata_id_e getId(void) const { return output_metadata_id_e::dlfc; } virtual size_t getLength(void) const { return 5; } virtual size_t write(uint8_t *buf); uint16_t dlfc; }; Opendigitalradio-ODR-DabMux-29c710c/src/fig/000077500000000000000000000000001476627344300204765ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG.cpp000066400000000000000000000036631476627344300216170ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 */ /* 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 "FIG.h" #include namespace FIC { int rate_increment_ms(FIG_rate rate) { switch (rate) { /* All these values are multiples of 24, so that it is easier to reason * about the behaviour when considering ETI frames of 24ms duration * * In large ensembles it's not always possible to respect the reptition rates, so * the values are a bit larger than what the spec says. * However, we observed that some receivers wouldn't always show labels (rate B), * and that's why we reduced B rate to slightly below 1s. * */ case FIG_rate::FIG0_0: return 96; // Is a special case case FIG_rate::A: return 240; case FIG_rate::A_B: return 480; case FIG_rate::B: return 960; case FIG_rate::C: return 24000; case FIG_rate::D: return 30000; case FIG_rate::E: return 120000; } throw std::logic_error("Invalid FIG_rate"); } } // namespace FIC Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG.h000066400000000000000000000067201476627344300212610ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 */ /* 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 . */ #pragma once #include #include "MuxElements.h" namespace FIC { #define PACKED __attribute__ ((packed)) class FIGRuntimeInformation { public: FIGRuntimeInformation(std::shared_ptr& e) : currentFrame(0), ensemble(e), factumAnalyzer(false) {} unsigned long currentFrame; std::shared_ptr ensemble; bool factumAnalyzer; }; /* Recommended FIG rates according to ETSI TR 101 496-2 Table 3.6.1 * Keep in mind that this specification was not updated since DAB+ came out. * The rates that are recommended there cannot be satisfied with a well filled * DAB+ multiplex, and this is the reason the rates have to be reduced compared * to this recommendation. */ enum class FIG_rate { FIG0_0, /* Special repetition rate for FIG0/0, EN 300 401 Clause 6.4 In any 96 ms period, the FIG 0/0 should be transmitted in a fixed time position. In transmission mode I, this should be the first FIB (of the three) associated with the first CIF (of the four) in the transmission frame (see clause 5.1). In transmission modes II and III, this should be the first FIB of every fourth transmission frame. In transmission mode IV, this should be the first FIB (of the three) associated with the first CIF (of the two) in every alternate transmission frame (see clause 5.1). */ A, // at least 10 times per second A_B, // between 10 times and once per second B, // once per second C, // once every 10 seconds D, // less than once every 10 seconds E, // all in two minutes }; /* Helper function to calculate the deadline for the next transmission, in milliseconds */ int rate_increment_ms(FIG_rate rate); /* The fill function of each FIG shall return a status telling * the carousel how many bytes have been written, and if the complete * set of information from that FIG was transmitted. */ struct FillStatus { FillStatus() : num_bytes_written(0), complete_fig_transmitted(false) {} size_t num_bytes_written; bool complete_fig_transmitted; }; class IFIG { public: virtual FillStatus fill(uint8_t *buf, size_t max_size) = 0; virtual FIG_rate repetition_rate() const = 0; virtual int figtype() const = 0; virtual int figextension() const = 0; virtual const std::string name() const { std::stringstream ss; ss << figtype() << "/" << figextension(); return ss.str(); } }; } // namespace FIC Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0.h000066400000000000000000000025571476627344300213450ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include "fig/FIG0structs.h" #include "fig/FIG0_0.h" #include "fig/FIG0_1.h" #include "fig/FIG0_2.h" #include "fig/FIG0_3.h" #include "fig/FIG0_5.h" #include "fig/FIG0_6.h" #include "fig/FIG0_7.h" #include "fig/FIG0_8.h" #include "fig/FIG0_9.h" #include "fig/FIG0_10.h" #include "fig/FIG0_13.h" #include "fig/FIG0_14.h" #include "fig/FIG0_17.h" #include "fig/FIG0_18.h" #include "fig/FIG0_19.h" #include "fig/FIG0_21.h" #include "fig/FIG0_24.h" Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_0.cpp000066400000000000000000000037361476627344300221170ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_0.h" #include "utils.h" namespace FIC { struct FIGtype0_0 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; uint16_t EId; uint8_t CIFcnt_hight:5; uint8_t Al:1; uint8_t Change:2; uint8_t CIFcnt_low:8; } PACKED; //=========== FIG 0/0 =========== FillStatus FIG0_0::fill(uint8_t *buf, size_t max_size) { FillStatus fs; if (max_size < 6) { fs.num_bytes_written = 0; return fs; } FIGtype0_0 *fig0_0; fig0_0 = (FIGtype0_0 *)buf; fig0_0->FIGtypeNumber = 0; fig0_0->Length = 5; fig0_0->CN = 0; fig0_0->OE = 0; fig0_0->PD = 0; fig0_0->Extension = 0; fig0_0->EId = htons(m_rti->ensemble->id); fig0_0->Change = 0; fig0_0->Al = m_rti->ensemble->alarm_flag; fig0_0->CIFcnt_hight = (m_rti->currentFrame / 250) % 20; fig0_0->CIFcnt_low = (m_rti->currentFrame % 250); fs.complete_fig_transmitted = true; fs.num_bytes_written = 6; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_0.h000066400000000000000000000027051476627344300215570ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include namespace FIC { // FIG type 0/0, Multiplex Configuration Info (MCI), // Ensemble information class FIG0_0 : public IFIG { public: FIG0_0(FIGRuntimeInformation* rti) : m_rti(rti) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::FIG0_0; } virtual int figtype() const { return 0; } virtual int figextension() const { return 0; } private: FIGRuntimeInformation *m_rti; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_1.cpp000066400000000000000000000171301476627344300221110ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 */ /* 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 "fig/FIG0_1.h" namespace FIC { struct FIGtype0_1 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; } PACKED; struct FIGtype0_1_SubChannel_ShortF { uint8_t StartAdress_high:2; uint8_t SubChId:6; uint8_t StartAdress_low:8; uint8_t TableIndex:6; uint8_t TableSwitch:1; uint8_t Short_Long_form:1; } PACKED; struct FIGtype0_1_SubChannel_LongF { uint8_t StartAdress_high:2; uint8_t SubChId:6; uint8_t StartAdress_low:8; uint8_t Sub_ChannelSize_high:2; uint8_t ProtectionLevel:2; uint8_t Option:3; uint8_t Short_Long_form:1; uint8_t Sub_ChannelSize_low:8; } PACKED; FIG0_1::FIG0_1(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false), m_watermarkSize(0), m_watermarkPos(0) { uint8_t buffer[sizeof(m_watermarkData) / 2]; snprintf((char*)buffer, sizeof(buffer), "%s %s, %s %s", PACKAGE_NAME, #if defined(GITVERSION) GITVERSION, #else PACKAGE_VERSION, #endif __DATE__, __TIME__); memset(m_watermarkData, 0, sizeof(m_watermarkData)); m_watermarkData[0] = 0x55; // Sync m_watermarkData[1] = 0x55; m_watermarkSize = 16; for (unsigned i = 0; i < strlen((char*)buffer); ++i) { for (int j = 0; j < 8; ++j) { uint8_t bit = (buffer[m_watermarkPos >> 3] >> (7 - (m_watermarkPos & 0x07))) & 1; m_watermarkData[m_watermarkSize >> 3] |= bit << (7 - (m_watermarkSize & 0x07)); ++m_watermarkSize; bit = 1; m_watermarkData[m_watermarkSize >> 3] |= bit << (7 - (m_watermarkSize & 0x07)); ++m_watermarkSize; ++m_watermarkPos; } } m_watermarkPos = 0; } FillStatus FIG0_1::fill(uint8_t *buf, size_t max_size) { #define FIG0_1_TRACE discard FillStatus fs; size_t remaining = max_size; etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill initialised=" << (m_initialised ? 1 : 0) << " max_size=" << max_size; const int watermark_bit = (m_watermarkData[m_watermarkPos >> 3] >> (7 - (m_watermarkPos & 0x07))) & 1; const bool iterate_forward = (watermark_bit == 1); if (not m_initialised) { m_initialised = true; subchannels = m_rti->ensemble->subchannels; if (not iterate_forward) { std::reverse(subchannels.begin(), subchannels.end()); } subchannelFIG0_1 = subchannels.begin(); } if (max_size < 6) { return fs; } FIGtype0_1 *figtype0_1 = NULL; // Rotate through the subchannels until there is no more // space in the FIG0/1 for (; subchannelFIG0_1 != subchannels.end(); ++subchannelFIG0_1 ) { size_t subch_iter_ix = std::distance(subchannels.begin(), subchannelFIG0_1); etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill loop ix=" << subch_iter_ix << " of " << subchannels.size(); dabProtection* protection = &(*subchannelFIG0_1)->protection; if (figtype0_1 == NULL) { etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill header " << (protection->form == UEP ? "UEP " : "EEP ") << remaining; if ( (protection->form == UEP && remaining < 2 + 3) || (protection->form == EEP && remaining < 2 + 4) ) { etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill no space for header"; break; } figtype0_1 = (FIGtype0_1*)buf; figtype0_1->FIGtypeNumber = 0; figtype0_1->Length = 1; figtype0_1->CN = 0; figtype0_1->OE = 0; figtype0_1->PD = 0; figtype0_1->Extension = 1; buf += 2; remaining -= 2; } else if ( (protection->form == UEP && remaining < 3) || (protection->form == EEP && remaining < 4) ) { etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill no space for fig " << (protection->form == UEP ? "UEP " : "EEP ") << remaining; break; } if (protection->form == UEP) { FIGtype0_1_SubChannel_ShortF *fig0_1subchShort = (FIGtype0_1_SubChannel_ShortF*)buf; fig0_1subchShort->SubChId = (*subchannelFIG0_1)->id; fig0_1subchShort->StartAdress_high = (*subchannelFIG0_1)->startAddress / 256; fig0_1subchShort->StartAdress_low = (*subchannelFIG0_1)->startAddress % 256; fig0_1subchShort->Short_Long_form = 0; fig0_1subchShort->TableSwitch = 0; fig0_1subchShort->TableIndex = protection->uep.tableIndex; buf += 3; remaining -= 3; figtype0_1->Length += 3; etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill insert UEP id=" << (int)fig0_1subchShort->SubChId << " rem=" << remaining << " ix=" << subch_iter_ix; } else if (protection->form == EEP) { FIGtype0_1_SubChannel_LongF *fig0_1subchLong1 = (FIGtype0_1_SubChannel_LongF*)buf; fig0_1subchLong1->SubChId = (*subchannelFIG0_1)->id; fig0_1subchLong1->StartAdress_high = (*subchannelFIG0_1)->startAddress / 256; fig0_1subchLong1->StartAdress_low = (*subchannelFIG0_1)->startAddress % 256; fig0_1subchLong1->Short_Long_form = 1; fig0_1subchLong1->Option = protection->eep.GetOption(); fig0_1subchLong1->ProtectionLevel = protection->level; fig0_1subchLong1->Sub_ChannelSize_high = (*subchannelFIG0_1)->getSizeCu() / 256; fig0_1subchLong1->Sub_ChannelSize_low = (*subchannelFIG0_1)->getSizeCu() % 256; buf += 4; remaining -= 4; figtype0_1->Length += 4; etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill insert EEP id=" << (int)fig0_1subchLong1->SubChId << " rem=" << remaining << " ix=" << subch_iter_ix; } } size_t subch_iter_ix = std::distance(subchannels.begin(), subchannelFIG0_1); etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill loop out, rem=" << remaining << " ix=" << subch_iter_ix; if (subchannelFIG0_1 == subchannels.end()) { etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill completed, rem=" << remaining; m_initialised = false; fs.complete_fig_transmitted = true; m_watermarkPos++; if (m_watermarkPos == m_watermarkSize) { m_watermarkPos = 0; } } fs.num_bytes_written = max_size - remaining; etiLog.level(FIG0_1_TRACE) << "FIG0_1::fill wrote " << fs.num_bytes_written; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_1.h000066400000000000000000000032701476627344300215560ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 */ /* 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 . */ #pragma once #include #include "fig/FIG.h" namespace FIC { // FIG type 0/1, MCI, Sub-Channel Organization, // one instance of the part for each subchannel class FIG0_1 : public IFIG { public: FIG0_1(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::A; } virtual int figtype() const { return 0; } virtual int figextension() const { return 1; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_subchannel subchannels; vec_sp_subchannel::iterator subchannelFIG0_1; uint8_t m_watermarkData[128]; size_t m_watermarkSize; size_t m_watermarkPos; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_10.cpp000066400000000000000000000061771476627344300222020ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_10.h" #include "utils.h" namespace FIC { struct FIGtype0_10_LongForm { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; uint8_t MJD_high:7; uint8_t RFU:1; uint8_t MJD_med; uint8_t Hours_high:3; uint8_t UTC:1; uint8_t ConfInd:1; uint8_t LSI:1; uint8_t MJD_low:2; uint8_t Minutes:6; uint8_t Hours_low:2; uint8_t Milliseconds_high:2; uint8_t Seconds:6; uint8_t Milliseconds_low; void setMJD(uint32_t date) { MJD_high = (date >> 10) & 0x7f; MJD_med = (date >> 2) & 0xff; MJD_low = date & 0x03; } void setHours(uint16_t hours) { Hours_high = (hours >> 2) & 0x07; Hours_low = hours & 0x03; } void setMilliseconds(uint16_t ms) { Milliseconds_high = (ms >> 8) & 0x03; Milliseconds_low = ms & 0xFF; } } PACKED; FIG0_10::FIG0_10(FIGRuntimeInformation *rti) : m_rti(rti) { } FillStatus FIG0_10::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; size_t remaining = max_size; if (remaining < 8) { fs.num_bytes_written = 0; return fs; } //Time and country identifier auto fig0_10 = (FIGtype0_10_LongForm*)buf; fig0_10->FIGtypeNumber = 0; fig0_10->Length = 7; fig0_10->CN = 0; fig0_10->OE = 0; fig0_10->PD = 0; fig0_10->Extension = 10; buf += 2; remaining -= 2; struct tm timeData; time_t dab_time_seconds = 0; uint32_t dab_time_millis = 0; get_dab_time(&dab_time_seconds, &dab_time_millis); gmtime_r(&dab_time_seconds, &timeData); fig0_10->RFU = 0; fig0_10->setMJD(gregorian2mjd(timeData.tm_year + 1900, timeData.tm_mon + 1, timeData.tm_mday)); fig0_10->LSI = 0; fig0_10->ConfInd = 1; fig0_10->UTC = 1; fig0_10->setHours(timeData.tm_hour); fig0_10->Minutes = timeData.tm_min; fig0_10->Seconds = timeData.tm_sec; fig0_10->setMilliseconds(dab_time_millis); buf += 6; remaining -= 6; fs.num_bytes_written = max_size - remaining; fs.complete_fig_transmitted = true; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_10.h000066400000000000000000000027521476627344300216420ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include "fig/FIG.h" namespace FIC { // FIG type 0/10 // The date and time feature is used to signal a location-independent timing // reference in UTC format. class FIG0_10 : public IFIG { public: FIG0_10(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 10; } private: FIGRuntimeInformation *m_rti; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_13.cpp000066400000000000000000000204171476627344300221760ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_13.h" #include "utils.h" namespace FIC { // See EN 300 401, Clause 8.1.20 for the FIG0_13 description struct FIG0_13_shortAppInfo { uint16_t SId; uint8_t No:4; uint8_t SCIdS:4; } PACKED; struct FIG0_13_longAppInfo { uint32_t SId; uint8_t No:4; uint8_t SCIdS:4; } PACKED; struct FIG0_13_app { uint8_t typeHigh; uint8_t length:5; uint8_t typeLow:3; void setType(uint16_t type) { typeHigh = type >> 3; typeLow = type & 0x1f; } } PACKED; FIG0_13::FIG0_13(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false), m_transmit_programme(false) { } FillStatus FIG0_13::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; ssize_t remaining = max_size; if (not m_initialised) { componentFIG0_13 = m_rti->ensemble->components.end(); m_initialised = true; } FIGtype0* fig0 = NULL; for (; componentFIG0_13 != ensemble->components.end(); ++componentFIG0_13) { auto subchannel = getSubchannel(ensemble->subchannels, (*componentFIG0_13)->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "Subchannel %i does not exist for component " "of service %i", (*componentFIG0_13)->subchId, (*componentFIG0_13)->serviceId); continue; } const auto type = (*subchannel)->type; if ( (m_transmit_programme and (type == subchannel_type_t::DABPlusAudio or type == subchannel_type_t::DABAudio) and (*componentFIG0_13)->audio.uaTypes.size() != 0) or (not m_transmit_programme and (*subchannel)->type == subchannel_type_t::Packet and (*componentFIG0_13)->packet.uaTypes.size() != 0)) { const std::vector& uaTypes = m_transmit_programme ? (*componentFIG0_13)->audio.uaTypes : (*componentFIG0_13)->packet.uaTypes; const size_t num_apps = uaTypes.size(); static_assert(sizeof(FIG0_13_shortAppInfo) == 3); static_assert(sizeof(FIG0_13_longAppInfo) == 5); static_assert(sizeof(FIG0_13_app) == 2); size_t xpaddata_length = 0; int required_size = 0; if (m_transmit_programme) { required_size += sizeof(FIG0_13_shortAppInfo); xpaddata_length = 2; // CAOrg is always absent } else { required_size += sizeof(FIG0_13_longAppInfo); xpaddata_length = 0; // X-PAD data field is absent } for (const auto& ua : uaTypes) { required_size += sizeof(FIG0_13_app) + xpaddata_length; if (ua.uaType == FIG0_13_APPTYPE_SPI) { required_size += 1; } else if (ua.uaType == FIG0_13_APPTYPE_WEBSITE || ua.uaType == FIG0_13_APPTYPE_JOURNALINE) { required_size += 2; } } if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = m_transmit_programme ? 0 : 1; fig0->Extension = 13; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } if (m_transmit_programme) { FIG0_13_shortAppInfo* info = (FIG0_13_shortAppInfo*)buf; info->SId = htonl((*componentFIG0_13)->serviceId) >> 16; info->SCIdS = (*componentFIG0_13)->SCIdS; info->No = num_apps; buf += sizeof(FIG0_13_shortAppInfo); remaining -= sizeof(FIG0_13_shortAppInfo); fig0->Length += sizeof(FIG0_13_shortAppInfo); } else { FIG0_13_longAppInfo* info = (FIG0_13_longAppInfo*)buf; info->SId = htonl((*componentFIG0_13)->serviceId); info->SCIdS = (*componentFIG0_13)->SCIdS; info->No = num_apps; buf += sizeof(FIG0_13_longAppInfo); remaining -= sizeof(FIG0_13_longAppInfo); fig0->Length += sizeof(FIG0_13_longAppInfo); } for (const auto& ua : uaTypes) { FIG0_13_app* app = (FIG0_13_app*)buf; app->setType(ua.uaType); app->length = xpaddata_length; if (ua.uaType == FIG0_13_APPTYPE_SPI) { app->length += 1; } else if (ua.uaType == FIG0_13_APPTYPE_WEBSITE || ua.uaType == FIG0_13_APPTYPE_JOURNALINE) { app->length += 2; } buf += sizeof(FIG0_13_app); remaining -= sizeof(FIG0_13_app); fig0->Length += sizeof(FIG0_13_app); if (m_transmit_programme) { const uint8_t dscty = 60; // TS 101 756 Table 2b (MOT) const uint16_t xpadapp = htons((ua.xpadAppType << 8) | dscty); /* xpad meaning CA = 0 CAOrg = 0 (CAOrg field absent) Rfu = 0 AppTy(5) = depending on config DG = 0 (MSC data groups used) Rfu = 0 DSCTy(6) = 60 (MOT) */ memcpy(buf, &xpadapp, 2); buf += 2; remaining -= 2; fig0->Length += 2; } if (ua.uaType == FIG0_13_APPTYPE_SPI) { buf[0] = 0x01; // = basic profile buf += 1; remaining -= 1; fig0->Length += 1; } else if (ua.uaType == FIG0_13_APPTYPE_WEBSITE) { buf[0] = 0x01; // = basic integrated receiver profile buf[1] = 0xFF; // = unrestricted (PC) profile buf += 2; remaining -= 2; fig0->Length += 2; } else if (ua.uaType == FIG0_13_APPTYPE_JOURNALINE) { // According to ETSI TS 102 979 Clause 8.1.2 buf[0] = 0x00; buf[1] = 0x00; buf += 2; remaining -= 2; fig0->Length += 2; } } } } if (componentFIG0_13 == ensemble->components.end()) { componentFIG0_13 = ensemble->components.begin(); // The full database is sent every second full loop fs.complete_fig_transmitted = m_transmit_programme; m_transmit_programme = not m_transmit_programme; // Alternate between data and and programme FIG0/13, // do not mix fig0 with PD=0 with extension 13 stuff // that actually needs PD=1, and vice versa } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_13.h000066400000000000000000000030471476627344300216430ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include #include "fig/FIG.h" namespace FIC { // FIG type 0/13 // User Application Information class FIG0_13 : public IFIG { public: FIG0_13(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 13; } private: FIGRuntimeInformation *m_rti; bool m_initialised; bool m_transmit_programme; vec_sp_component::iterator componentFIG0_13; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_14.cpp000066400000000000000000000070411476627344300221750ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li Copyright (C) 2022 Nick Piggott, nick@piggott.eu */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_14.h" #include "utils.h" namespace FIC { // See EN 300 401, Clause 6.2.2 for the FIG0/14 description struct FIG0_14_AppInfo { uint8_t fecScheme:2; uint8_t subchId:6; } PACKED; FIG0_14::FIG0_14(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false) { } FillStatus FIG0_14::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; ssize_t remaining = max_size; if (not m_initialised) { componentFIG0_14 = m_rti->ensemble->components.begin(); m_initialised = true; } FIGtype0 *fig0 = nullptr; for (; componentFIG0_14 != ensemble->components.end(); ++componentFIG0_14) { const auto subchannel = getSubchannel( ensemble->subchannels, (*componentFIG0_14)->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "Subchannel %i does not exist for component " "of service %i", (*componentFIG0_14)->subchId, (*componentFIG0_14)->serviceId); continue; } if ((*subchannel)->type == subchannel_type_t::Packet and (*subchannel)->packet_enhanced) { int required_size = 1; // FIG0_14 data field is 6 bits subchanID and 2 bits fec scheme if (fig0 == nullptr) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = required_size; fig0->CN = 0; fig0->OE = 0; // Rfu fig0->PD = 0; // Rfu fig0->Extension = 14; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } static_assert(sizeof(FIG0_14_AppInfo) == 1); /* No alignment issues in this cast because the sizeof() == 1 */ FIG0_14_AppInfo* app = (FIG0_14_AppInfo*)buf; app->subchId = (*componentFIG0_14)->subchId; app->fecScheme = 1; // 1=FEC Scheme according to EN 300 401 clause 5.3.5 is the only value allowed buf += 1; remaining -= 1; fig0->Length += 1; } } if (componentFIG0_14 == ensemble->components.end()) { componentFIG0_14 = ensemble->components.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_14.h000066400000000000000000000030471476627344300216440ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li Copyright (C) 2022 Nick Piggott, nick@piggott.eu */ /* 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 . */ #pragma once #include #include namespace FIC { // FIG type 0/14 // FEC sub-channel organization class FIG0_14 : public IFIG { public: FIG0_14(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 14; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_component::iterator componentFIG0_14; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_17.cpp000066400000000000000000000056771476627344300222150ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_17.h" #include "utils.h" namespace FIC { struct FIGtype0_17 { uint16_t SId; uint8_t rfa2_high:4; uint8_t rfu1:2; uint8_t rfa1:1; uint8_t SD:1; // Static/Dynamic uint8_t IntCode:5; uint8_t rfu2:1; uint8_t rfa2_low:2; } PACKED; FIG0_17::FIG0_17(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false) { } FillStatus FIG0_17::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { serviceFIG0_17 = m_rti->ensemble->services.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; FIGtype0* fig0 = NULL; for (; serviceFIG0_17 != ensemble->services.end(); ++serviceFIG0_17) { if ((*serviceFIG0_17)->pty_settings.pty == 0) { continue; } const int required_size = 4; if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 0; fig0->Extension = 17; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } auto fig0_17 = (FIGtype0_17*)buf; fig0_17->SId = htons((*serviceFIG0_17)->id); fig0_17->SD = ((*serviceFIG0_17)->pty_settings.dynamic_no_static ? 1 : 0); fig0_17->rfa1 = 0; fig0_17->rfu1 = 0; fig0_17->rfa2_low = 0; fig0_17->rfa2_high = 0; fig0_17->rfu2 = 0; fig0_17->IntCode = (*serviceFIG0_17)->pty_settings.pty; fig0->Length += 4; buf += 4; remaining -= 4; } if (serviceFIG0_17 == ensemble->services.end()) { serviceFIG0_17 = ensemble->services.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_17.h000066400000000000000000000027461476627344300216540ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include "fig/FIG.h" namespace FIC { // FIG type 0/17 // Programme Type (PTy) class FIG0_17 : public IFIG { public: FIG0_17(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 17; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_service::iterator serviceFIG0_17; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_18.cpp000066400000000000000000000055601476627344300222050ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_18.h" #include "utils.h" namespace FIC { struct FIGtype0_18 { uint16_t SId; uint16_t ASu; uint8_t NumClusters:5; uint8_t Rfa:3; /* Followed by uint8_t Cluster IDs */ } PACKED; FIG0_18::FIG0_18(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false) { } FillStatus FIG0_18::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { service = m_rti->ensemble->services.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; FIGtype0* fig0 = NULL; for (; service != ensemble->services.end(); ++service) { if ( (*service)->ASu == 0 ) { continue; } const ssize_t numclusters = (*service)->clusters.size(); const int required_size = 5 + numclusters; if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 0; fig0->Extension = 18; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } auto programme = (FIGtype0_18*)buf; programme->SId = htons((*service)->id); programme->ASu = htons((*service)->ASu); programme->Rfa = 0; programme->NumClusters = numclusters; buf += 5; for (uint8_t cluster : (*service)->clusters) { *(buf++) = cluster; } fig0->Length += 5 + numclusters; remaining -= 5 + numclusters; } if (service == ensemble->services.end()) { service = ensemble->services.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_18.h000066400000000000000000000027041476627344300216470ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include namespace FIC { // FIG type 0/18 class FIG0_18 : public IFIG { public: FIG0_18(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 18; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_service::iterator service; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_19.cpp000066400000000000000000000121111476627344300221740ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_19.h" #include "utils.h" namespace FIC { struct FIGtype0_19 { uint8_t ClusterId; uint16_t ASw; uint8_t SubChId:6; uint8_t RegionFlag:1; // shall be zero uint8_t NewFlag:1; // Region and RFa not supported } PACKED; FIG0_19::FIG0_19(FIGRuntimeInformation *rti) : m_rti(rti) { } #define FIG0_19_TRACE discard FillStatus FIG0_19::fill(uint8_t *buf, size_t max_size) { using namespace std; auto ensemble = m_rti->ensemble; m_transition.update_state(std::chrono::seconds(2), ensemble->clusters); FillStatus fs; ssize_t remaining = max_size; FIGtype0* fig0 = NULL; // Combine all clusters into one list set allclusters; for (const auto& cluster : m_transition.new_entries) { allclusters.insert(cluster.first.get()); } for (const auto& cluster : m_transition.repeated_entries) { allclusters.insert(cluster.get()); } for (const auto& cluster : m_transition.disabled_entries) { allclusters.insert(cluster.first.get()); } const int length_0_19 = 4; fs.complete_fig_transmitted = true; etiLog.level(FIG0_19_TRACE) << "FIG0_19::loop with " << allclusters.size() << " clusters"; for (auto& cluster : allclusters) { etiLog.level(FIG0_19_TRACE) << "FIG0_19::cluster " << cluster->cluster_id; if (fig0 == NULL) { if (remaining < 2 + length_0_19) { fs.complete_fig_transmitted = false; etiLog.level(FIG0_19_TRACE) << "FIG0_19::no space FIG0"; break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 0; fig0->Extension = 19; buf += 2; remaining -= 2; } else if (remaining < length_0_19) { etiLog.level(FIG0_19_TRACE) << "FIG0_19::no space FIG0/19"; fs.complete_fig_transmitted = false; break; } auto fig0_19 = (FIGtype0_19*)buf; fig0_19->ClusterId = cluster->cluster_id; if (cluster->is_active()) { fig0_19->ASw = htons(cluster->flags); } else { fig0_19->ASw = 0; } /* From the crc-mmbtools google groups, 2019-07-11, L. Cornell: * * Long ago, there was a defined use for the New flag - it was intended * to indicate whether the announcement was new or was a repeated * announcement. But the problem is that it doesn't really help * receivers because they might tune to the ensemble at any time, and * might tune to a service that may be interrupted at any time. So * some years ago it was decided that the New flag would not longer be * used in transmissions. The setting was fixed to be 1 because some * receivers would have never switched to the announcement if the flag * was set to 0. */ fig0_19->NewFlag = 1; fig0_19->RegionFlag = 0; fig0_19->SubChId = 0; bool found = false; for (const auto& subchannel : ensemble->subchannels) { if (subchannel->uid == cluster->subchanneluid) { fig0_19->SubChId = subchannel->id; found = true; break; } } if (not found) { etiLog.level(warn) << "FIG0/19: could not find subchannel " << cluster->subchanneluid << " for cluster " << (int)cluster->cluster_id; continue; } etiLog.level(FIG0_19_TRACE) << "FIG0_19::advance " << length_0_19; fig0->Length += length_0_19; buf += length_0_19; remaining -= length_0_19; } fs.num_bytes_written = max_size - remaining; etiLog.level(FIG0_19_TRACE) << "FIG0_19::out " << fs.num_bytes_written; return fs; } FIG_rate FIG0_19::repetition_rate() const { if ( m_transition.new_entries.size() > 0 or m_transition.disabled_entries.size() ) { return FIG_rate::A_B; } else { return FIG_rate::B; } } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_19.h000066400000000000000000000027101476627344300216450ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include #include "fig/TransitionHandler.h" namespace FIC { // FIG type 0/19 class FIG0_19 : public IFIG { public: FIG0_19(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const; virtual int figtype() const { return 0; } virtual int figextension() const { return 19; } private: FIGRuntimeInformation *m_rti; TransitionHandler m_transition; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_2.cpp000066400000000000000000000256071476627344300221220ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_2.h" #include "utils.h" namespace FIC { struct FIGtype0_2 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; } PACKED; struct FIGtype0_2_Service { uint16_t SId; uint8_t NbServiceComp:4; uint8_t CAId:3; uint8_t Local_flag:1; } PACKED; struct FIGtype0_2_Service_data { uint32_t SId; uint8_t NbServiceComp:4; uint8_t CAId:3; uint8_t Local_flag:1; } PACKED; struct FIGtype0_2_audio_component { uint8_t ASCTy:6; uint8_t TMid:2; uint8_t CA_flag:1; uint8_t PS:1; uint8_t SubChId:6; } PACKED; struct FIGtype0_2_data_component { uint8_t DSCTy:6; uint8_t TMid:2; uint8_t CA_flag:1; uint8_t PS:1; uint8_t SubChId:6; } PACKED; struct FIGtype0_2_packet_component { uint8_t SCId_high:6; uint8_t TMid:2; uint8_t CA_flag:1; uint8_t PS:1; uint8_t SCId_low:6; void setSCId(uint16_t SCId) { SCId_high = SCId >> 6; SCId_low = SCId & 0x3f; } } PACKED; FIG0_2::FIG0_2(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false), m_inserting_audio_not_data(false) { } FillStatus FIG0_2::fill(uint8_t *buf, size_t max_size) { #define FIG0_2_TRACE discard using namespace std; FillStatus fs; FIGtype0_2 *fig0_2 = NULL; int cur = 0; ssize_t remaining = max_size; const auto ensemble = m_rti->ensemble; etiLog.level(FIG0_2_TRACE) << "FIG0_2::fill init " << (m_initialised ? 1 : 0) << " ********************************"; if (not m_initialised) { m_audio_services.clear(); copy_if(ensemble->services.begin(), ensemble->services.end(), back_inserter(m_audio_services), [&](shared_ptr& s) { return s->isProgramme(ensemble); } ); m_data_services.clear(); copy_if(ensemble->services.begin(), ensemble->services.end(), back_inserter(m_data_services), [&](shared_ptr& s) { return not s->isProgramme(ensemble); } ); m_initialised = true; m_inserting_audio_not_data = !m_inserting_audio_not_data; if (m_inserting_audio_not_data) { serviceFIG0_2 = m_audio_services.begin(); } else { serviceFIG0_2 = m_data_services.begin(); } etiLog.level(FIG0_2_TRACE) << "FIG0_2::fill we have " << m_audio_services.size() << " audio and " << m_data_services.size() << " data services. Inserting " << (m_inserting_audio_not_data ? "AUDIO" : "DATA"); } const auto last_service = m_inserting_audio_not_data ? m_audio_services.end() : m_data_services.end(); // Rotate through the subchannels until there is no more // space for (; serviceFIG0_2 != last_service; ++serviceFIG0_2) { const auto type = (*serviceFIG0_2)->getType(ensemble); const auto isProgramme = (*serviceFIG0_2)->isProgramme(ensemble); etiLog.log(FIG0_2_TRACE, "FIG0_2::fill loop SId=%04x %s/%s", (*serviceFIG0_2)->id, m_inserting_audio_not_data ? "AUDIO" : "DATA", type == subchannel_type_t::DABPlusAudio ? "DABPlusAudio" : type == subchannel_type_t::DABAudio ? "DABAudio" : type == subchannel_type_t::Packet ? "Packet" : type == subchannel_type_t::DataDmb ? "DataDmb" : "?"); // filter out services which have no components if ((*serviceFIG0_2)->nbComponent(ensemble->components) == 0) { etiLog.level(FIG0_2_TRACE) << "FIG0_2::fill no components "; continue; } ++cur; const int required_size = isProgramme ? 3 + 2 * (*serviceFIG0_2)->nbComponent(ensemble->components) : 5 + 2 * (*serviceFIG0_2)->nbComponent(ensemble->components); if (fig0_2 == NULL) { etiLog.level(FIG0_2_TRACE) << "FIG0_2::fill header"; if (remaining < 2 + required_size) { etiLog.level(FIG0_2_TRACE) << "FIG0_2::fill header no place" << " rem=" << remaining << " req=" << 2 + required_size; break; } fig0_2 = (FIGtype0_2 *)buf; fig0_2->FIGtypeNumber = 0; fig0_2->Length = 1; fig0_2->CN = 0; fig0_2->OE = 0; fig0_2->PD = isProgramme ? 0 : 1; fig0_2->Extension = 2; buf += 2; remaining -= 2; } else if (remaining < required_size) { etiLog.level(FIG0_2_TRACE) << "FIG0_2::fill no place" << " rem=" << remaining << " req=" << required_size; break; } if (type == subchannel_type_t::DABPlusAudio or type == subchannel_type_t::DABAudio) { auto fig0_2serviceAudio = (FIGtype0_2_Service*)buf; fig0_2serviceAudio->SId = htons((*serviceFIG0_2)->id); fig0_2serviceAudio->Local_flag = 0; fig0_2serviceAudio->CAId = 0; fig0_2serviceAudio->NbServiceComp = (*serviceFIG0_2)->nbComponent(ensemble->components); buf += 3; fig0_2->Length += 3; remaining -= 3; etiLog.log(FIG0_2_TRACE, "FIG0_2::fill audio SId=%04x", (*serviceFIG0_2)->id); } else { auto fig0_2serviceData = (FIGtype0_2_Service_data*)buf; fig0_2serviceData->SId = htonl((*serviceFIG0_2)->id); fig0_2serviceData->Local_flag = 0; fig0_2serviceData->CAId = 0; fig0_2serviceData->NbServiceComp = (*serviceFIG0_2)->nbComponent(ensemble->components); buf += 5; fig0_2->Length += 5; remaining -= 5; etiLog.log(FIG0_2_TRACE, "FIG0_2::fill data SId=%04x", (*serviceFIG0_2)->id); } int curCpnt = 0; for (auto component = getComponent( ensemble->components, (*serviceFIG0_2)->id ); component != ensemble->components.end(); component = getComponent( ensemble->components, (*serviceFIG0_2)->id, component ) ) { auto subchannel = getSubchannel( ensemble->subchannels, (*component)->subchId); etiLog.log(FIG0_2_TRACE, "FIG0_2::fill comp sub=%04x srv=%04x", (*component)->subchId, (*component)->serviceId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "Subchannel %i does not exist for component " "of service %i", (*component)->subchId, (*component)->serviceId); continue; } switch ((*subchannel)->type) { case (subchannel_type_t::DABAudio): { auto audio_description = (FIGtype0_2_audio_component*)buf; audio_description->TMid = 0; audio_description->ASCTy = (*component)->type; audio_description->SubChId = (*subchannel)->id; audio_description->PS = ((curCpnt == 0) ? 1 : 0); audio_description->CA_flag = 0; } break; case (subchannel_type_t::DABPlusAudio): { auto audio_description = (FIGtype0_2_audio_component*)buf; audio_description->TMid = 0; audio_description->ASCTy = (*component)->type; audio_description->SubChId = (*subchannel)->id; audio_description->PS = ((curCpnt == 0) ? 1 : 0); audio_description->CA_flag = 0; } break; case subchannel_type_t::DataDmb: { auto data_description = (FIGtype0_2_data_component*)buf; data_description->TMid = 1; data_description->DSCTy = (*component)->type; data_description->SubChId = (*subchannel)->id; data_description->PS = ((curCpnt == 0) ? 1 : 0); data_description->CA_flag = 0; } break; case subchannel_type_t::Packet: { auto packet_description = (FIGtype0_2_packet_component*)buf; packet_description->TMid = 3; packet_description->setSCId((*component)->packet.id); packet_description->PS = ((curCpnt == 0) ? 1 : 0); packet_description->CA_flag = 0; } break; default: throw logic_error("Component type not supported"); } buf += 2; fig0_2->Length += 2; remaining -= 2; if (remaining < 0) { etiLog.log(error, "Sorry, no space left in FIG 0/2 to insert " "component %i of program service %i.", curCpnt, cur); throw MuxInitException(); } ++curCpnt; etiLog.log(FIG0_2_TRACE, "FIG0_2::fill comp length=%d", fig0_2->Length); } } if (serviceFIG0_2 == last_service) { etiLog.log(FIG0_2_TRACE, "FIG0_2::loop reached last"); m_initialised = false; fs.complete_fig_transmitted = !m_inserting_audio_not_data; } etiLog.log(FIG0_2_TRACE, "FIG0_2::loop end complete=%d", fs.complete_fig_transmitted ? 1 : 0); fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_2.h000066400000000000000000000032521476627344300215570ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include #include "fig/FIG.h" namespace FIC { // FIG type 0/2, MCI, Service Organization, one instance of // FIGtype0_2_Service for each subchannel class FIG0_2 : public IFIG { public: FIG0_2(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::A; } virtual int figtype() const { return 0; } virtual int figextension() const { return 2; } private: FIGRuntimeInformation *m_rti; bool m_initialised; bool m_inserting_audio_not_data; vec_sp_service m_audio_services; vec_sp_service m_data_services; vec_sp_service::iterator serviceFIG0_2; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_21.cpp000066400000000000000000000320361476627344300221750ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_21.h" #include "utils.h" using namespace std; namespace FIC { struct FIGtype0_21_header { // This was RegionId in EN 300 401 V1.4.1 uint8_t rfaHigh; uint8_t length_fi:5; uint8_t rfaLow:3; } PACKED; struct FIGtype0_21_fi_list_header { uint8_t idHigh; uint8_t idLow; uint8_t length_freq_list:3; uint8_t continuity:1; uint8_t range_modulation:4; void setId(uint16_t id) { idHigh = id >> 8; idLow = id & 0xFF; } void addToLength(uint8_t increment) { const uint32_t newlen = length_freq_list + increment; if (newlen > 0x7) { throw logic_error("FI freq list too long: " + to_string(newlen)); } length_freq_list = newlen; } } PACKED; struct FIGtype0_21_fi_dab_entry { uint8_t freqHigh:3; uint8_t control_field:5; uint8_t freqMid; uint8_t freqLow; void setFreq(uint32_t freq) { freqHigh = (freq >> 16) & 0x7; freqMid = (freq >> 8) & 0xff; freqLow = freq & 0xff; } } PACKED; FIG0_21::FIG0_21(FIGRuntimeInformation *rti) : m_rti(rti) { } static size_t get_num_frequencies( std::vector::iterator fi) { switch (fi->rm) { case RangeModulation::dab_ensemble: if (fi->fi_dab.frequencies.empty()) { throw std::logic_error("Empty DAB FI"); } return fi->fi_dab.frequencies.size(); case RangeModulation::fm_with_rds: if (fi->fi_fm.frequencies.empty()) { throw std::logic_error("Empty FM FI"); } return fi->fi_fm.frequencies.size(); case RangeModulation::amss: if (fi->fi_amss.frequencies.empty()) { throw std::logic_error("Empty AMSS FI"); } return fi->fi_amss.frequencies.size(); case RangeModulation::drm: if (fi->fi_drm.frequencies.empty()) { throw std::logic_error("Empty DRM FI"); } return fi->fi_drm.frequencies.size(); } throw logic_error("Unhandled get_num_frequencies"); } FillStatus FIG0_21::fill(uint8_t *buf, size_t max_size) { #define FIG0_21_TRACE discard FillStatus fs; size_t remaining = max_size; auto ensemble = m_rti->ensemble; if (not m_initialised) { freqInfoFIG0_21 = ensemble->frequency_information.begin(); fi_frequency_index = 0; if (freqInfoFIG0_21 != ensemble->frequency_information.end()) { m_last_oe = freqInfoFIG0_21->other_ensemble; } m_initialised = true; } FIGtype0* fig0 = nullptr; auto advance_loop = [&](void){ if (fi_frequency_index == get_num_frequencies(freqInfoFIG0_21)) { ++freqInfoFIG0_21; fi_frequency_index = 0; } }; for (; freqInfoFIG0_21 != ensemble->frequency_information.end(); advance_loop()) { /* For better usage of FIC capacity, we want to transmit * frequency lists with * 2 DAB frequencies, * 7 FM frequencies, * 3 AM/DRM frequencies */ // Check we have space for one frequency size_t required_fi_size = 2; // RegionId + length of FI list size_t list_entry_size = sizeof(struct FIGtype0_21_fi_list_header); switch (freqInfoFIG0_21->rm) { case RangeModulation::dab_ensemble: list_entry_size += 3; break; case RangeModulation::fm_with_rds: list_entry_size += 1; break; case RangeModulation::amss: list_entry_size += 1; // Id field 2 list_entry_size += 2; break; case RangeModulation::drm: list_entry_size += 1; // Id field 2 list_entry_size += 2; break; } required_fi_size += list_entry_size; const size_t required_size = sizeof(struct FIGtype0_21_header) + required_fi_size; if (m_last_oe != freqInfoFIG0_21->other_ensemble) { // Trigger resend of FIG0 when OE changes fig0 = nullptr; m_last_oe = freqInfoFIG0_21->other_ensemble; etiLog.level(FIG0_21_TRACE) << "FIG0_21::switch OE to " << freqInfoFIG0_21->other_ensemble; } if (fig0 == nullptr) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; // Database start or continuation flag, EN 300 401 Clause 5.2.2.1 part b) fig0->CN = (fi_frequency_index == 0 ? 0 : 1); fig0->OE = freqInfoFIG0_21->other_ensemble ? 1 : 0; fig0->PD = false; fig0->Extension = 21; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } etiLog.level(FIG0_21_TRACE) << "FIG0_21::loop " << freqInfoFIG0_21->uid << " " << std::distance(ensemble->frequency_information.begin(), freqInfoFIG0_21) << " freq entry " << fi_frequency_index; auto *fig0_21_header = (FIGtype0_21_header*)buf; fig0_21_header->rfaHigh = 0; fig0_21_header->rfaLow = 0; fig0_21_header->length_fi = sizeof(FIGtype0_21_fi_list_header); fig0->Length += sizeof(FIGtype0_21_header); buf += sizeof(FIGtype0_21_header); remaining -= sizeof(FIGtype0_21_header); auto *fi_list_header = (FIGtype0_21_fi_list_header*)buf; fig0->Length += sizeof(FIGtype0_21_fi_list_header); buf += sizeof(FIGtype0_21_fi_list_header); remaining -= sizeof(FIGtype0_21_fi_list_header); fi_list_header->continuity = freqInfoFIG0_21->continuity; fi_list_header->length_freq_list = 0; fi_list_header->range_modulation = static_cast(freqInfoFIG0_21->rm); bool continue_loop = true; switch (freqInfoFIG0_21->rm) { case RangeModulation::dab_ensemble: fi_list_header->setId(freqInfoFIG0_21->fi_dab.eid); for (size_t num_inserted = 0, i = fi_frequency_index; num_inserted < 2 and i < freqInfoFIG0_21->fi_dab.frequencies.size(); num_inserted++, i++) { if (remaining < 3) { continue_loop = false; break; } const auto& freq = freqInfoFIG0_21->fi_dab.frequencies.at(i); auto *field = (FIGtype0_21_fi_dab_entry*)buf; field->control_field = static_cast(freq.control_field); field->setFreq(static_cast( freq.frequency * 1000.0f / 16.0f)); fig0_21_header->length_fi += 3; fi_list_header->addToLength(3); fig0->Length += 3; buf += 3; remaining -= 3; etiLog.level(FIG0_21_TRACE) << "FIG0_21::freq " << fi_frequency_index << " " << freq.frequency << " rem " << remaining; fi_frequency_index++; } break; case RangeModulation::fm_with_rds: fi_list_header->setId(freqInfoFIG0_21->fi_fm.pi_code); for (size_t num_inserted = 0, i = fi_frequency_index; num_inserted < 7 and i < freqInfoFIG0_21->fi_fm.frequencies.size(); num_inserted++, i++) { if (remaining < 1) { continue_loop = false; break; } const auto& freq = freqInfoFIG0_21->fi_fm.frequencies.at(i); // RealFreq = 87.5 MHz + (F * 100kHz) // => F = (RealFreq - 87.5 MHz) / 100kHz // Do the whole calculation in kHz: *buf = (freq * 1000.0f - 87500.0f) / 100.0f; fig0_21_header->length_fi += 1; fi_list_header->addToLength(1); fig0->Length += 1; buf += 1; remaining -= 1; fi_frequency_index++; } break; case RangeModulation::drm: fi_list_header->setId((freqInfoFIG0_21->fi_drm.drm_service_id) & 0xFFFF); if (remaining < 3) { throw logic_error("Incorrect DRM FI size calculation"); } // Id field 2 *buf = (freqInfoFIG0_21->fi_drm.drm_service_id >> 16) & 0xFF; fig0_21_header->length_fi += 1; fi_list_header->addToLength(1); fig0->Length += 1; buf += 1; remaining -= 1; for (size_t num_inserted = 0, i = fi_frequency_index; num_inserted < 3 and i < freqInfoFIG0_21->fi_drm.frequencies.size(); num_inserted++, i++) { if (remaining < 2) { continue_loop = false; break; } const auto& freq = freqInfoFIG0_21->fi_drm.frequencies.at(i); uint16_t freq_field = static_cast(freq * 1000.0f); buf[0] = freq_field >> 8; buf[1] = freq_field & 0xFF; fi_list_header->addToLength(2); fig0_21_header->length_fi += 2; fig0->Length += 2; buf += 2; remaining -= 2; fi_frequency_index++; } break; case RangeModulation::amss: fi_list_header->setId((freqInfoFIG0_21->fi_amss.amss_service_id) & 0xFFFF); if (remaining < 3) { throw logic_error("Incorrect AMSS FI size calculation"); } // Id field 2 *buf = (freqInfoFIG0_21->fi_amss.amss_service_id >> 16) & 0xFF; fig0_21_header->length_fi += 1; fi_list_header->addToLength(1); fig0->Length += 1; buf += 1; remaining -= 1; for (size_t num_inserted = 0, i = fi_frequency_index; num_inserted < 3 and i < freqInfoFIG0_21->fi_amss.frequencies.size(); num_inserted++, i++) { if (remaining < 2) { continue_loop = false; break; } const auto& freq = freqInfoFIG0_21->fi_amss.frequencies.at(i); uint16_t freq_field = static_cast(freq * 1000.0f); buf[0] = freq_field >> 8; buf[1] = freq_field & 0xFF; fi_list_header->addToLength(2); fig0_21_header->length_fi += 2; fig0->Length += 2; buf += 2; remaining -= 2; fi_frequency_index++; } break; } // switch (RM) etiLog.level(FIG0_21_TRACE) << "FIG0_21::end " << (continue_loop ? "same " : "next ") << " len=" << static_cast(fig0->Length) << " rem=" << remaining << " ********************************"; if (not continue_loop) { // There is no more space left. The freqInfoFIG0_21 iterator // and fi_frequency_index save the position how to continue // next time we are called. break; } } // for over FI if (freqInfoFIG0_21 == ensemble->frequency_information.end()) { fs.complete_fig_transmitted = true; freqInfoFIG0_21 = ensemble->frequency_information.begin(); fi_frequency_index = 0; } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_21.h000066400000000000000000000031341476627344300216370ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 */ /* 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 . */ #pragma once #include #include #include namespace FIC { // FIG type 0/21 // Frequency Information class FIG0_21 : public IFIG { public: FIG0_21(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::E; } virtual int figtype() const { return 0; } virtual int figextension() const { return 21; } private: FIGRuntimeInformation *m_rti; bool m_initialised = false; bool m_last_oe = false; std::vector::iterator freqInfoFIG0_21; size_t fi_frequency_index = 0; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_24.cpp000066400000000000000000000142151476627344300221770ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_24.h" #include "utils.h" /* FIG0/24 allows us to announce if a service is available in another ensemble. * Things we do not support: * * - The CEI using length=0, because this information is not available in the * remote control, and there is no runtime changes. */ namespace FIC { struct FIGtype0_24_audioservice { uint16_t SId; uint8_t Length:4; uint8_t CAId:3; uint8_t rfa:1; } PACKED; struct FIGtype0_24_dataservice { uint32_t SId; uint8_t Length:4; uint8_t CAId:3; uint8_t rfa:1; } PACKED; FIG0_24::FIG0_24(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false), m_inserting_audio_not_data(false) { } FillStatus FIG0_24::fill(uint8_t *buf, size_t max_size) { #define FIG0_24_TRACE discard using namespace std; FillStatus fs; FIGtype0* fig0 = nullptr; ssize_t remaining = max_size; const auto ensemble = m_rti->ensemble; etiLog.level(FIG0_24_TRACE) << "FIG0_24::fill init " << (m_initialised ? 1 : 0) << " ********************************"; if (not m_initialised) { serviceFIG0_24 = ensemble->service_other_ensemble.begin(); m_initialised = true; } const auto last_service = ensemble->service_other_ensemble.end(); bool last_oe = false; // Rotate through the subchannels until there is no more // space for (; serviceFIG0_24 != last_service; ++serviceFIG0_24) { shared_ptr service; for (const auto& local_service : ensemble->services) { if (local_service->id == serviceFIG0_24->service_id) { service = local_service; break; } } subchannel_type_t type = subchannel_type_t::DABAudio; bool isProgramme = true; bool oe = true; if (service) { oe = false; type = service->getType(ensemble); isProgramme = service->isProgramme(ensemble); } etiLog.log(FIG0_24_TRACE, "FIG0_24::fill loop OE=%d SId=%04x %s/%s", oe, serviceFIG0_24->service_id, m_inserting_audio_not_data ? "AUDIO" : "DATA", type == subchannel_type_t::DABAudio ? "Audio" : type == subchannel_type_t::Packet ? "Packet" : type == subchannel_type_t::DataDmb ? "DataDmb" : "?"); if (last_oe != oe) { fig0 = nullptr; } const ssize_t required_size = (isProgramme ? 2 : 4) + 1 + serviceFIG0_24->other_ensembles.size() * 2; if (fig0 == nullptr) { etiLog.level(FIG0_24_TRACE) << "FIG0_24::fill header"; if (remaining < 2 + required_size) { etiLog.level(FIG0_24_TRACE) << "FIG0_24::fill header no place" << " rem=" << remaining << " req=" << 2 + required_size; break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; // CN according to ETSI TS 103 176, Clause 5.3.4.1 bool isFirst = serviceFIG0_24 == ensemble->service_other_ensemble.begin(); fig0->CN = (isFirst ? 0 : 1); fig0->OE = oe; fig0->PD = isProgramme ? 0 : 1; fig0->Extension = 24; buf += 2; remaining -= 2; } else if (remaining < required_size) { etiLog.level(FIG0_24_TRACE) << "FIG0_24::fill no place" << " rem=" << remaining << " req=" << required_size; break; } if (isProgramme) { auto fig0_24_audioservice = (FIGtype0_24_audioservice*)buf; fig0_24_audioservice->SId = htons(serviceFIG0_24->service_id); fig0_24_audioservice->rfa = 0; fig0_24_audioservice->CAId = 0; fig0_24_audioservice->Length = serviceFIG0_24->other_ensembles.size(); buf += 3; fig0->Length += 3; remaining -= 3; etiLog.log(FIG0_24_TRACE, "FIG0_24::fill audio SId=%04x", serviceFIG0_24->service_id); } else { auto fig0_24_dataservice = (FIGtype0_24_dataservice*)buf; fig0_24_dataservice->SId = htonl(serviceFIG0_24->service_id); fig0_24_dataservice->rfa = 0; fig0_24_dataservice->CAId = 0; fig0_24_dataservice->Length = serviceFIG0_24->other_ensembles.size(); buf += 4; fig0->Length += 4; remaining -= 4; etiLog.log(FIG0_24_TRACE, "FIG0_24::fill data SId=%04x", serviceFIG0_24->service_id); } for (const uint16_t oe : serviceFIG0_24->other_ensembles) { buf[0] = oe >> 8; buf[1] = oe & 0xFF; buf += 2; fig0->Length += 2; remaining -= 2; } } if (serviceFIG0_24 == last_service) { etiLog.log(FIG0_24_TRACE, "FIG0_24::loop reached last"); fs.complete_fig_transmitted = true; m_initialised = false; } etiLog.log(FIG0_24_TRACE, "FIG0_24::loop end complete=%d %d", fs.complete_fig_transmitted ? 1 : 0, max_size - remaining); fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_24.h000066400000000000000000000031671476627344300216500ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 */ /* 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 . */ #pragma once #include #include #include "FIG.h" namespace FIC { /* The OE Services feature is used to identify the services currently carried * in other DAB ensembles. */ class FIG0_24 : public IFIG { public: FIG0_24(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::E; } virtual int figtype() const { return 0; } virtual int figextension() const { return 24; } private: FIGRuntimeInformation *m_rti; bool m_initialised; bool m_inserting_audio_not_data; std::vector::iterator serviceFIG0_24; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_3.cpp000066400000000000000000000105711476627344300221150ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_3.h" #include "utils.h" namespace FIC { /* Warning: When bit SCCA_flag is unset(0), the multiplexer R&S does not send * the SCCA field. But, in the Factum ETI analyzer, if this field is not there, * it is an error. */ struct FIGtype0_3 { uint8_t SCId_high; uint8_t SCCA_flag:1; uint8_t rfa:3; uint8_t SCId_low:4; uint8_t DSCTy:6; uint8_t rfu:1; uint8_t DG_flag:1; uint8_t Packet_address_high:2; uint8_t SubChId:6; uint8_t Packet_address_low; uint16_t SCCA; void setSCId(uint16_t SCId) { SCId_high = SCId >> 4; SCId_low = SCId & 0xf; } void setPacketAddress(uint16_t address) { Packet_address_high = address >> 8; Packet_address_low = address & 0xff; } } PACKED; FIG0_3::FIG0_3(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false) { } FillStatus FIG0_3::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; auto ensemble = m_rti->ensemble; if (not m_initialised) { componentFIG0_3 = m_rti->ensemble->components.end(); m_initialised = true; } FIGtype0 *fig0 = NULL; for (; componentFIG0_3 != ensemble->components.end(); ++componentFIG0_3) { auto subchannel = getSubchannel(ensemble->subchannels, (*componentFIG0_3)->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "FIG0/3: Subchannel %i does not exist " "for component of service %i", (*componentFIG0_3)->subchId, (*componentFIG0_3)->serviceId); continue; } if ((*subchannel)->type != subchannel_type_t::Packet) continue; const int required_size = 5 + (m_rti->factumAnalyzer ? 2 : 0); if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 0; fig0->Extension = 3; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } /* Warning: When bit SCCA_flag is unset(0), the multiplexer * R&S does not send the SCCA field. But, in the Factum ETI * analyzer, if this field is not there, it is an error. */ FIGtype0_3 *fig0_3 = (FIGtype0_3*)buf; fig0_3->setSCId((*componentFIG0_3)->packet.id); fig0_3->rfa = 0; fig0_3->SCCA_flag = 0; // if 0, datagroups are used fig0_3->DG_flag = !(*componentFIG0_3)->packet.datagroup; fig0_3->rfu = 0; fig0_3->DSCTy = (*componentFIG0_3)->type; fig0_3->SubChId = (*subchannel)->id; fig0_3->setPacketAddress((*componentFIG0_3)->packet.address); if (m_rti->factumAnalyzer) { fig0_3->SCCA = 0; } fig0->Length += 5; buf += 5; remaining -= 5; if (m_rti->factumAnalyzer) { fig0->Length += 2; buf += 2; remaining -= 2; } } if (componentFIG0_3 == ensemble->components.end()) { componentFIG0_3 = ensemble->components.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_3.h000066400000000000000000000031151476627344300215560ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include namespace FIC { // FIG type 0/3 // The Extension 3 of FIG type 0 (FIG 0/3) gives additional information about // the service component description in packet mode. class FIG0_3 : public IFIG { public: FIG0_3(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::A; } virtual int figtype() const { return 0; } virtual int figextension() const { return 3; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_component::iterator componentFIG0_3; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_5.cpp000066400000000000000000000062601476627344300221170ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_5.h" #include "utils.h" namespace FIC { struct FIGtype0_5_short { uint8_t SubChId:6; uint8_t rfu:1; uint8_t LS:1; uint8_t language; } PACKED; FIG0_5::FIG0_5(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false) { } FillStatus FIG0_5::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; auto ensemble = m_rti->ensemble; if (not m_initialised) { componentFIG0_5 = m_rti->ensemble->components.end(); m_initialised = true; } FIGtype0* fig0 = NULL; for (; componentFIG0_5 != ensemble->components.end(); ++componentFIG0_5) { auto service = getService(*componentFIG0_5, ensemble->services); auto subchannel = getSubchannel(ensemble->subchannels, (*componentFIG0_5)->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "FIG0/5: Subchannel %i does not exist " "for component of service %i", (*componentFIG0_5)->subchId, (*componentFIG0_5)->serviceId); continue; } if ( (*service)->language == 0) { continue; } const int required_size = 2; if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 0; fig0->Extension = 5; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } FIGtype0_5_short *fig0_5 = (FIGtype0_5_short*)buf; fig0_5->LS = 0; fig0_5->rfu = 0; fig0_5->SubChId = (*subchannel)->id; fig0_5->language = (*service)->language; fig0->Length += 2; buf += 2; remaining -= 2; } if (componentFIG0_5 == ensemble->components.end()) { componentFIG0_5 = ensemble->components.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_5.h000066400000000000000000000034331476627344300215630ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include namespace FIC { // FIG type 0/5 // The service component language feature is used to signal a language // associated with a service component; the spoken language of an audio // component or the language of the content of a data component. This // information can be used for user selection or display. The feature is // encoded in Extension 5 of FIG type 0 (FIG 0/5). class FIG0_5 : public IFIG { public: FIG0_5(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 5; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_component::iterator componentFIG0_5; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_6.cpp000066400000000000000000000260021476627344300221140ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_6.h" #include "utils.h" namespace FIC { struct FIGtype0_6 { uint8_t LSN_high:4; // Linkage Set Number uint8_t ILS:1; // 0=national / 1=international uint8_t SH:1; // 0=Soft link / 1=Hard link uint8_t LA:1; // Linkage actuator uint8_t IdListFlag:1; // Marks the presence of the list uint8_t LSN_low; // Linkage Set Number void setLSN(uint16_t LSN) { LSN_high = LSN >> 8; LSN_low = LSN & 0xff; } } PACKED; #define FIG0_6_IDLQ_DAB 0x0 #define FIG0_6_IDLQ_RDS 0x1 #define FIG0_6_IDLQ_DRM_AMSS 0x3 struct FIGtype0_6_header { uint8_t num_ids:4; // number of Ids to follow in the list uint8_t rfa:1; // must be 0 uint8_t IdLQ:2; // Identifier List Qualifier, see above defines uint8_t rfu:1; // must be 0 } PACKED; FIG0_6::FIG0_6(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false) { } FillStatus FIG0_6::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; auto ensemble = m_rti->ensemble; if (not m_initialised) { update(); m_initialised = true; } for (; linkageSetFIG0_6 != linkageSubsets.end(); ++linkageSetFIG0_6) { const bool PD = false; const bool ILS = linkageSetFIG0_6->international; // Are there earlier subsets to this LSN? // DB key is OE (always 0),P/D (always 0),S/H,ILS,LSN bool database_start = true; for (auto it = linkageSubsets.begin(); it != linkageSetFIG0_6; ++it) { if ( it->hard == linkageSetFIG0_6->hard and it->international == linkageSetFIG0_6->international and it->lsn == linkageSetFIG0_6->lsn) { database_start = false; } } // need to add key service to num_ids, unless it is empty which means we // send a CEI, and unless it's the continuation of the database const size_t num_ids = linkageSetFIG0_6->keyservice.empty() ? // do not transmit list if keyservice empty, it should anyway be empty 0 : ((database_start ? 1 : 0) + linkageSetFIG0_6->id_list.size()); if (num_ids > 0x0F) { etiLog.log(error, "Too many links for linkage set 0x%04x", linkageSetFIG0_6->lsn); continue; } const size_t headersize = sizeof(struct FIGtype0_6_header); const int required_size = sizeof(FIGtype0) + sizeof(struct FIGtype0_6) + headersize + (num_ids > 0 ? (PD == 0 ? (ILS == 0 ? 2*num_ids : 3*num_ids) : 4*num_ids) : 0); // Always insert a FIG0 header because it gets too confusing with the database continuation if (remaining < required_size) { break; } auto *fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = database_start ? 0 : 1; fig0->OE = 0; fig0->PD = PD; fig0->Extension = 6; buf += 2; remaining -= 2; FIGtype0_6 *fig0_6 = (FIGtype0_6*)buf; fig0_6->IdListFlag = (num_ids > 0); // C/N=0 and IdListFlag=0 is CEI fig0_6->LA = linkageSetFIG0_6->active; fig0_6->SH = linkageSetFIG0_6->hard; fig0_6->ILS = ILS; fig0_6->setLSN(linkageSetFIG0_6->lsn); fig0->Length += sizeof(struct FIGtype0_6); buf += sizeof(struct FIGtype0_6); remaining -= sizeof(struct FIGtype0_6); //etiLog.log(debug, "Linkage set 0x%04x wrote %d", linkageSetFIG0_6->lsn, fig0->Length); if (num_ids > 0) { FIGtype0_6_header *header = (FIGtype0_6_header*)buf; header->rfu = 0; // update() guarantees us that all entries in a linkage set // have the same type for (const auto& l : linkageSetFIG0_6->id_list) { if (l.type != linkageSetFIG0_6->id_list.front().type) { etiLog.log(error, "INTERNAL ERROR: invalid linkage subset 0x%04x", linkageSetFIG0_6->lsn); throw std::logic_error("FIG0_6 INTERNAL ERROR"); } } switch (linkageSetFIG0_6->id_list.front().type) { case ServiceLinkType::DAB: header->IdLQ = FIG0_6_IDLQ_DAB; break; case ServiceLinkType::FM: header->IdLQ = FIG0_6_IDLQ_RDS; break; case ServiceLinkType::DRM: header->IdLQ = FIG0_6_IDLQ_DRM_AMSS; break; case ServiceLinkType::AMSS: header->IdLQ = FIG0_6_IDLQ_DRM_AMSS; break; } header->rfa = 0; header->num_ids = num_ids; fig0->Length += headersize; buf += headersize; remaining -= headersize; if (database_start) { const std::string keyserviceuid = linkageSetFIG0_6->keyservice; const auto& keyservice = std::find_if( ensemble->services.begin(), ensemble->services.end(), [&](const std::shared_ptr srv) { return srv->uid == keyserviceuid; }); if (keyservice == ensemble->services.end()) { std::stringstream ss; ss << "Invalid key service " << keyserviceuid << " in linkage set 0x" << std::hex << linkageSetFIG0_6->lsn; throw MuxInitException(ss.str()); } if (not PD and not ILS) { buf[0] = (*keyservice)->id >> 8; buf[1] = (*keyservice)->id & 0xFF; fig0->Length += 2; buf += 2; remaining -= 2; } else if (not PD and ILS) { buf[0] = ((*keyservice)->ecc == 0) ? ensemble->ecc : (*keyservice)->ecc; buf[1] = (*keyservice)->id >> 8; buf[2] = (*keyservice)->id & 0xFF; fig0->Length += 3; buf += 3; remaining -= 3; } else { // PD == true // TODO if IdLQ is 11, MSB shall be zero buf[0] = (*keyservice)->id >> 24; buf[1] = (*keyservice)->id >> 16; buf[2] = (*keyservice)->id >> 8; buf[3] = (*keyservice)->id & 0xFF; fig0->Length += 4; buf += 4; remaining -= 4; } } if (not PD and not ILS) { for (const auto& l : linkageSetFIG0_6->id_list) { buf[0] = l.id >> 8; buf[1] = l.id & 0xFF; fig0->Length += 2; buf += 2; remaining -= 2; } } else if (not PD and ILS) { for (const auto& l : linkageSetFIG0_6->id_list) { buf[0] = l.ecc; buf[1] = l.id >> 8; buf[2] = l.id & 0xFF; fig0->Length += 3; buf += 3; remaining -= 3; } } else { // PD == true for (const auto& l : linkageSetFIG0_6->id_list) { buf[0] = l.id >> 24; buf[1] = l.id >> 16; buf[2] = l.id >> 8; buf[3] = l.id & 0xFF; fig0->Length += 4; buf += 4; remaining -= 4; } } } } if (linkageSetFIG0_6 == linkageSubsets.end()) { update(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } void FIG0_6::update() { linkageSubsets.clear(); // TODO check if AMSS and DRM have to be put into a single subset for (const auto& linkageset : m_rti->ensemble->linkagesets) { const auto lsn = linkageset->lsn; if (linkageset->keyservice.empty()) { linkageSubsets.push_back(*linkageset); } else for (const auto& link : linkageset->id_list) { const auto type = link.type; const auto subset = std::find_if(linkageSubsets.begin(), linkageSubsets.end(), [&](const LinkageSet& l) { return not l.id_list.empty() and l.lsn == lsn and l.id_list.front().type == type; }); if (subset == linkageSubsets.end()) { // A subset with that LSN and type does not exist yet linkageSubsets.push_back( linkageset->filter_type(type) ); } } } linkageSetFIG0_6 = linkageSubsets.begin(); #if 0 etiLog.log(info, " Linkage Sets rearranged"); for (const auto& lsd : linkageSubsets) { etiLog.log(info, " * LSN 0x%04x", lsd.lsn); etiLog.level(info) << " active " << (lsd.active ? "true" : "false"); etiLog.level(info) << " " << (lsd.hard ? "hard" : "soft"); etiLog.level(info) << " international " << (lsd.international ? "true" : "false"); etiLog.level(info) << " key service " << lsd.keyservice; etiLog.level(info) << " ID list"; for (const auto& link : lsd.id_list) { switch (link.type) { case ServiceLinkType::DAB: etiLog.level(info) << " type DAB"; break; case ServiceLinkType::FM: etiLog.level(info) << " type FM"; break; case ServiceLinkType::DRM: etiLog.level(info) << " type DRM"; break; case ServiceLinkType::AMSS: etiLog.level(info) << " type AMSS"; break; } etiLog.log(info, " id 0x%04x", link.id); if (lsd.international) { etiLog.log(info, " ecc 0x%04x", link.ecc); } } } #endif } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_6.h000066400000000000000000000051001476627344300215550ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include #include namespace FIC { /* TODO * This FIG code is unable to transmit the CEI to announce * activation/deactivation of linkage sets. * The TransitionHandler.h would be useful for that purpose */ // FIG type 0/6 // Service Linking // // This feature shall use the SIV signalling (see clause 5.2.2.1). The database // shall be divided by use of a database key. Changes to the database shall be // signalled using the CEI. The first service in the list of services in each // part of the database, as divided by the database key, shall be a service // carried in the ensemble. This service is called the key service. // // The database key comprises the OE and P/D flags and the S/H, ILS, and LSN // fields. class FIG0_6 : public IFIG { public: FIG0_6(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::E; } virtual int figtype() const { return 0; } virtual int figextension() const { return 6; } private: FIGRuntimeInformation *m_rti; bool m_initialised; /* Update the linkageSubsets */ void update(); /* A LinkageSet can contain links of different types * (DAB, FM, DRM, AMSS), but the FIG needs to send * different FIGs for each of those, because the IdLQ flag * is common to a list. * * We reorganise all LinkageSets into subsets that have * the same type. */ std::vector linkageSubsets; std::vector::iterator linkageSetFIG0_6; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_7.cpp000066400000000000000000000043471476627344300221250ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li Mathias Kuntze, mathias@kuntze.email */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_7.h" #include "utils.h" namespace FIC { struct FIGtype0_7 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; uint8_t ReconfigCounter_high:2; uint8_t ServiceCount:6; uint8_t ReconfigCounter_low:8; } PACKED; //=========== FIG 0/7 =========== FillStatus FIG0_7::fill(uint8_t *buf, size_t max_size) { FillStatus fs; if (max_size < 4) { fs.complete_fig_transmitted = false; fs.num_bytes_written = 0; return fs; } auto ensemble = m_rti->ensemble; if (ensemble->reconfig_counter < 0) { // FIG 0/7 is disabled fs.complete_fig_transmitted = true; fs.num_bytes_written = 0; return fs; } FIGtype0_7 *fig0_7; fig0_7 = (FIGtype0_7 *)buf; fig0_7->FIGtypeNumber = 0; fig0_7->Length = 3; fig0_7->CN = 0; fig0_7->OE = 0; fig0_7->PD = 0; fig0_7->Extension = 7; fig0_7->ServiceCount = ensemble->services.size(); fig0_7->ReconfigCounter_high = (ensemble->reconfig_counter % 1024) / 256; fig0_7->ReconfigCounter_low = (ensemble->reconfig_counter % 1024) % 256; fs.complete_fig_transmitted = true; fs.num_bytes_written = 4; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_7.h000066400000000000000000000027701476627344300215700ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li Mathias Kuntze, mathias@kuntze.email */ /* 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 . */ #pragma once #include #include namespace FIC { // FIG type 0/7, Configuration Info (MCI), // Service Number information class FIG0_7 : public IFIG { public: FIG0_7(FIGRuntimeInformation* rti) : m_rti(rti) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::FIG0_0; } virtual int figtype() const { return 0; } virtual int figextension() const { return 7; } private: FIGRuntimeInformation *m_rti; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_8.cpp000066400000000000000000000175251476627344300221300ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_8.h" #include "utils.h" namespace FIC { struct FIGtype0_8_short { uint8_t SCIdS:4; uint8_t rfa_1:3; uint8_t ext:1; uint8_t Id:6; uint8_t MscFic:1; uint8_t LS:1; uint8_t rfa_2; } PACKED; struct FIGtype0_8_long { uint8_t SCIdS:4; uint8_t rfa_1:3; uint8_t ext:1; uint8_t SCId_high:4; uint8_t rfa:3; uint8_t LS:1; uint8_t SCId_low; uint8_t rfa_2; void setSCId(uint16_t id) { SCId_high = id >> 8; SCId_low = id & 0xff; } uint16_t getSCid() { return (SCId_high << 8) | SCId_low; } } PACKED; FIG0_8::FIG0_8(FIGRuntimeInformation *rti) : m_rti(rti), m_initialised(false), m_transmit_programme(false) { } FillStatus FIG0_8::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; ssize_t remaining = max_size; if (not m_initialised) { componentFIG0_8 = m_rti->ensemble->components.end(); m_initialised = true; } FIGtype0* fig0 = NULL; for (; componentFIG0_8 != ensemble->components.end(); ++componentFIG0_8) { auto service = getService(*componentFIG0_8, ensemble->services); auto subchannel = getSubchannel(ensemble->subchannels, (*componentFIG0_8)->subchId); if (subchannel == ensemble->subchannels.end()) { etiLog.log(error, "Subchannel %i does not exist for component " "of service %i", (*componentFIG0_8)->subchId, (*componentFIG0_8)->serviceId); continue; } if (m_transmit_programme and (*service)->isProgramme(ensemble)) { const int required_size = ((*subchannel)->type == subchannel_type_t::Packet ? 5 : 4); if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 0; fig0->Extension = 8; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } if ((*subchannel)->type == subchannel_type_t::Packet) { // Data packet buf[0] = ((*componentFIG0_8)->serviceId >> 8) & 0xFF; buf[1] = ((*componentFIG0_8)->serviceId) & 0xFF; fig0->Length += 2; buf += 2; remaining -= 2; FIGtype0_8_long* definition = (FIGtype0_8_long*)buf; memset(definition, 0, 3); definition->ext = 0; // no rfa definition->SCIdS = (*componentFIG0_8)->SCIdS; definition->LS = 1; definition->setSCId((*componentFIG0_8)->packet.id); fig0->Length += 3; buf += 3; // 8 minus rfa remaining -= 3; } else { // Audio, data stream buf[0] = ((*componentFIG0_8)->serviceId >> 8) & 0xFF; buf[1] = ((*componentFIG0_8)->serviceId) & 0xFF; fig0->Length += 2; buf += 2; remaining -= 2; FIGtype0_8_short* definition = (FIGtype0_8_short*)buf; memset(definition, 0, 2); definition->ext = 0; // no rfa definition->SCIdS = (*componentFIG0_8)->SCIdS; definition->LS = 0; definition->MscFic = 0; definition->Id = (*componentFIG0_8)->subchId; fig0->Length += 2; buf += 2; // 4 minus rfa remaining -= 2; } } else if (!m_transmit_programme and !(*service)->isProgramme(ensemble)) { // Data const int required_size = ((*subchannel)->type == subchannel_type_t::Packet ? 7 : 6); if (fig0 == NULL) { if (remaining < 2 + required_size) { break; } fig0 = (FIGtype0*)buf; fig0->FIGtypeNumber = 0; fig0->Length = 1; fig0->CN = 0; fig0->OE = 0; fig0->PD = 1; fig0->Extension = 8; buf += 2; remaining -= 2; } else if (remaining < required_size) { break; } if ((*subchannel)->type == subchannel_type_t::Packet) { // Data packet buf[0] = ((*componentFIG0_8)->serviceId >> 24) & 0xFF; buf[1] = ((*componentFIG0_8)->serviceId >> 16) & 0xFF; buf[2] = ((*componentFIG0_8)->serviceId >> 8) & 0xFF; buf[3] = ((*componentFIG0_8)->serviceId) & 0xFF; fig0->Length += 4; buf += 4; remaining -= 4; FIGtype0_8_long* definition = (FIGtype0_8_long*)buf; memset(definition, 0, 3); definition->ext = 0; // no rfa definition->SCIdS = (*componentFIG0_8)->SCIdS; definition->LS = 1; definition->setSCId((*componentFIG0_8)->packet.id); fig0->Length += 3; buf += 3; // 8 minus rfa remaining -= 3; } else { // Audio, data stream buf[0] = ((*componentFIG0_8)->serviceId >> 24) & 0xFF; buf[1] = ((*componentFIG0_8)->serviceId >> 16) & 0xFF; buf[2] = ((*componentFIG0_8)->serviceId >> 8) & 0xFF; buf[3] = ((*componentFIG0_8)->serviceId) & 0xFF; fig0->Length += 4; buf += 4; remaining -= 4; FIGtype0_8_short* definition = (FIGtype0_8_short*)buf; memset(definition, 0, 2); definition->ext = 0; // no rfa definition->SCIdS = (*componentFIG0_8)->SCIdS; definition->LS = 0; definition->MscFic = 0; definition->Id = (*componentFIG0_8)->subchId; fig0->Length += 2; buf += 2; // 4 minus rfa remaining -= 2; } } } if (componentFIG0_8 == ensemble->components.end()) { componentFIG0_8 = ensemble->components.begin(); // The full database is sent every second full loop fs.complete_fig_transmitted = m_transmit_programme; m_transmit_programme = not m_transmit_programme; // Alternate between data and and programme FIG0/13, // do not mix fig0 with PD=0 with extension 13 stuff // that actually needs PD=1, and vice versa } fs.num_bytes_written = max_size - remaining; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_8.h000066400000000000000000000033411476627344300215640ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include #include "fig/FIG.h" namespace FIC { // FIG type 0/8 // The Extension 8 of FIG type 0 (FIG 0/8) provides information to link // together the service component description that is valid within the ensemble // to a service component description that is valid in other ensembles class FIG0_8 : public IFIG { public: FIG0_8(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 8; } private: FIGRuntimeInformation *m_rti; bool m_initialised; bool m_transmit_programme; vec_sp_component::iterator componentFIG0_8; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_9.cpp000066400000000000000000000131211476627344300221150ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2021 Matthias P. Braendli http://www.opendigitalradio.org */ /* 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 "fig/FIG0structs.h" #include "fig/FIG0_9.h" #include "utils.h" #include using namespace std; namespace FIC { struct FIGtype0_9 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; uint8_t ensembleLto:6; uint8_t rfa1:1; uint8_t ext:1; uint8_t ensembleEcc; uint8_t tableId; } PACKED; struct FIGtype0_9_Subfield { uint8_t Rfa2:6; uint8_t NumServices:2; uint8_t ecc; // Followed by list of NumServices uint16_t SIDs } PACKED; FIG0_9::FIG0_9(FIGRuntimeInformation *rti) : m_rti(rti) {} FillStatus FIG0_9::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; size_t remaining = max_size; if (m_extended_fields.empty()) { map > ecc_to_services; for (const auto& srv : ensemble->services) { if (srv->ecc != 0 and srv->ecc != ensemble->ecc and srv->isProgramme(ensemble)) { if (srv->id > 0xFFFF) { throw std::logic_error("Service ID for programme service > 0xFFFF"); } ecc_to_services[srv->ecc].push_back(srv->id); } } // Reorganise the data so that it fits into the // extended field. Max 3 SIds per sub-field. for (auto& ecc_sids_pair : ecc_to_services) { FIG0_9_Extended_Field ef; ef.ecc = ecc_sids_pair.first; size_t i = 0; for (auto& sid : ecc_sids_pair.second) { if (i == 3) { m_extended_fields.push_back(ef); ef.sids.clear(); i = 0; } ef.sids.push_back(sid); i++; } if (not ef.sids.empty()) { m_extended_fields.push_back(ef); } } // Max length of extended field is 25 bytes size_t subfield_required = 0; for (const auto& ef : m_extended_fields) { subfield_required += ef.required_bytes(); } if (subfield_required > 25) { etiLog.level(error) << "Cannot transmit FIG 0/9: too many services with different ECC"; } #if 0 for (const auto& ef : m_extended_fields) { stringstream ss; ss << "FIG0_9 Ext ECC 0x" << hex << (int)ef.ecc << dec << ":"; for (auto& sid : ef.sids) { ss << " " << hex << (int)sid << dec; } ss << "."; etiLog.level(debug) << ss.str(); } #endif } // Transmitting a FIG0/9 without any extended field was the CEI in EN 300 401 v1. // It went away in v2, and I interpret this that it is impossible to transmit // more than 11 services with a different ECC. size_t required = 5; for (const auto& ef : m_extended_fields) { required += ef.required_bytes(); } if (remaining < required) { fs.num_bytes_written = 0; return fs; } auto fig0_9 = (FIGtype0_9*)buf; fig0_9->FIGtypeNumber = 0; fig0_9->Length = 4; fig0_9->CN = 0; fig0_9->OE = 0; fig0_9->PD = 0; fig0_9->Extension = 9; fig0_9->ext = m_extended_fields.empty() ? 0 : 1; fig0_9->rfa1 = 0; // Had a different meaning in EN 300 401 V1.4.1 if (ensemble->lto_auto) { time_t now = time(NULL); struct tm ltime; localtime_r(&now, <ime); time_t now2 = timegm(<ime); ensemble->lto = (now2 - now) / 1800; } if (ensemble->lto >= 0) { fig0_9->ensembleLto = ensemble->lto; } else { /* Convert to 1-complement representation */ fig0_9->ensembleLto = (-ensemble->lto) | (1<<5); } fig0_9->ensembleEcc = ensemble->ecc; fig0_9->tableId = ensemble->international_table; buf += 5; remaining -= 5; for (const auto& ef : m_extended_fields) { if (ef.required_bytes() > remaining) { throw logic_error("Wrong FIG0/9 length calculation"); } if (ef.sids.size() > 3) { throw logic_error("Wrong FIG0/9 subfield generation"); } auto subfield = (FIGtype0_9_Subfield*)buf; subfield->NumServices = ef.sids.size(); subfield->Rfa2 = 0; subfield->ecc = ef.ecc; buf += 2; fig0_9->Length += 2; remaining -= 2; for (uint16_t sid : ef.sids) { const uint16_t sid_field = htons(sid); memcpy(buf, &sid_field, 2); buf += 2; fig0_9->Length += 2; remaining -= 2; } } fs.num_bytes_written = max_size - remaining; fs.complete_fig_transmitted = true; return fs; } } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0_9.h000066400000000000000000000036001476627344300215630ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2021 Matthias P. Braendli http://www.opendigitalradio.org */ /* 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 . */ #pragma once #include #include #include "fig/FIG.h" namespace FIC { // FIG type 0/9 // The Country, LTO and International table feature defines the local time // offset, the International Table and the Extended Country Code (ECC) for // the ensemble. Also, the ECC for services with differing ECC. class FIG0_9 : public IFIG { public: FIG0_9(FIGRuntimeInformation* rti); virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 0; } virtual int figextension() const { return 9; } private: FIGRuntimeInformation *m_rti; struct FIG0_9_Extended_Field { uint8_t ecc; std::list sids; size_t required_bytes() const { return 2 + 2 * sids.size(); } }; std::list m_extended_fields; }; } Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG0structs.h000066400000000000000000000030441476627344300227650ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include "fig/FIG.h" constexpr uint16_t FIG0_13_APPTYPE_SLIDESHOW = 0x2; constexpr uint16_t FIG0_13_APPTYPE_WEBSITE = 0x3; constexpr uint16_t FIG0_13_APPTYPE_TPEG = 0x4; constexpr uint16_t FIG0_13_APPTYPE_DGPS = 0x5; constexpr uint16_t FIG0_13_APPTYPE_TMC = 0x6; constexpr uint16_t FIG0_13_APPTYPE_SPI = 0x7; constexpr uint16_t FIG0_13_APPTYPE_DABJAVA = 0x8; constexpr uint16_t FIG0_13_APPTYPE_JOURNALINE = 0x44a; struct FIGtype0 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:5; uint8_t PD:1; uint8_t OE:1; uint8_t CN:1; } PACKED; Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG1.cpp000066400000000000000000000164431476627344300217000ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 Implementation of FIG1 */ /* 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 "fig/FIG1.h" namespace FIC { //=========== FIG 1/0 =========== FillStatus FIG1_0::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; size_t remaining = max_size; if (not ensemble->label.has_fig1_label()) { fs.complete_fig_transmitted = true; fs.num_bytes_written = 0; return fs; } if (remaining < 22) { fs.num_bytes_written = 0; return fs; } auto fig1_0 = (FIGtype1_0*)buf; fig1_0->Length = 21; fig1_0->FIGtypeNumber = 1; fig1_0->Extension = 0; fig1_0->OE = 0; fig1_0->Charset = 0; fig1_0->EId = htons(ensemble->id); buf += 4; ensemble->label.writeLabel(buf); buf += 16; buf[0] = ensemble->label.flag() >> 8; buf[1] = ensemble->label.flag() & 0xFF; buf += 2; remaining -= 22; fs.complete_fig_transmitted = true; fs.num_bytes_written = max_size - remaining; return fs; } //=========== FIG 1/1 =========== FillStatus FIG1_1::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { service = m_rti->ensemble->services.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; // Rotate through the subchannels until there is no more // space for (; service != ensemble->services.end(); ++service) { if (remaining < 4 + 16 + 2) { break; } const bool is_programme = (*service)->isProgramme(ensemble); if (is_programme and (*service)->label.has_fig1_label()) { auto fig1_1 = (FIGtype1_1 *)buf; fig1_1->FIGtypeNumber = 1; fig1_1->Length = 21; fig1_1->Charset = 0; fig1_1->OE = 0; fig1_1->Extension = 1; fig1_1->Sld = htons((*service)->id); buf += 4; remaining -= 4; (*service)->label.writeLabel(buf); buf += 16; remaining -= 16; buf[0] = (*service)->label.flag() >> 8; buf[1] = (*service)->label.flag() & 0xFF; buf += 2; remaining -= 2; } } if (service == ensemble->services.end()) { service = ensemble->services.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } //=========== FIG 1/4 =========== FillStatus FIG1_4::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { component = m_rti->ensemble->components.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; // Rotate through the subchannels until there is no more // space for (; component != ensemble->components.end(); ++component) { auto service = getService(*component, ensemble->services); /* We check in the config parser if the primary component has * a label, which is forbidden since V2.1.1 */ if (not (*component)->label.long_label().empty() ) { if ((*service)->isProgramme(ensemble)) { if (remaining < 5 + 16 + 2) { break; } // Programme FIGtype1_4_programme *fig1_4; fig1_4 = (FIGtype1_4_programme*)buf; fig1_4->FIGtypeNumber = 1; fig1_4->Length = 22; fig1_4->Charset = 0; fig1_4->OE = 0; fig1_4->Extension = 4; fig1_4->PD = 0; fig1_4->rfa = 0; fig1_4->SCIdS = (*component)->SCIdS; fig1_4->SId = htons((*service)->id); buf += 5; remaining -= 5; } else { // Data if (remaining < 7 + 16 + 2) { break; } FIGtype1_4_data *fig1_4; fig1_4 = (FIGtype1_4_data *)buf; fig1_4->FIGtypeNumber = 1; fig1_4->Length = 24; fig1_4->Charset = 0; fig1_4->OE = 0; fig1_4->Extension = 4; fig1_4->PD = 1; fig1_4->rfa = 0; fig1_4->SCIdS = (*component)->SCIdS; fig1_4->SId = htonl((*service)->id); buf += 7; remaining -= 7; } (*component)->label.writeLabel(buf); buf += 16; remaining -= 16; buf[0] = (*component)->label.flag() >> 8; buf[1] = (*component)->label.flag() & 0xFF; buf += 2; remaining -= 2; } } if (component == ensemble->components.end()) { component = ensemble->components.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } //=========== FIG 1/5 =========== FillStatus FIG1_5::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { service = m_rti->ensemble->services.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; // Rotate through the subchannels until there is no more // space for (; service != ensemble->services.end(); ++service) { if (remaining < 6 + 16 + 2) { break; } const auto type = (*service)->getType(ensemble); const bool is_audio = (type == subchannel_type_t::DABAudio or type == subchannel_type_t::DABPlusAudio); if (not is_audio) { auto fig1_5 = (FIGtype1_5 *)buf; fig1_5->FIGtypeNumber = 1; fig1_5->Length = 23; fig1_5->Charset = 0; fig1_5->OE = 0; fig1_5->Extension = 5; fig1_5->SId = htonl((*service)->id); buf += 6; remaining -= 6; (*service)->label.writeLabel(buf); buf += 16; remaining -= 16; buf[0] = (*service)->label.flag() >> 8; buf[1] = (*service)->label.flag() & 0xFF; buf += 2; remaining -= 2; } } if (service == ensemble->services.end()) { service = ensemble->services.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } // namespace FIC Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG1.h000066400000000000000000000102221476627344300213320ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 */ /* 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 __FIG1_H_ #define __FIG1_H_ #include #include "fig/FIG.h" namespace FIC { // FIG type 1/0, Multiplex Configuration Info (MCI), // Ensemble information class FIG1_0 : public IFIG { public: FIG1_0(FIGRuntimeInformation* rti) : m_rti(rti) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 1; } virtual int figextension() const { return 0; } private: FIGRuntimeInformation *m_rti; }; // FIG type 1/1, programme service label class FIG1_1 : public IFIG { public: FIG1_1(FIGRuntimeInformation* rti) : m_rti(rti), m_initialised(false) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 1; } virtual int figextension() const { return 1; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_service::iterator service; }; // FIG type 1/4, service component label class FIG1_4 : public IFIG { public: FIG1_4(FIGRuntimeInformation* rti) : m_rti(rti), m_initialised(false) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 1; } virtual int figextension() const { return 4; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_component::iterator component; }; // FIG type 1/5, data service label class FIG1_5 : public IFIG { public: FIG1_5(FIGRuntimeInformation* rti) : m_rti(rti), m_initialised(false) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 1; } virtual int figextension() const { return 5; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_service::iterator service; }; #ifdef _WIN32 # pragma pack(push) #endif struct FIGtype1_0 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:3; uint8_t OE:1; uint8_t Charset:4; uint16_t EId; } PACKED; struct FIGtype1_1 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:3; uint8_t OE:1; uint8_t Charset:4; uint16_t Sld; } PACKED; struct FIGtype1_5 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:3; uint8_t OE:1; uint8_t Charset:4; uint32_t SId; } PACKED; struct FIGtype1_4_programme { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:3; uint8_t OE:1; uint8_t Charset:4; uint8_t SCIdS:4; uint8_t rfa:3; uint8_t PD:1; uint16_t SId; } PACKED; struct FIGtype1_4_data { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:3; uint8_t OE:1; uint8_t Charset:4; uint8_t SCIdS:4; uint8_t rfa:3; uint8_t PD:1; uint32_t SId; } PACKED; #ifdef _WIN32 # pragma pack(pop) #endif } // namespace FIC #endif // __FIG1_H_ Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG2.cpp000066400000000000000000000306311476627344300216740ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 Implementation of FIG2 */ /* 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 #include #include "fig/FIG2.h" #include "lib/charset/charset.h" namespace FIC { using namespace std; static uint8_t* write_fig2_segment_field( uint8_t *buf, ssize_t& remaining, FIG2_Segments& segments, const DabLabel& label) { if (segments.current_segment_index() == 0) { // EN 300 401 5.2.2.3.3 "The second segment shall be carried in a following // FIG type 2 data field ...", i.e. do not insert the header anymore. if (label.fig2_uses_text_control()) { auto ext = (FIG2_Extended_Label_WithTextControl*)buf; auto tc = label.fig2_text_control(); ext->TextControl = (tc.bidi_flag ? 0x8 : 0) | (tc.base_direction_is_rtl ? 0x4 : 0) | (tc.contextual_flag ? 0x2 : 0) | (tc.combining_flag ? 0x1 : 0); ext->SegmentCount = segments.segment_count(); ext->EncodingFlag = 0; // UTF-8 buf += sizeof(FIG2_Extended_Label_WithTextControl); remaining -= sizeof(FIG2_Extended_Label_WithTextControl); } else { auto ext = (FIG2_Extended_Label_WithCharacterFlag*)buf; ext->Rfa = 0; ext->SegmentCount = segments.segment_count(); ext->EncodingFlag = 0; // UTF-8 ext->CharacterFlag = htons(label.fig2_character_field()); buf += sizeof(FIG2_Extended_Label_WithCharacterFlag); remaining -= sizeof(FIG2_Extended_Label_WithCharacterFlag); } } const auto character_field = segments.advance_segment(); copy(character_field.begin(), character_field.end(), buf); buf += character_field.size(); remaining -= character_field.size(); return buf; } void FIG2_Segments::clear() { segments.clear(); current_segment_it = segments.end(); } void FIG2_Segments::load(const string& label) { /* ETSI EN 300 401 Clause 5.2.2.3.3: * "The label may be carried in one or two FIGs" */ if (label.size() > 32) { throw runtime_error("FIG2 label too long: " + to_string(label.size())); } if (label != label_on_last_load) { toggle = not toggle; label_on_last_load = label; } segments.clear(); vector label_bytes(label.begin(), label.end()); for (size_t i = 0; i < label_bytes.size(); i+=16) { size_t len = distance(label_bytes.begin() + i, label_bytes.end()); len = min(len, (size_t)16); segments.emplace_back(label_bytes.begin() + i, label_bytes.begin() + i + len); } current_segment_it = segments.begin(); } size_t FIG2_Segments::segment_count() const { if (segments.empty()) { throw runtime_error("Empty FIG2 has not segments"); } return segments.size() - 1; } std::vector FIG2_Segments::advance_segment() { if (current_segment_it == segments.end()) { return {}; } else { return *(current_segment_it++); } } size_t FIG2_Segments::current_segment_length() const { if (current_segment_it == segments.end()) { return 0; } else { return current_segment_it->size(); } } size_t FIG2_Segments::current_segment_index() const { vv::const_iterator cur = current_segment_it; return distance(segments.begin(), cur); } bool FIG2_Segments::ready() const { return not segments.empty(); } bool FIG2_Segments::complete() const { return not segments.empty() and current_segment_it == segments.end(); } int FIG2_Segments::toggle_flag() const { return toggle ? 1 : 0; } // Ensemble label FillStatus FIG2_0::fill(uint8_t *buf, size_t max_size) { FillStatus fs; auto ensemble = m_rti->ensemble; ssize_t remaining = max_size; if (not ensemble->label.has_fig2_label()) { fs.complete_fig_transmitted = true; fs.num_bytes_written = 0; return fs; } if (not m_segments.ready()) { m_segments.load(ensemble->label.fig2_label()); if (not m_segments.ready()) { throw logic_error("Non-empty label but segments not ready()"); } } const size_t segment_header_length = ensemble->label.fig2_uses_text_control() ? sizeof(FIG2_Extended_Label_WithTextControl) : sizeof(FIG2_Extended_Label_WithCharacterFlag); const ssize_t required_bytes = (m_segments.current_segment_index() == 0) ? sizeof(FIGtype2) + 2 + segment_header_length + m_segments.current_segment_length() : sizeof(FIGtype2) + 2 + m_segments.current_segment_length(); if (remaining < required_bytes) { fs.num_bytes_written = 0; return fs; } auto fig2 = (FIGtype2*)buf; fig2->Length = required_bytes - 1; fig2->FIGtypeNumber = 2; fig2->Extension = 0; fig2->Rfu = ensemble->label.fig2_uses_text_control() ? 1 : 0; fig2->SegmentIndex = m_segments.current_segment_index(); fig2->ToggleFlag = m_segments.toggle_flag(); buf += sizeof(FIGtype2); remaining -= sizeof(FIGtype2); // Identifier field buf[0] = ensemble->id >> 8; buf[1] = ensemble->id & 0xFF; buf += 2; remaining -= 2; buf = write_fig2_segment_field(buf, remaining, m_segments, ensemble->label); if (m_segments.complete()) { fs.complete_fig_transmitted = true; m_segments.clear(); } fs.num_bytes_written = max_size - remaining; return fs; } // Programme service label and data service label FillStatus FIG2_1_and_5::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { service = m_rti->ensemble->services.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; // Rotate through the subchannels until there is no more space while (service != ensemble->services.end()) { const bool is_programme = (*service)->isProgramme(ensemble); if (not (m_programme xor is_programme) and (*service)->label.has_fig2_label()) { auto& segments = segment_per_service[(*service)->id]; if (not segments.ready()) { segments.load((*service)->label.fig2_label()); if (not segments.ready()) { throw logic_error("Non-empty label but segments not ready()"); } } const size_t id_length = (is_programme ? 2 : 4); const size_t segment_header_length = ensemble->label.fig2_uses_text_control() ? sizeof(FIG2_Extended_Label_WithTextControl) : sizeof(FIG2_Extended_Label_WithCharacterFlag); const ssize_t required_bytes = sizeof(FIGtype2) + id_length + segments.current_segment_length() + ((segments.current_segment_index() == 0) ? segment_header_length : 0); if (remaining < required_bytes) { break; } auto fig2 = (FIGtype2*)buf; fig2->Length = required_bytes - 1; fig2->FIGtypeNumber = 2; fig2->Extension = figextension(); fig2->Rfu = (*service)->label.fig2_uses_text_control() ? 1 : 0; fig2->SegmentIndex = segments.current_segment_index(); fig2->ToggleFlag = segments.toggle_flag(); buf += sizeof(FIGtype2); remaining -= sizeof(FIGtype2); // Identifier field if (is_programme) { buf[0] = (*service)->id >> 8; buf[1] = (*service)->id & 0xFF; } else { buf[0] = ((*service)->id >> 24) & 0xFF; buf[1] = ((*service)->id >> 16) & 0xFF; buf[2] = ((*service)->id >> 8) & 0xFF; buf[3] = (*service)->id & 0xFF; } buf += id_length; remaining -= id_length; buf = write_fig2_segment_field(buf, remaining, segments, (*service)->label); if (segments.complete()) { segments.clear(); ++service; } } else { ++service; } } if (service == ensemble->services.end()) { service = ensemble->services.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } // Component label FillStatus FIG2_4::fill(uint8_t *buf, size_t max_size) { FillStatus fs; ssize_t remaining = max_size; if (not m_initialised) { component = m_rti->ensemble->components.end(); m_initialised = true; } auto ensemble = m_rti->ensemble; while (component != ensemble->components.end()) { if ((*component)->label.has_fig2_label()) { auto service = getService(*component, ensemble->services); auto& segments = segment_per_component[{(*component)->serviceId, (*component)->SCIdS}]; if (not segments.ready()) { segments.load((*component)->label.fig2_label()); if (not segments.ready()) { throw logic_error("Non-empty label but segments not ready()"); } } const bool is_programme = (*service)->isProgramme(ensemble); const size_t id_length = is_programme ? sizeof(FIGtype2_4_Programme_Identifier) : sizeof(FIGtype2_4_Data_Identifier); const size_t segment_header_length = (*component)->label.fig2_uses_text_control() ? sizeof(FIG2_Extended_Label_WithTextControl) : sizeof(FIG2_Extended_Label_WithCharacterFlag); const ssize_t required_bytes = sizeof(FIGtype2) + id_length + segments.current_segment_length() + ((segments.current_segment_index() == 0) ? segment_header_length : 0); if (remaining < required_bytes) { break; } auto fig2 = (FIGtype2*)buf; fig2->Length = required_bytes - 1; fig2->FIGtypeNumber = 2; fig2->Extension = 4; fig2->Rfu = (*component)->label.fig2_uses_text_control() ? 1 : 0; fig2->SegmentIndex = segments.current_segment_index(); fig2->ToggleFlag = segments.toggle_flag(); buf += sizeof(FIGtype2); remaining -= sizeof(FIGtype2); // Identifier field if (is_programme) { auto fig2_4 = (FIGtype2_4_Programme_Identifier*)buf; fig2_4->SCIdS = (*component)->SCIdS; fig2_4->rfa = 0; fig2_4->PD = 0; fig2_4->SId = htons((*service)->id); buf += sizeof(FIGtype2_4_Programme_Identifier); remaining -= sizeof(FIGtype2_4_Programme_Identifier); } else { auto fig2_4 = (FIGtype2_4_Data_Identifier*)buf; fig2_4->SCIdS = (*component)->SCIdS; fig2_4->rfa = 0; fig2_4->PD = 1; fig2_4->SId = htonl((*service)->id); buf += sizeof(FIGtype2_4_Data_Identifier); remaining -= sizeof(FIGtype2_4_Data_Identifier); } buf = write_fig2_segment_field(buf, remaining, segments, (*component)->label); if (segments.complete()) { segments.clear(); ++component; } } else { ++component; } } if (component == ensemble->components.end()) { component = ensemble->components.begin(); fs.complete_fig_transmitted = true; } fs.num_bytes_written = max_size - remaining; return fs; } } // namespace FIC Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIG2.h000066400000000000000000000105021476627344300213340ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 */ /* 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 __FIG2_H_ #define __FIG2_H_ #include #include #include "fig/FIG.h" namespace FIC { class FIG2_Segments { public: void clear(); void load(const std::string& label); size_t segment_count() const; std::vector advance_segment(); size_t current_segment_length() const; size_t current_segment_index() const; bool ready() const; bool complete() const; int toggle_flag() const; private: using vv = std::vector >; vv segments; vv::iterator current_segment_it; std::string label_on_last_load; bool toggle = true; }; // FIG type 2/0, Multiplex Configuration Info (MCI), // Ensemble information class FIG2_0 : public IFIG { public: FIG2_0(FIGRuntimeInformation* rti) : m_rti(rti) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 2; } virtual int figextension() const { return 0; } private: FIGRuntimeInformation *m_rti; FIG2_Segments m_segments; }; // FIG type 2/1, programme service label and FIG type 2/5, data service label // share this code. class FIG2_1_and_5 : public IFIG { public: FIG2_1_and_5(FIGRuntimeInformation* rti, bool programme) : m_rti(rti), m_initialised(false), m_programme(programme) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 2;} virtual int figextension() const { return m_programme ? 1 : 5; } private: FIGRuntimeInformation *m_rti; bool m_initialised; bool m_programme; vec_sp_service::iterator service; std::map segment_per_service; }; // FIG type 2/4, service component label class FIG2_4 : public IFIG { public: FIG2_4(FIGRuntimeInformation* rti) : m_rti(rti), m_initialised(false) {} virtual FillStatus fill(uint8_t *buf, size_t max_size); virtual FIG_rate repetition_rate() const { return FIG_rate::B; } virtual int figtype() const { return 2; } virtual int figextension() const { return 4; } private: FIGRuntimeInformation *m_rti; bool m_initialised; vec_sp_component::iterator component; std::map, FIG2_Segments> segment_per_component; }; #ifdef _WIN32 # pragma pack(push) #endif struct FIGtype2 { uint8_t Length:5; uint8_t FIGtypeNumber:3; uint8_t Extension:3; uint8_t Rfu:1; uint8_t SegmentIndex:3; uint8_t ToggleFlag:1; } PACKED; struct FIGtype2_4_Programme_Identifier { uint8_t SCIdS:4; uint8_t rfa:3; uint8_t PD:1; uint16_t SId; } PACKED; struct FIGtype2_4_Data_Identifier { uint8_t SCIdS:4; uint8_t rfa:3; uint8_t PD:1; uint32_t SId; } PACKED; struct FIG2_Extended_Label_WithCharacterFlag { uint8_t Rfa:4; uint8_t SegmentCount:3; uint8_t EncodingFlag:1; uint16_t CharacterFlag; } PACKED; struct FIG2_Extended_Label_WithTextControl { uint8_t TextControl:4; uint8_t SegmentCount:3; uint8_t EncodingFlag:1; } PACKED; #ifdef _WIN32 # pragma pack(pop) #endif } // namespace FIC #endif // __FIG2_H_ Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIGCarousel.cpp000066400000000000000000000307251476627344300233140ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 Implementation of the FIG carousel to schedule the FIGs into the FIBs. */ /* 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" #include "Log.h" #include "fig/FIGCarousel.h" #include #include #include #include #define CAROUSELDEBUG 0 namespace FIC { /**************** FIGCarouselElement ****************/ void FIGCarouselElement::reduce_deadline() { deadline -= 24; //ms } void FIGCarouselElement::increase_deadline() { deadline = rate_increment_ms(fig->repetition_rate()); } bool FIGCarouselElement::check_deadline() { const auto new_rate = fig->repetition_rate(); const bool rate_changed = (m_last_rate != new_rate); if (rate_changed) { const auto new_deadline = rate_increment_ms(new_rate); if (deadline > new_deadline) { deadline = new_deadline; } m_last_rate = new_rate; } return rate_changed; } /**************** FIGCarousel *****************/ FIGCarousel::FIGCarousel(std::shared_ptr ensemble) : m_rti(ensemble), m_fig0_0(&m_rti), m_fig0_1(&m_rti), m_fig0_2(&m_rti), m_fig0_3(&m_rti), m_fig0_5(&m_rti), m_fig0_6(&m_rti), m_fig0_7(&m_rti), m_fig0_17(&m_rti), m_fig0_8(&m_rti), m_fig1_0(&m_rti), m_fig0_13(&m_rti), m_fig0_14(&m_rti), m_fig0_10(&m_rti), m_fig0_9(&m_rti), m_fig1_1(&m_rti), m_fig1_4(&m_rti), m_fig1_5(&m_rti), m_fig0_18(&m_rti), m_fig0_19(&m_rti), m_fig0_21(&m_rti), m_fig0_24(&m_rti), m_fig2_0(&m_rti), m_fig2_1(&m_rti, true), m_fig2_5(&m_rti, false), m_fig2_4(&m_rti) { /* Complete MCI except FIG0/8 should be in FIB0. * EN 300 401 V1.4.1 Clause 6.1 * * It seems that this has become a weak requirement * with time, because current receivers can cope with * FIGs in any FIB. During elaboration of the standard, * receiver manufacturers were concerned about the complexity, * and pushed for support for receivers that only could * decode FIB0. * * V2.1.1 of the spec drops this requirement. Only FIG0/0 and * FIG 0/7 have a defined location in the FIC. */ load_and_allocate(m_fig0_0, FIBAllocation::FIB0); load_and_allocate(m_fig0_7, FIBAllocation::FIB0); load_and_allocate(m_fig0_1, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_2, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_3, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_5, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_6, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_8, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_13, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_14, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_17, FIBAllocation::FIB_ANY); load_and_allocate(m_fig1_0, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_10, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_9, FIBAllocation::FIB_ANY); load_and_allocate(m_fig1_1, FIBAllocation::FIB_ANY); load_and_allocate(m_fig1_4, FIBAllocation::FIB_ANY); load_and_allocate(m_fig1_5, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_18, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_19, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_21, FIBAllocation::FIB_ANY); load_and_allocate(m_fig0_24, FIBAllocation::FIB_ANY); load_and_allocate(m_fig2_0, FIBAllocation::FIB_ANY); load_and_allocate(m_fig2_1, FIBAllocation::FIB_ANY); load_and_allocate(m_fig2_5, FIBAllocation::FIB_ANY); load_and_allocate(m_fig2_4, FIBAllocation::FIB_ANY); } void FIGCarousel::load_and_allocate(IFIG& fig, FIBAllocation fib) { FIGCarouselElement el; el.fig = &fig; el.deadline = 0; el.increase_deadline(); m_fibs[fib].push_back(el); } size_t FIGCarousel::write_fibs( uint8_t *buf, uint64_t current_frame, bool fib3_present) { m_rti.currentFrame = current_frame; if ((current_frame % 250) == 0 and m_missed_deadlines.size() > 0) { std::stringstream ss; for (const auto& fig_missed_count : m_missed_deadlines) { ss << " " << fig_missed_count; } m_missed_deadlines.clear(); etiLog.level(info) << "Could not respect repetition rates for FIGs:" << ss.str(); } /* Decrement all deadlines of all figs */ for (auto& fib_fig : m_fibs) { auto& fig = fib_fig.second; for (auto& fig_el : fig) { fig_el.reduce_deadline(); if (fig_el.deadline < 0) { #if CAROUSELDEBUG etiLog.level(warn) << " FIG" << fig_el.fig->name() << " LATE"; #endif m_missed_deadlines.insert(fig_el.fig->name()); } } } const int fibCount = fib3_present ? 4 : 3; for (int fib = 0; fib < fibCount; fib++) { memset(buf, 0x00, 30); size_t figSize = carousel(fib, buf, 30, current_frame); if (figSize < 30) { buf[figSize] = 0xff; // end marker } else if (figSize > 30) { std::stringstream ss; ss << "FIB" << fib << " overload (" << figSize << "> 30)"; throw std::runtime_error(ss.str()); } uint16_t CRCtmp = 0xffff; CRCtmp = crc16(CRCtmp, buf, 30); CRCtmp ^= 0xffff; buf += 30; *(buf++) = (CRCtmp >> 8) & 0x00FF; *(buf++) = CRCtmp & 0x00FF; } return 32 * fibCount; } size_t FIGCarousel::carousel( int fib, uint8_t *buf, const size_t bufsize, uint64_t current_frame) { const int framephase = current_frame % 4; uint8_t *pbuf = buf; FIBAllocation fibix; switch (fib) { case 0: fibix = FIBAllocation::FIB0; break; case 1: fibix = FIBAllocation::FIB1; break; case 2: fibix = FIBAllocation::FIB2; break; default: throw std::invalid_argument("FIGCarousel::carousel called with invalid fib"); } // Create our list of FIGs to consider for this FIB std::deque sorted_figs; for (auto& fig : m_fibs[fibix]) { sorted_figs.push_back(&fig); } for (auto& fig : m_fibs[FIBAllocation::FIB_ANY]) { sorted_figs.push_back(&fig); } /* Some FIGs might have changed rate since we last * set the deadline */ for (auto& fig : sorted_figs) { if (fig->check_deadline()) { #if CAROUSELDEBUG etiLog.level(debug) << "FRAME " << current_frame << " FIG" << fig->fig->figtype() << "/" << fig->fig->figextension() << " deadline changed"; #endif } } /* Sort the FIGs in the FIB according to their deadline */ std::sort(sorted_figs.begin(), sorted_figs.end(), []( const FIGCarouselElement* left, const FIGCarouselElement* right) { return left->deadline < right->deadline; }); #if 0 { std::stringstream ss; ss << "FRAME " << current_frame << " sorted FIGs "; for (auto& f : sorted_figs) { ss << f->fig->figtype() << "/" << f->fig->figextension() << "(" << f->deadline << ") "; } etiLog.level(debug) << ss.str(); } #endif /* Data structure to carry FIB */ size_t available_size = bufsize; /* Take special care for FIG0/0 which must be the first FIG of the FIB */ auto fig0_0 = find_if(sorted_figs.begin(), sorted_figs.end(), [](const FIGCarouselElement *f) { return (f->fig->figtype() == 0 && f->fig->figextension() == 0); }); /* FIG0/7 must directly follow FIG 0/0 */ auto fig0_7 = find_if(sorted_figs.begin(), sorted_figs.end(), [](const FIGCarouselElement *f) { return (f->fig->figtype() == 0 && f->fig->figextension() == 7); }); if (fig0_0 != sorted_figs.end()) { if (framephase == 0) { // TODO check for all TM FillStatus status = (*fig0_0)->fig->fill(pbuf, available_size); size_t written = status.num_bytes_written; if (written > 0) { available_size -= written; pbuf += written; #if CAROUSELDEBUG etiLog.level(debug) << "FRAME " << current_frame << " *** FIG0/0(special) wrote\t" << written << " bytes"; #endif } else { throw std::logic_error("Failed to write FIG0/0"); } if (status.complete_fig_transmitted) { (*fig0_0)->increase_deadline(); } else { throw std::logic_error("FIG0/0 did not complete!"); } if (fig0_7 != sorted_figs.end()) { FillStatus status0_7 = (*fig0_7)->fig->fill(pbuf, available_size); size_t written = status0_7.num_bytes_written; if (written > 0) { available_size -= written; pbuf += written; #if CAROUSELDEBUG etiLog.level(debug) << "FRAME " << current_frame << " ****** FIG0/7(special) wrote\t" << written << " bytes"; #endif } if (status0_7.complete_fig_transmitted) { (*fig0_7)->increase_deadline(); } } } // never transmit FIG 0/0 in any other spot sorted_figs.erase(fig0_0); } // never transmit FIG 0/7 except right after FIG 0/0 if (fig0_7 != sorted_figs.end()) { sorted_figs.erase(fig0_7); } /* Fill the FIB with the FIGs, taking the earliest deadline first */ while (available_size > 0 and not sorted_figs.empty()) { auto fig_el = sorted_figs[0]; FillStatus status = fig_el->fig->fill(pbuf, available_size); size_t written = status.num_bytes_written; // If exactly two bytes were written, it's because the FIG did // only write the header, but no content. // Writing only one byte is not allowed if (written == 1 or written == 2) { std::stringstream ss; ss << "Assertion error: FIG" << fig_el->fig->figtype() << "/" << fig_el->fig->figextension() << " did not write enough data: (" << written << ")"; throw std::logic_error(ss.str()); } else if (written > available_size) { std::stringstream ss; ss << "Assertion error: FIG" << fig_el->fig->figtype() << "/" << fig_el->fig->figextension() << " wrote " << written << " bytes, but only " << available_size << " available!"; throw std::logic_error(ss.str()); } if (written > 2) { available_size -= written; pbuf += written; } #if CAROUSELDEBUG if (written) { etiLog.level(debug) << " FRAME " << current_frame << " ** FIB" << fib << " FIG" << fig_el->fig->figtype() << "/" << fig_el->fig->figextension() << " wrote\t" << written << " bytes" << (status.complete_fig_transmitted ? ", complete" : ", incomplete") << ", margin " << fig_el->deadline; } #endif if (status.complete_fig_transmitted) { fig_el->increase_deadline(); } sorted_figs.pop_front(); } #if 0 std::cerr << "FIB "; for (size_t i = 0; i < bufsize; i++) { std::cerr << boost::format("%02x ") % (unsigned int)buf[i]; } std::cerr << std::endl; #endif return bufsize - available_size; } } // namespace FIC Opendigitalradio-ODR-DabMux-29c710c/src/fig/FIGCarousel.h000066400000000000000000000064121476627344300227550ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 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 Implementation of the FIG carousel to schedule the FIGs into the FIBs. */ /* 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 __FIG_CAROUSEL_H_ #define __FIG_CAROUSEL_H_ #include "fig/FIG.h" #include "fig/FIG0.h" #include "fig/FIG1.h" #include "fig/FIG2.h" #include #include #include #include #include "MuxElements.h" namespace FIC { class FIGCarouselElement { public: IFIG* fig; int deadline; // unit: ms void reduce_deadline(); void increase_deadline(); /* Returns true if the repetition rate changed and the * deadline was recalculated */ bool check_deadline(); private: FIG_rate m_last_rate = FIG_rate::A; }; enum class FIBAllocation { FIB0, FIB1, FIB2, FIB3, FIB_ANY }; class FIGCarousel { public: FIGCarousel(std::shared_ptr ensemble); /* Write all FIBs to the buffer, including correct padding and crc. * Returns number of bytes written. * * The buffer buf must be large enough to accomodate the FIBs, i.e. * 32 bytes per FIB. */ size_t write_fibs( uint8_t *buf, uint64_t current_frame, bool fib3_present); private: size_t carousel(int fib, uint8_t *buf, size_t bufsize, uint64_t current_frame); void load_and_allocate(IFIG& fig, FIBAllocation fib); std::unordered_set m_missed_deadlines; FIGRuntimeInformation m_rti; // Some FIGs can be mapped to a specific FIB or to FIB_ANY std::map > m_fibs; // See in ctor for allocation to FIBs FIG0_0 m_fig0_0; FIG0_1 m_fig0_1; FIG0_2 m_fig0_2; FIG0_3 m_fig0_3; FIG0_5 m_fig0_5; FIG0_6 m_fig0_6; FIG0_7 m_fig0_7; FIG0_17 m_fig0_17; FIG0_8 m_fig0_8; FIG1_0 m_fig1_0; FIG0_13 m_fig0_13; FIG0_14 m_fig0_14; FIG0_10 m_fig0_10; FIG0_9 m_fig0_9; FIG1_1 m_fig1_1; FIG1_4 m_fig1_4; FIG1_5 m_fig1_5; FIG0_18 m_fig0_18; FIG0_19 m_fig0_19; FIG0_21 m_fig0_21; FIG0_24 m_fig0_24; FIG2_0 m_fig2_0; FIG2_1_and_5 m_fig2_1; FIG2_1_and_5 m_fig2_5; FIG2_4 m_fig2_4; }; } // namespace FIC #endif // __FIG_CAROUSEL_H_ Opendigitalradio-ODR-DabMux-29c710c/src/fig/TransitionHandler.h000066400000000000000000000103521476627344300243000ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li */ /* 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 . */ #pragma once #include #include #include #include #include #include namespace FIC { // Some FIGs need to adapt their rate or their contents depending // on if some data entries are stable or currently undergoing a // change. The TransitionHandler keeps track of which entries // are in what state. template class TransitionHandler { public: using duration = std::chrono::steady_clock::duration; // update_state will move entries from new to repeated to disabled // depending on their is_active() return value. void update_state(duration timeout, std::vector > all_entries) { using namespace std::chrono; auto now = steady_clock::now(); for (const auto& entry : all_entries) { if (entry->is_active()) { if (repeated_entries.count(entry) > 0) { // We are currently announcing this entry continue; } if (new_entries.count(entry) > 0) { // We are currently announcing this entry at a // fast rate. Handle timeout: if (new_entries[entry] <= now) { repeated_entries.insert(entry); new_entries.erase(entry); } continue; } // unlikely if (disabled_entries.count(entry) > 0) { new_entries[entry] = now + timeout; disabled_entries.erase(entry); continue; } // It's a new entry! new_entries[entry] = now + timeout; } else { // Not active if (disabled_entries.count(entry) > 0) { if (disabled_entries[entry] <= now) { disabled_entries.erase(entry); } continue; } if (repeated_entries.count(entry) > 0) { // We are currently announcing this entry disabled_entries[entry] = now + timeout; repeated_entries.erase(entry); continue; } // unlikely if (new_entries.count(entry) > 0) { // We are currently announcing this entry at a // fast rate. We must stop announcing it disabled_entries[entry] = now + timeout; new_entries.erase(entry); continue; } } } } // The FIG that needs the information about what state an entry is in // can read from the following data structures. It shall not modify them. using time_point = std::chrono::steady_clock::time_point; std::map< std::shared_ptr, time_point> new_entries; std::set< std::shared_ptr > repeated_entries; std::map< std::shared_ptr, time_point> disabled_entries; }; } // namespace FIC Opendigitalradio-ODR-DabMux-29c710c/src/input/000077500000000000000000000000001476627344300210705ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/src/input/Edi.cpp000066400000000000000000000453311476627344300223030ustar00rootroot00000000000000/* Copyright (C) 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 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 "input/Edi.h" #include #include #include #include #include #include #include #include #include #include "Socket.h" #include "edi/common.hpp" #include "utils.h" using namespace std; namespace Inputs { constexpr size_t TCP_BLOCKSIZE = 2048; Edi::Edi(const std::string& name, const dab_input_edi_config_t& config) : RemoteControllable(name), m_tcp_receive_server(TCP_BLOCKSIZE), m_sti_writer(bind(&Edi::m_new_sti_frame_callback, this, placeholders::_1)), m_sti_decoder(m_sti_writer), m_max_frames_overrun(config.buffer_size), m_num_frames_prebuffering(config.prebuffering), m_name(name), m_stats(name) { constexpr bool VERBOSE = false; m_sti_decoder.set_verbose(VERBOSE); RC_ADD_PARAMETER(buffermanagement, "Set type of buffer management to use [prebuffering, timestamped]"); RC_ADD_PARAMETER(buffer, "Size of the input buffer [24ms frames]"); RC_ADD_PARAMETER(prebuffering, "Min buffer level before streaming starts [24ms frames]"); RC_ADD_PARAMETER(tistdelay, "TIST delay to add [ms]"); } Edi::~Edi() { m_running = false; if (m_thread.joinable()) { m_thread.join(); } } void Edi::open(const std::string& name) { const std::regex re_udp("udp://:([0-9]+)"); const std::regex re_udp_multicast("udp://@([0-9.]+):([0-9]+)"); const std::regex re_udp_multicast_bindto("udp://([0-9.])+@([0-9.]+):([0-9]+)"); const std::regex re_tcp("tcp://(.*):([0-9]+)"); lock_guard lock(m_mutex); m_running = false; if (m_thread.joinable()) { m_thread.join(); } std::smatch m; if (std::regex_match(name, m, re_udp)) { const int udp_port = std::stoi(m[1].str()); m_input_used = InputUsed::UDP; m_udp_sock.reinit(udp_port); m_udp_sock.setBlocking(false); } else if (std::regex_match(name, m, re_udp_multicast_bindto)) { const string bind_to = m[1].str(); const string multicast_address = m[2].str(); const int udp_port = std::stoi(m[3].str()); m_input_used = InputUsed::UDP; if (IN_MULTICAST(ntohl(inet_addr(multicast_address.c_str())))) { m_udp_sock.init_receive_multicast(udp_port, bind_to, multicast_address); } else { throw runtime_error(string("Address ") + multicast_address + " is not a multicast address"); } m_udp_sock.setBlocking(false); } else if (std::regex_match(name, m, re_udp_multicast)) { const string multicast_address = m[1].str(); const int udp_port = std::stoi(m[2].str()); m_input_used = InputUsed::UDP; if (IN_MULTICAST(ntohl(inet_addr(multicast_address.c_str())))) { m_udp_sock.init_receive_multicast(udp_port, "0.0.0.0", multicast_address); } else { throw runtime_error(string("Address ") + multicast_address + " is not a multicast address"); } m_udp_sock.setBlocking(false); } else if (std::regex_match(name, m, re_tcp)) { m_input_used = InputUsed::TCP; const string addr = m[1].str(); const int tcp_port = std::stoi(m[2].str()); m_tcp_receive_server.start(tcp_port, addr); } else { throw runtime_error(string("Cannot parse EDI input URI '") + name + "'"); } m_stats.registerAtServer(); m_running = true; m_thread = std::thread(&Edi::m_run, this); } size_t Edi::readFrame(uint8_t *buffer, size_t size) { // Save stats data in bytes, not in frames m_stats.notifyBuffer(m_frames.size() * size); m_stats.notifyTimestampOffset(0); EdiDecoder::sti_frame_t sti; if (m_is_prebuffering) { m_is_prebuffering = m_frames.size() < m_num_frames_prebuffering; if (not m_is_prebuffering) { etiLog.level(info) << "EDI input " << m_name << " pre-buffering complete."; } memset(buffer, 0, size * sizeof(*buffer)); m_stats.notifyUnderrun(); return 0; } else if (not m_pending_sti_frame.frame.empty()) { // Can only happen when switching from timestamp-based buffer management! if (m_pending_sti_frame.frame.size() != size) { if (not m_size_mismatch_printed) { etiLog.level(debug) << "EDI input " << m_name << " size mismatch: " << m_pending_sti_frame.frame.size() << " received, " << size << " requested"; m_size_mismatch_printed = true; } memset(buffer, 0, size * sizeof(*buffer)); m_stats.notifyUnderrun(); return 0; } else { if (not m_pending_sti_frame.version_data.version.empty()) { m_stats.notifyVersion( m_pending_sti_frame.version_data.version, m_pending_sti_frame.version_data.uptime_s); } m_stats.notifyPeakLevels(m_pending_sti_frame.audio_levels.left, m_pending_sti_frame.audio_levels.right); copy(m_pending_sti_frame.frame.begin(), m_pending_sti_frame.frame.end(), buffer); m_pending_sti_frame.frame.clear(); m_size_mismatch_printed = false; return size; } } else if (m_frames.try_pop(sti)) { if (sti.frame.size() == 0) { etiLog.level(debug) << "EDI input " << m_name << " empty frame"; memset(buffer, 0, size * sizeof(*buffer)); m_stats.notifyUnderrun(); return 0; } else if (sti.frame.size() == size) { // Steady-state when everything works well if (m_frames.size() > m_max_frames_overrun) { m_stats.notifyOverrun(); /* If the buffer is too full, we drop as many frames as needed * to get down to the prebuffering size. We would like to have our buffer * filled to the prebuffering length. */ size_t over_max = m_frames.size() - m_num_frames_prebuffering; while (over_max--) { EdiDecoder::sti_frame_t discard; m_frames.try_pop(discard); } } if (not sti.version_data.version.empty()) { m_stats.notifyVersion( sti.version_data.version, sti.version_data.uptime_s); } m_stats.notifyPeakLevels(sti.audio_levels.left, sti.audio_levels.right); copy(sti.frame.cbegin(), sti.frame.cend(), buffer); m_size_mismatch_printed = false; return size; } else { if (not m_size_mismatch_printed) { etiLog.level(debug) << "EDI input " << m_name << " size mismatch: " << sti.frame.size() << " received, " << size << " requested"; m_size_mismatch_printed = true; } memset(buffer, 0, size * sizeof(*buffer)); m_stats.notifyUnderrun(); return 0; } } else { memset(buffer, 0, size * sizeof(*buffer)); m_is_prebuffering = true; etiLog.level(info) << "EDI input " << m_name << " re-enabling pre-buffering"; m_size_mismatch_printed = false; m_stats.notifyUnderrun(); return 0; } } size_t Edi::readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) { if (m_pending_sti_frame.frame.empty()) { m_frames.try_pop(m_pending_sti_frame); } m_stats.notifyBuffer(m_frames.size() * size); if (m_is_prebuffering) { size_t num_discarded_wrong_size = 0; size_t num_discarded_invalid_ts = 0; size_t num_discarded_late = 0; while (not m_pending_sti_frame.frame.empty()) { if (m_pending_sti_frame.frame.size() == size) { if (m_pending_sti_frame.timestamp.is_valid()) { auto ts_req = EdiDecoder::frame_timestamp_t::from_unix_epoch(seconds, utco, tsta); ts_req += m_tist_delay; const double offset = ts_req.diff_s(m_pending_sti_frame.timestamp); m_stats.notifyTimestampOffset(offset); if (offset < 0) { // Too far in the future break; } else if (offset < 24e-3) { // Just right m_is_prebuffering = false; etiLog.level(info) << "EDI input " << m_name << " valid timestamp, pre-buffering complete." << " Wrong size: " << num_discarded_wrong_size << " Invalid TS: " << num_discarded_invalid_ts << " Late: " << num_discarded_late; if (not m_pending_sti_frame.version_data.version.empty()) { m_stats.notifyVersion( m_pending_sti_frame.version_data.version, m_pending_sti_frame.version_data.uptime_s); } m_stats.notifyPeakLevels(m_pending_sti_frame.audio_levels.left, m_pending_sti_frame.audio_levels.right); copy(m_pending_sti_frame.frame.cbegin(), m_pending_sti_frame.frame.cend(), buffer); m_pending_sti_frame.frame.clear(); return size; } else { // Too late num_discarded_late++; } } else { num_discarded_invalid_ts++; } } else { num_discarded_wrong_size++; } m_pending_sti_frame.frame.clear(); m_frames.try_pop(m_pending_sti_frame); } if (num_discarded_wrong_size > 0) { etiLog.level(warn) << "EDI input " << m_name << ": " << num_discarded_wrong_size << " packets with wrong size."; } if (num_discarded_invalid_ts > 0) { etiLog.level(warn) << "EDI input " << m_name << ": " << num_discarded_invalid_ts << " packets with invalid timestamp."; } memset(buffer, 0, size); m_stats.notifyUnderrun(); return 0; } else { if (m_pending_sti_frame.frame.empty()) { etiLog.level(warn) << "EDI input " << m_name << " empty, re-enabling pre-buffering"; memset(buffer, 0, size); m_stats.notifyUnderrun(); m_is_prebuffering = true; return 0; } else if (not m_pending_sti_frame.timestamp.is_valid()) { etiLog.level(warn) << "EDI input " << m_name << " invalid timestamp, ignoring"; memset(buffer, 0, size); m_pending_sti_frame.frame.clear(); m_stats.notifyUnderrun(); return 0; } else { auto ts_req = EdiDecoder::frame_timestamp_t::from_unix_epoch(seconds, utco, tsta); ts_req += m_tist_delay; const double offset = m_pending_sti_frame.timestamp.diff_s(ts_req); m_stats.notifyTimestampOffset(offset); if (-24e-3 < offset and offset <= 0) { if (not m_pending_sti_frame.version_data.version.empty()) { m_stats.notifyVersion( m_pending_sti_frame.version_data.version, m_pending_sti_frame.version_data.uptime_s); } m_stats.notifyPeakLevels(m_pending_sti_frame.audio_levels.left, m_pending_sti_frame.audio_levels.right); copy(m_pending_sti_frame.frame.cbegin(), m_pending_sti_frame.frame.cend(), buffer); m_pending_sti_frame.frame.clear(); return size; } else { m_stats.notifyUnderrun(); m_is_prebuffering = true; m_pending_sti_frame.frame.clear(); etiLog.level(warn) << "EDI input " << m_name << " timestamp out of bounds, re-enabling pre-buffering"; memset(buffer, 0, size); return 0; } } } } void Edi::m_run() { while (m_running) { try { switch (m_input_used) { case InputUsed::UDP: { constexpr size_t packsize = 2048; auto packet = m_udp_sock.receive(packsize); if (packet.buffer.size() == packsize) { fprintf(stderr, "Warning, possible UDP truncation\n"); } if (not packet.buffer.empty()) { EdiDecoder::Packet p(move(packet.buffer)); m_sti_decoder.push_packet(p); } else { this_thread::sleep_for(chrono::milliseconds(12)); } } break; case InputUsed::TCP: { auto message = m_tcp_receive_server.receive(); if (auto data = dynamic_pointer_cast(message)) { m_sti_decoder.push_bytes(data->data); } else if (dynamic_pointer_cast(message)) { etiLog.level(info) << "EDI input " << m_name << " disconnected"; m_sti_decoder.push_bytes({}); // Push an empty frame to clear the internal state } else if (dynamic_pointer_cast(message)) { this_thread::sleep_for(chrono::milliseconds(12)); } else { throw logic_error("unimplemented TCPReceiveMessage type"); } } break; default: throw logic_error("unimplemented input"); } } catch (const invalid_argument& e) { etiLog.level(warn) << "EDI input " << m_name << " exception: " << e.what(); m_sti_decoder.push_bytes({}); // Push an empty frame to clear the internal state this_thread::sleep_for(chrono::milliseconds(8)); } catch (const runtime_error& e) { etiLog.level(warn) << "EDI input " << m_name << " exception: " << e.what(); m_sti_decoder.push_bytes({}); // Push an empty frame to clear the internal state this_thread::sleep_for(chrono::milliseconds(8)); } } } void Edi::m_new_sti_frame_callback(EdiDecoder::sti_frame_t&& sti) { if (not sti.frame.empty()) { // We should not wait here, because we want the complete input buffering // happening inside m_frames. Using the blocking function is only a protection // against runaway memory usage if something goes wrong in the consumer. m_frames.push_wait_if_full(move(sti), m_max_frames_overrun * 2); } } int Edi::setBitrate(int bitrate) { if (bitrate <= 0) { throw invalid_argument("Invalid bitrate " + to_string(bitrate) + " for " + m_name); } return bitrate; } void Edi::close() { m_udp_sock.close(); } void Edi::set_parameter(const std::string& parameter, const std::string& value) { if (parameter == "buffer") { size_t new_limit = atol(value.c_str()); m_max_frames_overrun = new_limit; } else if (parameter == "prebuffering") { size_t new_limit = atol(value.c_str()); m_num_frames_prebuffering = new_limit; } else if (parameter == "buffermanagement") { if (value == "prebuffering") { setBufferManagement(Inputs::BufferManagement::Prebuffering); } else if (value == "timestamped") { setBufferManagement(Inputs::BufferManagement::Timestamped); } else { throw ParameterError("Invalid value for '" + parameter + "' in controllable " + get_rc_name()); } } else if (parameter == "tistdelay") { m_tist_delay = chrono::milliseconds(stoi(value)); } else { throw ParameterError("Parameter '" + parameter + "' is not exported by controllable " + get_rc_name()); } } const std::string Edi::get_parameter(const std::string& parameter) const { stringstream ss; if (parameter == "buffer") { ss << m_max_frames_overrun; } else if (parameter == "prebuffering") { ss << m_num_frames_prebuffering; } else if (parameter == "buffermanagement") { switch (getBufferManagement()) { case Inputs::BufferManagement::Prebuffering: ss << "prebuffering"; break; case Inputs::BufferManagement::Timestamped: ss << "timestamped"; break; } } else if (parameter == "tistdelay") { ss << m_tist_delay.count(); } else { throw ParameterError("Parameter '" + parameter + "' is not exported by controllable " + get_rc_name()); } return ss.str(); } const json::map_t Edi::get_all_values() const { json::map_t map; map["buffer"].v = m_max_frames_overrun; map["prebuffering"].v = m_num_frames_prebuffering; switch (getBufferManagement()) { case Inputs::BufferManagement::Prebuffering: map["buffermanagement"].v = "prebuffering"; break; case Inputs::BufferManagement::Timestamped: map["buffermanagement"].v = "timestamped"; break; } map["tistdelay"].v = m_tist_delay.count(); return map; } } Opendigitalradio-ODR-DabMux-29c710c/src/input/Edi.h000066400000000000000000000102351476627344300217430ustar00rootroot00000000000000/* Copyright (C) 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 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 . */ #pragma once #include #include #include #include #include #include "Socket.h" #include "input/inputs.h" #include "edi/STIDecoder.hpp" #include "edi/STIWriter.hpp" #include "ThreadsafeQueue.h" #include "ManagementServer.h" namespace Inputs { struct dab_input_edi_config_t { /* The size of the internal buffer, measured in number * of elements. * * Each element corresponds to one frame, i.e. 24ms */ size_t buffer_size = 100; /* The amount of prebuffering to do before we start streaming * * Same units as buffer_size */ size_t prebuffering = 30; }; /* * Receives EDI from UDP or TCP in a separate thread and pushes that data * into the STIDecoder. Complete frames are then put into a queue for the consumer. * * This way, the EDI decoding happens in a separate thread. */ class Edi : public InputBase, public RemoteControllable { public: Edi(const std::string& name, const dab_input_edi_config_t& config); Edi(const Edi&) = delete; Edi& operator=(const Edi&) = delete; ~Edi(); virtual void open(const std::string& name); virtual size_t readFrame(uint8_t *buffer, size_t size); virtual size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); virtual int setBitrate(int bitrate); virtual void close(); /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; protected: void m_run(); void m_new_sti_frame_callback(EdiDecoder::sti_frame_t&& frame); std::mutex m_mutex; enum class InputUsed { Invalid, UDP, TCP }; InputUsed m_input_used = InputUsed::Invalid; Socket::UDPSocket m_udp_sock; Socket::TCPReceiveServer m_tcp_receive_server; EdiDecoder::STIWriter m_sti_writer; EdiDecoder::STIDecoder m_sti_decoder; std::thread m_thread; std::atomic m_running = ATOMIC_VAR_INIT(false); ThreadsafeQueue m_frames; // InputBase defines bufferManagement and tist delay // Used in timestamp-based buffer management EdiDecoder::sti_frame_t m_pending_sti_frame; // State variable used in prebuffering-based buffer management bool m_is_prebuffering = true; // Display the 'size mismatch' warning only once bool m_size_mismatch_printed = false; /* When using prebuffering, consider the buffer to be full on the * receive side if it's above the overrun threshold. * * When using timestamping, start discarding the front of the queue once the queue * is this full. Must be smaller than m_max_frames_queued. * * Parameter 'buffer' inside RC. */ std::atomic m_max_frames_overrun = ATOMIC_VAR_INIT(1000); /* When not using timestamping, how many frames to prebuffer. * Parameter 'prebuffering' inside RC. */ std::atomic m_num_frames_prebuffering = ATOMIC_VAR_INIT(10); std::string m_name; InputStat m_stats; }; }; Opendigitalradio-ODR-DabMux-29c710c/src/input/File.cpp000066400000000000000000000452001476627344300224540ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli http://www.opendigitalradio.org */ /* 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 #include #include #include #include #ifndef _WIN32 # define O_BINARY 0 #endif #include "input/File.h" #include "mpeg.h" #include "ReedSolomon.h" using namespace std; namespace Inputs { #ifdef _WIN32 # pragma pack(push, 1) #endif struct packetHeader { unsigned char addressHigh:2; unsigned char last:1; unsigned char first:1; unsigned char continuityIndex:2; unsigned char packetLength:2; unsigned char addressLow; unsigned char dataLength:7; unsigned char command; } #ifdef _WIN32 # pragma pack(pop) #else __attribute((packed)) #endif ; void FileBase::open(const std::string& name) { m_filename = name; if (m_load_entire_file) { load_entire_file(); } else { int flags = O_RDONLY | O_BINARY; if (m_nonblock) { flags |= O_NONBLOCK; } m_fd = ::open(name.c_str(), flags); if (m_fd == -1) { throw runtime_error("Could not open input file " + name + ": " + strerror(errno)); } } } size_t FileBase::readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) { // Will not be implemented, as there is no obvious way to carry timestamps // in files. memset(buffer, 0, size); return 0; } int FileBase::setBitrate(int bitrate) { if (bitrate <= 0) { throw invalid_argument("Invalid bitrate " + to_string(bitrate)); } return bitrate; } void FileBase::close() { if (m_fd != -1) { ::close(m_fd); m_fd = -1; } } void FileBase::setNonblocking(bool nonblock) { if (m_load_entire_file) { throw runtime_error("Cannot set both nonblock and load_entire_file"); } m_nonblock = nonblock; } void FileBase::setLoadEntireFile(bool load_entire_file) { if (m_nonblock) { throw runtime_error("Cannot set both nonblock and load_entire_file"); } m_load_entire_file = load_entire_file; } ssize_t FileBase::rewind() { if (m_load_entire_file) { return load_entire_file(); } else if (m_fd) { return ::lseek(m_fd, 0, SEEK_SET); } else { throw runtime_error("Cannot rewind"); } } ssize_t FileBase::load_entire_file() { // Clear the buffer if the file open fails, this allows user to stop transmission // of the current data. vector old_file_contents = move(m_file_contents); m_file_contents.clear(); m_file_contents_offset = 0; // Read entire file in chunks of 4MiB constexpr size_t blocksize = 4 * 1024 * 1024; constexpr int flags = O_RDONLY | O_BINARY; m_fd = ::open(m_filename.c_str(), flags); if (m_fd == -1) { if (not m_file_open_alert_shown) { etiLog.level(error) << "Could not open input file " << m_filename << ": " << strerror(errno); } m_file_open_alert_shown = true; return -1; } ssize_t offset = 0; ssize_t r = 0; do { m_file_contents.resize(offset + blocksize); uint8_t *buffer = m_file_contents.data() + offset; r = read(m_fd, buffer, blocksize); if (r == -1) { if (not m_file_open_alert_shown) { etiLog.level(error) << "Can't read file " << strerror(errno); } m_file_contents.clear(); // ensures size is not larger than what we read close(); m_file_open_alert_shown = true; return -1; } m_file_contents.resize(offset + r); offset += r; } while (r > 0); close(); if (old_file_contents != m_file_contents) { etiLog.level(info) << "Loaded " << m_file_contents.size() << " bytes from " << m_filename; } m_file_open_alert_shown = false; return m_file_contents.size(); } ssize_t FileBase::readFromFile(uint8_t *buffer, size_t size) { using namespace std; ssize_t ret = 0; if (m_nonblock) { if (size > m_nonblock_buffer.size()) { const size_t required_len = size - m_nonblock_buffer.size(); vector buf(required_len); ret = read(m_fd, buf.data(), required_len); /* If no process has the pipe open for writing, read() shall return 0 * to indicate end-of-file. */ if (ret == 0) { return 0; } /* If some process has the pipe open for writing and O_NONBLOCK is * set, read() shall return −1 and set errno to [EAGAIN]. */ if (ret == -1 and errno == EAGAIN) { return 0; } else if (ret == -1) { etiLog.level(error) << "Can't read file " << strerror(errno); return -1; } // read() might read less data than requested buf.resize(ret); copy(buf.begin(), buf.end(), back_inserter(m_nonblock_buffer)); } if (m_nonblock_buffer.size() >= size) { copy(m_nonblock_buffer.begin(), m_nonblock_buffer.begin() + size, buffer); vector remaining_buf; copy(m_nonblock_buffer.begin() + size, m_nonblock_buffer.end(), back_inserter(remaining_buf)); m_nonblock_buffer = move(remaining_buf); return size; } else { return 0; } } else if (m_load_entire_file) { // Handle file read errors. if (m_file_contents.size() == 0) { rewind(); } if (m_file_contents.size() == 0) { memset(buffer, 0, size); } else { size_t remain = size; while (m_file_contents_offset + remain > m_file_contents.size()) { copy( m_file_contents.cbegin() + m_file_contents_offset, m_file_contents.cend(), buffer); size_t copied = m_file_contents.size() - m_file_contents_offset; remain -= copied; rewind(); // In case rewind() fails if (m_file_contents.size() == 0) { memset(buffer, 0, size); return size; } } copy( m_file_contents.cbegin() + m_file_contents_offset, m_file_contents.cbegin() + m_file_contents_offset + remain, buffer); m_file_contents_offset += remain; } return size; } else { ret = read(m_fd, buffer, size); if (ret == -1) { etiLog.level(error) << "Can't read file " << strerror(errno); return -1; } if (ret < (ssize_t)size) { ssize_t sizeOut = ret; etiLog.log(info, "reach end of file -> rewinding"); if (rewind() == -1) { etiLog.log(error, "Can't rewind file"); return -1; } ret = read(m_fd, buffer + sizeOut, size - sizeOut); if (ret == -1) { etiLog.log(error, "Can't read file"); perror(""); return -1; } if (ret < (ssize_t)size) { etiLog.log(error, "Not enough data in file"); return -1; } } } return size; } size_t MPEGFile::readFrame(uint8_t *buffer, size_t size) { int result; bool do_rewind = false; READ_SUBCHANNEL: if (m_parity) { result = readData(m_fd, buffer, size, 2); m_parity = false; return 0; } else { result = readMpegHeader(m_fd, buffer, size); if (result > 0) { result = readMpegFrame(m_fd, buffer, size); if (result < 0 && getMpegFrequency(buffer) == 24000) { m_parity = true; result = size; } } } switch (result) { case MPEG_BUFFER_UNDERFLOW: etiLog.log(warn, "data underflow -> frame muted"); goto MUTE_SUBCHANNEL; case MPEG_BUFFER_OVERFLOW: etiLog.log(warn, "bitrate too high -> frame muted"); goto MUTE_SUBCHANNEL; case MPEG_FILE_EMPTY: if (do_rewind) { etiLog.log(error, "file rewinded and still empty " "-> frame muted"); goto MUTE_SUBCHANNEL; } else { etiLog.log(info, "reach end of file -> rewinding"); rewind(); goto READ_SUBCHANNEL; } case MPEG_FILE_ERROR: etiLog.log(error, "can't read file (%i) -> frame muted", errno); perror(""); goto MUTE_SUBCHANNEL; case MPEG_SYNC_NOT_FOUND: etiLog.log(error, "mpeg sync not found, maybe is not a valid file " "-> frame muted"); goto MUTE_SUBCHANNEL; case MPEG_INVALID_FRAME: etiLog.log(error, "file is not a valid mpeg file " "-> frame muted"); goto MUTE_SUBCHANNEL; default: if (result < 0) { etiLog.log(error, "unknown error (code = %i) -> frame muted", result); MUTE_SUBCHANNEL: memset(buffer, 0, size); } else { if (result < (ssize_t)size) { etiLog.log(warn, "bitrate too low from file " "-> frame padded"); memset((char*)buffer + result, 0, size - result); } result = checkDabMpegFrame(buffer); switch (result) { case MPEG_FREQUENCY: etiLog.log(error, "file has a frame with an invalid " "frequency: %i, should be 48000 or 24000", getMpegFrequency(buffer)); break; case MPEG_PADDING: etiLog.log(warn, "file has a frame with padding bit setn"); break; case MPEG_COPYRIGHT: result = 0; break; case MPEG_ORIGINAL: result = 0; break; case MPEG_EMPHASIS: etiLog.log(warn, "file has a frame with emphasis bits set"); break; default: if (result < 0) { etiLog.log(error, "mpeg file has an invalid DAB " "mpeg frame (unknown reason: %i)", result); } break; } } } // TODO this is probably wrong, because it should return // the number of bytes written. return result; } int MPEGFile::setBitrate(int bitrate) { if (bitrate < 0) { throw invalid_argument("Invalid bitrate " + to_string(bitrate)); } else if (bitrate == 0) { uint8_t buffer[4]; if (readFrame(buffer, 4) == 0) { bitrate = getMpegBitrate(buffer); } else { bitrate = -1; } rewind(); } return bitrate; } size_t RawFile::readFrame(uint8_t *buffer, size_t size) { return readFromFile(buffer, size); } PacketFile::PacketFile(bool enhancedPacketMode) { m_enhancedPacketEnabled = enhancedPacketMode; } size_t PacketFile::readFrame(uint8_t *buffer, size_t size) { size_t written = 0; int length; packetHeader* header; int indexRow; int indexCol; while (written < size) { if (m_enhancedPacketWaiting > 0) { *buffer = 192 - m_enhancedPacketWaiting; *buffer /= 22; *buffer <<= 2; *(buffer++) |= 0x03; *(buffer++) = 0xfe; indexCol = 188; indexCol += (192 - m_enhancedPacketWaiting) / 12; indexRow = 0; indexRow += (192 - m_enhancedPacketWaiting) % 12; for (int j = 0; j < 22; ++j) { if (m_enhancedPacketWaiting == 0) { *(buffer++) = 0; } else { *(buffer++) = m_enhancedPacketData[indexRow][indexCol]; if (++indexRow == 12) { indexRow = 0; ++indexCol; } --m_enhancedPacketWaiting; } } written += 24; if (m_enhancedPacketWaiting == 0) { m_enhancedPacketLength = 0; } } else if (m_packetLength != 0) { header = (packetHeader*)(&m_packetData[0]); if (written + m_packetLength > (unsigned)size) { memset(buffer, 0, 22); buffer[22] = 0x60; buffer[23] = 0x4b; length = 24; } else if (m_enhancedPacketEnabled) { if (m_enhancedPacketLength + m_packetLength > (12 * 188)) { memset(buffer, 0, 22); buffer[22] = 0x60; buffer[23] = 0x4b; length = 24; } else { copy(m_packetData.begin(), m_packetData.begin() + m_packetLength, buffer); length = m_packetLength; m_packetLength = 0; } } else { copy(m_packetData.begin(), m_packetData.begin() + m_packetLength, buffer); length = m_packetLength; m_packetLength = 0; } if (m_enhancedPacketEnabled) { indexCol = m_enhancedPacketLength / 12; indexRow = m_enhancedPacketLength % 12; // TODO Check if always 0 for (int j = 0; j < length; ++j) { m_enhancedPacketData[indexRow][indexCol] = buffer[j]; if (++indexRow == 12) { indexRow = 0; ++indexCol; } } m_enhancedPacketLength += length; if (m_enhancedPacketLength >= (12 * 188)) { m_enhancedPacketLength = (12 * 188); ReedSolomon encoder(204, 188); for (int j = 0; j < 12; ++j) { encoder.encode(&m_enhancedPacketData[j][0], 188); } m_enhancedPacketWaiting = 192; } } written += length; buffer += length; } else { int nbBytes = readFromFile(buffer, 3); header = (packetHeader*)buffer; if (nbBytes == -1) { if (errno == EAGAIN) goto END_PACKET; perror("Packet file"); return -1; } else if (nbBytes == 0) { if (rewind() == -1) { goto END_PACKET; } continue; } else if (nbBytes < 3) { etiLog.log(error, "Error while reading file for packet header; " "read %i out of 3 bytes", nbBytes); break; } length = header->packetLength * 24 + 24; if (written + length > size) { memcpy(&m_packetData[0], header, 3); readFromFile(&m_packetData[3], length - 3); m_packetLength = length; continue; } if (m_enhancedPacketEnabled) { if (m_enhancedPacketLength + length > (12 * 188)) { memcpy(&m_packetData[0], header, 3); readFromFile(&m_packetData[3], length - 3); m_packetLength = length; continue; } } nbBytes = readFromFile(buffer + 3, length - 3); if (nbBytes == -1) { perror("Packet file"); return -1; } else if (nbBytes == 0) { etiLog.log(info, "Packet header read, but no data!"); if (rewind() == -1) { goto END_PACKET; } continue; } else if (nbBytes < length - 3) { etiLog.log(error, "Error while reading packet file; " "read %i out of %i bytes", nbBytes, length - 3); break; } if (m_enhancedPacketEnabled) { indexCol = m_enhancedPacketLength / 12; indexRow = m_enhancedPacketLength % 12; // TODO Check if always 0 for (int j = 0; j < length; ++j) { m_enhancedPacketData[indexRow][indexCol] = buffer[j]; if (++indexRow == 12) { indexRow = 0; ++indexCol; } } m_enhancedPacketLength += length; if (m_enhancedPacketLength >= (12 * 188)) { if (m_enhancedPacketLength > (12 * 188)) { etiLog.log(error, "Error, too much enhanced packet data!"); } ReedSolomon encoder(204, 188); for (int j = 0; j < 12; ++j) { encoder.encode(&m_enhancedPacketData[j][0], 188); } m_enhancedPacketWaiting = 192; } } written += length; buffer += length; } } END_PACKET: while (written < size) { memset(buffer, 0, 22); buffer[22] = 0x60; buffer[23] = 0x4b; buffer += 24; written += 24; } return written; } }; Opendigitalradio-ODR-DabMux-29c710c/src/input/File.h000066400000000000000000000062111476627344300221200ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2022 Matthias P. Braendli http://www.opendigitalradio.org */ /* 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 . */ #pragma once #include #include #include #include #include "input/inputs.h" #include "ManagementServer.h" namespace Inputs { class FileBase : public InputBase { public: virtual void open(const std::string& name); virtual size_t readFrame(uint8_t *buffer, size_t size) = 0; virtual size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); virtual int setBitrate(int bitrate); virtual void close(); virtual void setNonblocking(bool nonblock); virtual void setLoadEntireFile(bool load_entire_file); protected: /* Rewind the file * Returns -1 on failure, 0 on success */ virtual ssize_t rewind(); /* Read len bytes from the file into buf, and return * the number of bytes read, or -1 in case of error. */ virtual ssize_t readFromFile(uint8_t* buf, size_t len); virtual ssize_t load_entire_file(); // We use unix open() instead of fopen() because // of non-blocking I/O int m_fd = -1; std::string m_filename; bool m_nonblock = false; bool m_load_entire_file = false; std::vector m_nonblock_buffer; size_t m_file_contents_offset = 0; std::vector m_file_contents; bool m_file_open_alert_shown = false; }; class MPEGFile : public FileBase { public: virtual size_t readFrame(uint8_t *buffer, size_t size); virtual int setBitrate(int bitrate); private: bool m_parity = false; }; class RawFile : public FileBase { public: virtual size_t readFrame(uint8_t *buffer, size_t size); }; class PacketFile : public FileBase { public: PacketFile(bool enhancedPacketMode); virtual size_t readFrame(uint8_t *buffer, size_t size); protected: std::array m_packetData; size_t m_packetLength = 0; /* Enhanced packet mode enables FEC for MSC packet mode * as described in EN 300 401 Clause 5.3.5 */ bool m_enhancedPacketEnabled = false; std::array,12> m_enhancedPacketData; size_t m_enhancedPacketWaiting = 0; size_t m_enhancedPacketLength = 0; }; }; Opendigitalradio-ODR-DabMux-29c710c/src/input/Prbs.cpp000066400000000000000000000050771476627344300225130ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org Pseudo-Random Bit Sequence generator for test purposes. */ /* 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 "input/Prbs.h" #include #include #include #include #include #include #include "utils.h" using namespace std; namespace Inputs { // ETS 300 799 Clause G.2.1 // Preferred polynomial is G(x) = x^20 + x^17 + 1 const uint32_t PRBS_DEFAULT_POLY = (1 << 20) | (1 << 17) | (1 << 0); void Prbs::open(const string& name) { if (name.substr(0, 7) != "prbs://") { throw logic_error("Invalid PRBS name"); } const string& url_polynomial = name.substr(7); if (url_polynomial.empty()) { m_prbs.setup(PRBS_DEFAULT_POLY); } else { if (url_polynomial[0] != ':') { throw invalid_argument( "Invalid PRBS address format. " "Must be prbs://:polynomial."); } const string poly_str = url_polynomial.substr(1); long polynomial = hexparse(poly_str); if (polynomial == 0) { throw invalid_argument("No polynomial given for PRBS input"); } m_prbs.setup(polynomial); } rewind(); } size_t Prbs::readFrame(uint8_t *buffer, size_t size) { for (size_t i = 0; i < size; ++i) { buffer[i] = m_prbs.step(); } return size; } size_t Prbs::readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) { memset(buffer, 0, size); return 0; } int Prbs::setBitrate(int bitrate) { if (bitrate <= 0) { throw invalid_argument("Invalid bitrate " + to_string(bitrate)); } return bitrate; } void Prbs::close() { } int Prbs::rewind() { m_prbs.rewind(); return 0; } }; Opendigitalradio-ODR-DabMux-29c710c/src/input/Prbs.h000066400000000000000000000027371476627344300221600ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2016 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org Pseudo-Random Bit Sequence generator for test purposes. */ /* 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 . */ #pragma once #include #include "input/inputs.h" #include "PrbsGenerator.h" namespace Inputs { class Prbs : public InputBase { public: virtual void open(const std::string& name); virtual size_t readFrame(uint8_t *buffer, size_t size); virtual size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); virtual int setBitrate(int bitrate); virtual void close(); private: virtual int rewind(); PrbsGenerator m_prbs; }; }; Opendigitalradio-ODR-DabMux-29c710c/src/input/Udp.cpp000066400000000000000000000201551476627344300223270ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 "input/Udp.h" #include #include #include #include #include #include #include "utils.h" using namespace std; namespace Inputs { void Udp::open(const std::string& name) { // Skip the udp:// part if it is present const string endpoint = (name.substr(0, 6) == "udp://") ? name.substr(6) : name; // The endpoint should be address:port const auto colon_pos = endpoint.find_first_of(":"); if (colon_pos == string::npos) { stringstream ss; ss << "'" << name << " is an invalid format for udp address: " "expected [udp://]address:port"; throw invalid_argument(ss.str()); } m_name = name; openUdpSocket(endpoint); } void Udp::openUdpSocket(const std::string& endpoint) { const auto colon_pos = endpoint.find_first_of(":"); const auto address = endpoint.substr(0, colon_pos); const auto port_str = endpoint.substr(colon_pos + 1); const long port = strtol(port_str.c_str(), nullptr, 10); if ((port == LONG_MIN or port == LONG_MAX) and errno == ERANGE) { throw out_of_range("udp input: port out of range"); } else if (port == 0 and errno != 0) { stringstream ss; ss << "udp input port parse error: " << strerror(errno); throw invalid_argument(ss.str()); } if (port == 0) { throw out_of_range("can't use port number 0 in udp address"); } m_sock.reinit(port, address); m_sock.setBlocking(false); etiLog.level(info) << "Opened UDP port " << address << ":" << port; } size_t Udp::readFrame(uint8_t *buffer, size_t size) { // Regardless of buffer contents, try receiving data. auto packet = m_sock.receive(32768); std::copy(packet.buffer.cbegin(), packet.buffer.cend(), back_inserter(m_buffer)); // Take data from the buffer if it contains enough data, // in any case write the buffer if (m_buffer.size() >= (size_t)size) { std::copy(m_buffer.begin(), m_buffer.begin() + size, buffer); return size; } else { memset(buffer, 0x0, size); return 0; } } size_t Udp::readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) { // Maybe there's a way to carry timestamps, but we don't need it. memset(buffer, 0x0, size); return 0; } int Udp::setBitrate(int bitrate) { if (bitrate <= 0) { throw invalid_argument("Invalid bitrate " + to_string(bitrate) + " for " + m_name); } return bitrate; } void Udp::close() { m_sock.close(); } // ETSI EN 300 797 V1.2.1 ch 8.2.1.2 #define STI_SYNC_LEN 3 static const uint8_t STI_FSync0[STI_SYNC_LEN] = { 0x1F, 0x90, 0xCA }; static const uint8_t STI_FSync1[STI_SYNC_LEN] = { 0xE0, 0x6F, 0x35 }; static const size_t RTP_HEADER_LEN = 12; static bool stiSyncValid(const uint8_t *buf) { return (memcmp(buf, STI_FSync0, sizeof(STI_FSync0)) == 0) or (memcmp(buf, STI_FSync1, sizeof(STI_FSync1)) == 0); } static bool rtpHeaderValid(const uint8_t *buf) { uint32_t version = (buf[0] & 0xC0) >> 6; uint32_t payloadType = (buf[1] & 0x7F); return (version == 2 and payloadType == 34); } static uint16_t unpack2(const uint8_t *buf) { return (((uint16_t)buf[0]) << 8) | buf[1]; } void Sti_d_Rtp::open(const std::string& name) { // Skip the rtp:// part if it is present const string endpoint = (name.substr(0, 6) == "rtp://") ? name.substr(6) : name; // The endpoint should be address:port const auto colon_pos = endpoint.find_first_of(":"); if (colon_pos == string::npos) { stringstream ss; ss << "'" << name << " is an invalid format for rtp address: " "expected [rtp://]address:port"; throw invalid_argument(ss.str()); } m_name = name; openUdpSocket(endpoint); } void Sti_d_Rtp::receive_packet() { auto packet = m_sock.receive(32768); if (packet.buffer.empty()) { // No packet was received return; } const size_t STI_FC_LEN = 8; if (packet.buffer.size() < RTP_HEADER_LEN + STI_SYNC_LEN + STI_FC_LEN) { etiLog.level(info) << "Received too small RTP packet for " << m_name; return; } if (not rtpHeaderValid(packet.buffer.data())) { etiLog.level(info) << "Received invalid RTP header for " << m_name; return; } // STI(PI, X) size_t index = RTP_HEADER_LEN; const uint8_t *buf = packet.buffer.data(); // SYNC index++; // Advance over STAT // FSYNC if (not stiSyncValid(buf + index)) { etiLog.level(info) << "Received invalid STI-D header for " << m_name; return; } index += 3; // TFH // DFS uint16_t DFS = unpack2(buf+index); index += 2; if (DFS == 0) { etiLog.level(info) << "Received STI data with DFS=0 for " << m_name; return; } if (packet.buffer.size() < index + DFS) { etiLog.level(info) << "Received STI too small for given DFS for " << m_name; return; } // CFS uint32_t CFS = unpack2(buf+index); index += 2; if (CFS != 0) { etiLog.level(info) << "Ignoring STI control data for " << m_name; } // SPID index += 2; // rfa DL index += 2; // rfa index += 1; // DFCT uint8_t DFCTL = buf[index]; index += 1; uint8_t DFCTH = buf[index] >> 3; uint16_t NST = unpack2(buf+index) & 0x7FF; // 11 bits index += 2; if (packet.buffer.size() < index + 4*NST) { etiLog.level(info) << "Received STI too small to contain NST for " << m_name << " packet: " << packet.buffer.size() << " need " << index + 4*NST; return; } if (NST == 0) { etiLog.level(info) << "Received STI with NST=0 for " << m_name; return; } else { // Take the first stream even if NST > 1 uint32_t STL = unpack2(buf+index) & 0x1FFF; // 13 bits uint32_t CRCSTF = buf[index+3] & 0x80 >> 7; // 7th bit index += NST*4+4; const size_t dataSize = STL - 2*CRCSTF; const size_t frameNumber = DFCTH*250 + DFCTL; (void)frameNumber; // TODO must align framenumber with ETI // TODO reordering vec_u8 data(dataSize); copy(buf+index, buf+index+dataSize, data.begin()); m_queue.push_back(data); } if (NST > 1) { etiLog.level(info) << "Ignoring STI supernumerary STC streams for " << m_name; } } size_t Sti_d_Rtp::readFrame(uint8_t *buffer, size_t size) { // Make sure we fill faster than we consume in case there // are pending packets. receive_packet(); receive_packet(); if (m_queue.empty()) { memset(buffer, 0x0, size); return 0; } else if (m_queue.front().size() != size) { etiLog.level(warn) << "Invalid input data size for STI " << m_name << " : RX " << m_queue.front().size() << " expected " << size; memset(buffer, 0x0, size); m_queue.pop_front(); return 0; } else { copy(m_queue.front().begin(), m_queue.front().end(), buffer); m_queue.pop_front(); return size; } } } Opendigitalradio-ODR-DabMux-29c710c/src/input/Udp.h000066400000000000000000000044701476627344300217760ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2017 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 . */ #pragma once #include #include #include #include #include "input/inputs.h" #include "Socket.h" namespace Inputs { /* A Udp input that takes incoming datagrams, concatenates them * together and gives them back. */ class Udp : public InputBase { public: virtual void open(const std::string& name); virtual size_t readFrame(uint8_t *buffer, size_t size); virtual size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); virtual int setBitrate(int bitrate); virtual void close(); protected: Socket::UDPSocket m_sock; std::string m_name; void openUdpSocket(const std::string& endpoint); private: // The content of the UDP packets gets written into the // buffer, and the UDP packet boundaries disappear there. std::vector m_buffer; }; /* An input for STI-D(LI) carried in STI(PI, X) inside RTP inside UDP. * Reorders incoming datagrams which must contain an RTP header and valid * STI-D data. * * This is intended to be compatible with encoders from AVT. */ class Sti_d_Rtp : public Udp { using vec_u8 = std::vector; public: virtual void open(const std::string& name); virtual size_t readFrame(uint8_t *buffer, size_t size); private: void receive_packet(void); std::deque m_queue; }; }; Opendigitalradio-ODR-DabMux-29c710c/src/input/Zmq.cpp000066400000000000000000000461411476627344300223510ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli http://www.opendigitalradio.org ZeroMQ input. see www.zeromq.org for more info For the AAC+ input, each zeromq message must contain one superframe or one zmq_frame_header_t followed by a superframe. For the MPEG input, each zeromq message must contain one frame. Encryption is provided by zmq_curve, see the corresponding manpage. From the ZeroMQ manpage 'zmq': The 0MQ lightweight messaging kernel is a library which extends the standard socket interfaces with features traditionally provided by specialised messaging middleware products. 0MQ sockets provide an abstraction of asynchronous message queues, multiple messaging patterns, message filtering (subscriptions), seamless access to multiple transport protocols and more. */ /* 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 "input/Zmq.h" #include #include #include #include #include #include #include #include #include "PcDebug.h" #include "Log.h" #include "zmq.hpp" #ifdef __MINGW32__ # define bzero(s, n) memset(s, 0, n) #endif namespace Inputs { using namespace std; int readkey(string& keyfile, char* key) { FILE* fd = fopen(keyfile.c_str(), "r"); if (fd == nullptr) { return -1; } int ret = fread(key, CURVE_KEYLEN, 1, fd); fclose(fd); if (ret == 0) { return -1; } /* It needs to be zero-terminated */ key[CURVE_KEYLEN] = '\0'; return 0; } /***** Common functions (MPEG and AAC) ******/ /* If necessary, unbind the socket, then check the keys, * if they are ok and encryption is required, set the * keys to the socket, and finally bind the socket * to the new address */ void ZmqBase::rebind() { if (not m_zmq_sock_bound_to.empty()) { try { m_zmq_sock.unbind(m_zmq_sock_bound_to.c_str()); } catch (const zmq::error_t& err) { etiLog.level(warn) << "ZMQ unbind for input " << m_rc_name << " failed"; } } m_zmq_sock_bound_to = ""; /* Load each key independently */ if (not m_config.curve_public_keyfile.empty()) { int rc = readkey(m_config.curve_public_keyfile, m_curve_public_key); if (rc < 0) { etiLog.level(warn) << "Invalid public key for input " << m_rc_name; INVALIDATE_KEY(m_curve_public_key); } } if (not m_config.curve_secret_keyfile.empty()) { int rc = readkey(m_config.curve_secret_keyfile, m_curve_secret_key); if (rc < 0) { etiLog.level(warn) << "Invalid secret key for input " << m_rc_name; INVALIDATE_KEY(m_curve_secret_key); } } if (not m_config.curve_encoder_keyfile.empty()) { int rc = readkey(m_config.curve_encoder_keyfile, m_curve_encoder_key); if (rc < 0) { etiLog.level(warn) << "Invalid encoder key for input " << m_rc_name; INVALIDATE_KEY(m_curve_encoder_key); } } /* If you want encryption, you need to have defined all * key files */ if ( m_config.enable_encryption and ( not (KEY_VALID(m_curve_public_key) and KEY_VALID(m_curve_secret_key) and KEY_VALID(m_curve_encoder_key) ) ) ) { throw std::runtime_error("When enabling encryption, all three " "keyfiles must be valid!"); } if (m_config.enable_encryption) { try { /* We want to check that the encoder is the right one, * so the encoder is the CURVE server. */ m_zmq_sock.setsockopt(ZMQ_CURVE_SERVERKEY, m_curve_encoder_key, CURVE_KEYLEN); } catch (const zmq::error_t& err) { std::ostringstream os; os << "ZMQ set encoder key for input " << m_rc_name << " failed" << err.what(); throw std::runtime_error(os.str()); } try { m_zmq_sock.setsockopt(ZMQ_CURVE_PUBLICKEY, m_curve_public_key, CURVE_KEYLEN); } catch (const zmq::error_t& err) { std::ostringstream os; os << "ZMQ set public key for input " << m_rc_name << " failed" << err.what(); throw std::runtime_error(os.str()); } try { m_zmq_sock.setsockopt(ZMQ_CURVE_SECRETKEY, m_curve_secret_key, CURVE_KEYLEN); } catch (const zmq::error_t& err) { std::ostringstream os; os << "ZMQ set secret key for input " << m_rc_name << " failed" << err.what(); throw std::runtime_error(os.str()); } } else { try { /* This forces the socket to go to the ZMQ_NULL auth * mechanism */ const int no = 0; m_zmq_sock.setsockopt(ZMQ_CURVE_SERVER, &no, sizeof(no)); } catch (const zmq::error_t& err) { etiLog.level(warn) << "ZMQ disable encryption keys for input " << m_rc_name << " failed: " << err.what(); } } // Prepare the ZMQ socket to accept connections try { m_zmq_sock.bind(m_inputUri.c_str()); } catch (const zmq::error_t& err) { std::ostringstream os; os << "ZMQ bind for input " << m_rc_name << " failed" << err.what(); throw std::runtime_error(os.str()); } m_zmq_sock_bound_to = m_inputUri; try { m_zmq_sock.setsockopt(ZMQ_SUBSCRIBE, nullptr, 0); } catch (const zmq::error_t& err) { std::ostringstream os; os << "ZMQ set socket options for input " << m_rc_name << " failed" << err.what(); throw std::runtime_error(os.str()); } } void ZmqBase::open(const std::string& inputUri) { m_inputUri = inputUri; /* Let caller handle exceptions when we open() */ rebind(); // We want to appear in the statistics ! m_stats.registerAtServer(); } void ZmqBase::close() { m_zmq_sock.close(); } int ZmqBase::setBitrate(int bitrate) { if (bitrate <= 0) { throw invalid_argument("Invalid bitrate " + to_string(bitrate) + " for " + m_name); } m_bitrate = bitrate; return bitrate; } // size corresponds to a frame size. It is constant for a given bitrate size_t ZmqBase::readFrame(uint8_t* buffer, size_t size) { /* We must *always* read data from the ZMQ socket, * to make sure that ZMQ internal buffers are emptied * quickly. It's the only way to control the buffers * of the whole path from encoder to our frame_buffer. */ const auto readsize = readFromSocket(size); /* Notify of a buffer overrun, and drop some frames */ if (m_frame_buffer.size() >= m_config.buffer_size) { m_stats.notifyOverrun(); /* If the buffer is really too full, we drop as many frames as needed * to get down to the prebuffering size. We would like to have our buffer * filled to the prebuffering length. */ if (m_frame_buffer.size() >= 1.5*m_config.buffer_size) { size_t over_max = m_frame_buffer.size() - m_config.prebuffering; while (over_max--) { m_frame_buffer.pop_front(); } } else { /* Our frame_buffer contains DAB logical frames. Five of these make one * AAC superframe. * * Dropping this superframe amounts to dropping 120ms of audio. * * We're actually not sure to drop five DAB logical frames * belonging to the same AAC superframe. It is assumed that no * receiver will crash because of this. At least, the DAB logical frame * vs. AAC superframe alignment is preserved. * * TODO: of course this assumption probably doesn't hold. Fix this ! * TODO: also, with MPEG, the above doesn't hold, so we drop five * frames even though we could drop less. * */ for (int frame_del_count = 0; frame_del_count < 5; frame_del_count++) { m_frame_buffer.pop_front(); } } } if (m_prebuf_current > 0) { if (readsize > 0) m_prebuf_current--; if (m_prebuf_current == 0) etiLog.log(info, "inputZMQ %s input pre-buffering complete", m_rc_name.c_str()); /* During prebuffering, give a zeroed frame to the mux */ m_stats.notifyUnderrun(); memset(buffer, 0, size); return size; } // Save stats data in bytes, not in frames m_stats.notifyBuffer(m_frame_buffer.size() * size); if (m_frame_buffer.empty()) { etiLog.log(warn, "inputZMQ %s input empty, re-enabling pre-buffering", m_rc_name.c_str()); // reset prebuffering m_prebuf_current = m_config.prebuffering; /* We have no data to give, we give a zeroed frame */ m_stats.notifyUnderrun(); memset(buffer, 0, size); return size; } else { /* Normal situation, give a frame from the frame_buffer */ auto& newframe = m_frame_buffer.front(); if (newframe.size() != size) { throw logic_error("Inconsistent ZMQ sizes"); } memcpy(buffer, newframe.data(), newframe.size()); m_frame_buffer.pop_front(); return size; } } size_t ZmqBase::readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) { // TODO add timestamps into the metadata and implement this memset(buffer, 0, size); return 0; } /******** MPEG input *******/ // Read a MPEG frame from the socket, and push to list int ZmqMPEG::readFromSocket(size_t framesize) { bool messageReceived = false; zmq::message_t msg; try { auto result = m_zmq_sock.recv(msg, zmq::recv_flags::dontwait); messageReceived = result.has_value(); if (not messageReceived) { return 0; } } catch (const zmq::error_t& err) { etiLog.level(error) << "Failed to receive MPEG frame from zmq socket " << m_rc_name << ": " << err.what(); } /* This is the old 'one superframe per ZMQ message' format */ uint8_t* data = (uint8_t*)msg.data(); size_t datalen = msg.size(); /* Look for the new zmq_frame_header_t format */ zmq_frame_header_t* frame = (zmq_frame_header_t*)msg.data(); if ( msg.size() >= sizeof(zmq_frame_header_t) and msg.size() == ZMQ_FRAME_SIZE(frame) and frame->version == 1 and frame->encoder == ZMQ_ENCODER_MPEG_L2) { datalen = frame->datasize; data = ZMQ_FRAME_DATA(frame); m_stats.notifyPeakLevels(frame->audiolevel_left, frame->audiolevel_right); } if (datalen == framesize) { if (m_frame_buffer.size() > m_config.buffer_size) { etiLog.level(warn) << "inputZMQ " << m_rc_name << " buffer full (" << m_frame_buffer.size() << ")," " dropping incoming frame !"; messageReceived = false; } else if (m_enable_input) { // copy the input frame blockwise into the frame_buffer vector framedata(framesize); copy(data, data + framesize, framedata.begin()); m_frame_buffer.push_back(move(framedata)); } else { return 0; } } else { etiLog.level(error) << "inputZMQ " << m_rc_name << " verify bitrate: recv'd " << msg.size() << " B" << ", need " << framesize << "."; messageReceived = false; } return messageReceived ? msg.size() : 0; } /******** AAC+ input *******/ // Read a AAC+ superframe from the socket, cut it into five frames, // and push to list int ZmqAAC::readFromSocket(size_t framesize) { bool messageReceived = false; zmq::message_t msg; try { auto result = m_zmq_sock.recv(msg, zmq::recv_flags::dontwait); messageReceived = result.has_value(); if (not messageReceived) { return 0; } } catch (const zmq::error_t& err) { etiLog.level(error) << "Failed to receive AAC superframe from zmq socket " << m_rc_name << ": " << err.what(); } /* This is the old 'one superframe per ZMQ message' format */ uint8_t* data = (uint8_t*)msg.data(); size_t datalen = msg.size(); /* Look for the new zmq_frame_header_t format */ zmq_frame_header_t* frame = (zmq_frame_header_t*)msg.data(); if ( msg.size() >= sizeof(zmq_frame_header_t) and msg.size() == ZMQ_FRAME_SIZE(frame) and frame->version == 1 and frame->encoder == ZMQ_ENCODER_AACPLUS) { datalen = frame->datasize; data = ZMQ_FRAME_DATA(frame); m_stats.notifyPeakLevels(frame->audiolevel_left, frame->audiolevel_right); } /* TS 102 563, Section 6: * Audio super frames are transported in five successive DAB logical frames * with additional error protection. */ if (datalen) { if (datalen == 5*framesize) { if (m_frame_buffer.size() > m_config.buffer_size) { etiLog.level(warn) << "inputZMQ " << m_rc_name << " buffer full (" << m_frame_buffer.size() << ")," " dropping incoming superframe !"; datalen = 0; } else if (m_enable_input) { // copy the input frame blockwise into the frame_buffer for (uint8_t* framestart = data; framestart < &data[5*framesize]; framestart += framesize) { vector audioframe(framesize); copy(framestart, framestart + framesize, audioframe.begin()); m_frame_buffer.push_back(move(audioframe)); } } else { datalen = 0; } } else { etiLog.level(error) << "inputZMQ " << m_rc_name << " verify bitrate: recv'd " << msg.size() << " B" << ", need " << 5*framesize << "."; datalen = 0; } } else { etiLog.level(error) << "inputZMQ " << m_rc_name << " invalid frame received"; } return datalen; } /********* REMOTE CONTROL ***********/ void ZmqBase::set_parameter(const string& parameter, const string& value) { if (parameter == "buffer") { size_t new_limit = atol(value.c_str()); if (new_limit < INPUT_ZMQ_MIN_BUFFER_SIZE) { throw ParameterError("Desired buffer size too small." " Minimum " STRINGIFY(INPUT_ZMQ_MIN_BUFFER_SIZE) ); } else if (new_limit > INPUT_ZMQ_MAX_BUFFER_SIZE) { throw ParameterError("Desired buffer size too large." " Maximum " STRINGIFY(INPUT_ZMQ_MAX_BUFFER_SIZE) ); } m_config.buffer_size = new_limit; } else if (parameter == "prebuffering") { size_t new_prebuf = atol(value.c_str()); if (new_prebuf < INPUT_ZMQ_MIN_BUFFER_SIZE) { throw ParameterError("Desired prebuffering too small." " Minimum " STRINGIFY(INPUT_ZMQ_MIN_BUFFER_SIZE) ); } else if (new_prebuf > INPUT_ZMQ_MAX_BUFFER_SIZE) { throw ParameterError("Desired prebuffering too large." " Maximum " STRINGIFY(INPUT_ZMQ_MAX_BUFFER_SIZE) ); } m_config.prebuffering = new_prebuf; } else if (parameter == "enable") { if (value == "1") { m_enable_input = true; } else if (value == "0") { m_enable_input = false; } else { throw ParameterError("Value not understood, specify 0 or 1."); } } else if (parameter == "encryption") { if (value == "1") { m_config.enable_encryption = true; } else if (value == "0") { m_config.enable_encryption = false; } else { throw ParameterError("Value not understood, specify 0 or 1."); } try { rebind(); } catch (const std::runtime_error &e) { stringstream ss; ss << "Could not bind socket again with new keys." << e.what(); throw ParameterError(ss.str()); } } else if (parameter == "secretkey") { m_config.curve_secret_keyfile = value; } else if (parameter == "publickey") { m_config.curve_public_keyfile = value; } else if (parameter == "encoderkey") { m_config.curve_encoder_keyfile = value; } else { stringstream ss; ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } } const string ZmqBase::get_parameter(const string& parameter) const { stringstream ss; if (parameter == "buffer") { ss << m_config.buffer_size; } else if (parameter == "prebuffering") { ss << m_config.prebuffering; } else if (parameter == "enable") { if (m_enable_input) ss << "true"; else ss << "false"; } else if (parameter == "encryption") { if (m_config.enable_encryption) ss << "true"; else ss << "false"; } else if (parameter == "secretkey") { ss << m_config.curve_secret_keyfile; } else if (parameter == "publickey") { ss << m_config.curve_public_keyfile; } else if (parameter == "encoderkey") { ss << m_config.curve_encoder_keyfile; } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); } return ss.str(); } const json::map_t ZmqBase::get_all_values() const { json::map_t map; map["buffer"].v = m_config.buffer_size; map["prebuffering"].v = m_config.prebuffering; map["enable"].v = m_enable_input; map["encryption"].v = m_config.enable_encryption; map["secretkey"].v = m_config.curve_secret_keyfile; map["publickey"].v = m_config.curve_public_keyfile; map["encoderkey"].v = m_config.curve_encoder_keyfile; return map; } }; Opendigitalradio-ODR-DabMux-29c710c/src/input/Zmq.h000066400000000000000000000202151476627344300220100ustar00rootroot00000000000000/* Copyright (C) 2009 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2019 Matthias P. Braendli http://www.opendigitalradio.org ZeroMQ input. see www.zeromq.org for more info For the AAC+ input, each zeromq message must contain one superframe, or one zmq_frame_header_t followed by a superframe. For the MPEG input, each zeromq message must contain one frame. Encryption is provided by zmq_curve, see the corresponding manpage. From the ZeroMQ manpage 'zmq': The 0MQ lightweight messaging kernel is a library which extends the standard socket interfaces with features traditionally provided by specialised messaging middleware products. 0MQ sockets provide an abstraction of asynchronous message queues, multiple messaging patterns, message filtering (subscriptions), seamless access to multiple transport protocols and more. */ /* This file is part of ODR-DabMux. It defines a ZeroMQ input for audio and dabplus data. 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 . */ #pragma once #include #include #include #include #include "zmq.hpp" #include "input/inputs.h" #include "ManagementServer.h" namespace Inputs { /* The frame_buffer contains DAB logical frames as defined in * TS 102 563, section 6. * Five elements of this buffer make one AAC superframe (120ms audio) */ // Minimum frame_buffer size in number of elements // This is one AAC superframe, but you probably don't want to // go that low anyway. const size_t INPUT_ZMQ_MIN_BUFFER_SIZE = 5*1; // 120ms // Maximum frame_buffer size in number of elements // One minute is clearly way over what everybody would // want. const size_t INPUT_ZMQ_MAX_BUFFER_SIZE = 5*500; // 60s /* The ZeroMQ Curve key is 40 bytes long in Z85 representation * * But we need to store it as zero-terminated string. */ const size_t CURVE_KEYLEN = 40; /* helper to invalidate a key */ #define INVALIDATE_KEY(k) memset(k, 0, CURVE_KEYLEN+1) /* Verification for key validity */ #define KEY_VALID(k) (k[0] != '\0') /* Read a key from file into key * * Returns 0 on success, negative value on failure */ int readkey(std::string& keyfile, char* key); struct dab_input_zmq_config_t { /* The size of the internal buffer, measured in number * of elements. * * Each element corresponds to five frames, * or one AAC superframe. */ size_t buffer_size; /* The amount of prebuffering to do before we start streaming * * Same units as buffer_size */ size_t prebuffering; /* Whether to enforce encryption or not */ bool enable_encryption; /* Full path to file containing public key. */ std::string curve_public_keyfile; /* Full path to file containing secret key. */ std::string curve_secret_keyfile; /* Full path to file containing encoder public key. */ std::string curve_encoder_keyfile; }; #define ZMQ_ENCODER_AACPLUS 1 #define ZMQ_ENCODER_MPEG_L2 2 /* This defines the on-wire representation of a ZMQ message header. * * The data follows right after this header */ struct zmq_frame_header_t { uint16_t version; // we support version=1 now uint16_t encoder; // see ZMQ_ENCODER_XYZ /* length of the 'data' field */ uint32_t datasize; /* Audio level, peak, linear PCM */ int16_t audiolevel_left; int16_t audiolevel_right; /* Data follows this header */ } __attribute__ ((packed)); /* The expected frame size incl data of the given frame */ #define ZMQ_FRAME_SIZE(f) (sizeof(zmq_frame_header_t) + f->datasize) #define ZMQ_FRAME_DATA(f) ( ((uint8_t*)f)+sizeof(zmq_frame_header_t) ) class ZmqBase : public InputBase, public RemoteControllable { public: ZmqBase(const std::string& name, dab_input_zmq_config_t config) : RemoteControllable(name), m_zmq_context(1), m_zmq_sock(m_zmq_context, ZMQ_SUB), m_zmq_sock_bound_to(""), m_bitrate(0), m_enable_input(true), m_config(config), m_name(name), m_stats(name), m_prebuf_current(config.prebuffering) { RC_ADD_PARAMETER(enable, "If the input is enabled. Set to zero to empty the buffer."); RC_ADD_PARAMETER(encryption, "If encryption is enabled or disabled [1 or 0]." " If 1 is written, the keys are reloaded."); RC_ADD_PARAMETER(publickey, "The multiplexer's public key file."); RC_ADD_PARAMETER(secretkey, "The multiplexer's secret key file."); RC_ADD_PARAMETER(encoderkey, "The encoder's public key file."); /* Set all keys to zero */ INVALIDATE_KEY(m_curve_public_key); INVALIDATE_KEY(m_curve_secret_key); INVALIDATE_KEY(m_curve_encoder_key); } virtual void open(const std::string& inputUri); virtual size_t readFrame(uint8_t *buffer, size_t size); virtual size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); virtual int setBitrate(int bitrate); virtual void close(); /* Remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value); /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const; virtual const json::map_t get_all_values() const; protected: virtual int readFromSocket(size_t framesize) = 0; virtual void rebind(); zmq::context_t m_zmq_context; zmq::socket_t m_zmq_sock; // handle for the zmq socket /* If the socket is bound, this saves the endpoint, * otherwise, it's an empty string */ std::string m_zmq_sock_bound_to; int m_bitrate; /* set this to zero to empty the input buffer */ bool m_enable_input; /* stores elements of type char[] */ std::list > m_frame_buffer; dab_input_zmq_config_t m_config; /* Key management, keys need to be zero-terminated */ char m_curve_public_key[CURVE_KEYLEN+1]; char m_curve_secret_key[CURVE_KEYLEN+1]; char m_curve_encoder_key[CURVE_KEYLEN+1]; std::string m_inputUri; std::string m_name; InputStat m_stats; private: size_t m_prebuf_current; }; class ZmqMPEG : public ZmqBase { public: ZmqMPEG(const std::string& name, dab_input_zmq_config_t config) : ZmqBase(name, config) { RC_ADD_PARAMETER(buffer, "Size of the input buffer [mpeg frames]"); RC_ADD_PARAMETER(prebuffering, "Min buffer level before streaming starts [mpeg frames]"); } private: virtual int readFromSocket(size_t framesize); }; class ZmqAAC : public ZmqBase { public: ZmqAAC(const std::string& name, dab_input_zmq_config_t config) : ZmqBase(name, config) { RC_ADD_PARAMETER(buffer, "Size of the input buffer [aac superframes]"); RC_ADD_PARAMETER(prebuffering, "Min buffer level before streaming starts [aac superframes]"); } private: virtual int readFromSocket(size_t framesize); }; }; Opendigitalradio-ODR-DabMux-29c710c/src/input/inputs.h000066400000000000000000000066731476627344300225770ustar00rootroot00000000000000/* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 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 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 . */ #pragma once #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "Log.h" #include "RemoteControl.h" #include namespace Inputs { enum class BufferManagement { // Use a buffer in the input that doesn't consider timestamps Prebuffering, // Buffer incoming data until a given timestamp is reached Timestamped, }; /* New input object base */ class InputBase { public: /* Throws runtime_error or invalid_argument on failure */ virtual void open(const std::string& name) = 0; /* read a frame from the input. Buffer management is either not necessary * (e.g. File input) or done with pre-buffering (network-based inputs). * * This ignores timestamps. All inputs support this. * * Returns number of data bytes written to the buffer. May clear the buffer * if no data bytes available, in which case it will return 0. * * Returns negative on error. */ virtual size_t readFrame(uint8_t *buffer, size_t size) = 0; /* read a frame from the input, taking into account timestamp. The timestamp of the data * returned is not more recent than the timestamp specified in seconds and tsta. * * seconds is in UNIX epoch, utco is the TAI-UTC offset, tsta is in the format used by ETI. * * Returns number of data bytes written to the buffer. May clear the buffer * if no data bytes available, in which case it will return 0. * * Returns negative on error. * * Calling this function on inputs that do not support timestamps returns 0. This allows * changing the buffer management at runtime without risking an crash due to an exception. */ virtual size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta) = 0; /* Returns the effectively used bitrate, or throws invalid_argument on invalid bitrate */ virtual int setBitrate(int bitrate) = 0; virtual void close() = 0; virtual ~InputBase() {} void setTistDelay(const std::chrono::milliseconds& ms) { m_tist_delay = ms; } void setBufferManagement(BufferManagement bm) { m_bufferManagement = bm; } BufferManagement getBufferManagement() const { return m_bufferManagement; } protected: InputBase() {} std::atomic m_bufferManagement = ATOMIC_VAR_INIT(BufferManagement::Prebuffering); std::chrono::milliseconds m_tist_delay; }; }; Opendigitalradio-ODR-DabMux-29c710c/src/mpeg.c000066400000000000000000000164101476627344300210270ustar00rootroot00000000000000/* 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 "mpeg.h" #include #include static const short bitrateArray[4][4][16] = { { // MPEG 2.5 { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // layer invalid { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // layer 3 { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // layer 2 { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 } // layer 1 }, { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MPEG invalid { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 } }, { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MPEG 2 { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 } }, { { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MPEG 1 { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 }, { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 } } }; static const int samplingrateArray[4][4] = { { 11025, 12000, 8000, 0 }, // MPEG 2.5 { -1, -1, -1, -1 }, // MPEG invalid { 22050, 24000, 16000, 0 }, // MPEG 2 { 44100, 48000, 32000, 0 } // MPEG 1 }; short getMpegBitrate(void* data) { mpegHeader* header = (mpegHeader*)data; return bitrateArray[header->id][header->layer][header->bitrate]; } int getMpegFrequency(void* data) { mpegHeader* header = (mpegHeader*)data; return samplingrateArray[header->id][header->samplingrate]; } int getMpegFrameLength(mpegHeader* header) { short bitrate = getMpegBitrate(header); int samplingrate = getMpegFrequency(header); int framelength = -1; if (bitrate <= 0) { return -1; } if (samplingrate <= 0) { return -1; } switch (header->layer) { case 1: // layer III case 2: // layer II framelength = (int)144000 * bitrate / samplingrate + header->padding; break; case 3: // layer I framelength = (12 * bitrate / samplingrate + header->padding) * 4; break; default: framelength = -1; } return framelength; } /** * This function replace the read function by trying many times a reading. * It tries to read until all bytes are read. Very useful when reading from a * pipe because sometimes 2 pass is necessary to read all bytes. * @param file File descriptor. * @param data Address of the buffer to write data into. * @param size Number of bytes to read. * @param tries Max number of tries to read. * @return Same as read function: * Nb of bytes read. * -1 if error. */ ssize_t readData(int file, void* data, size_t size, unsigned int tries) { ssize_t result; size_t offset = 0; if (size == 0) return 0; if (tries == 0) return 0; result = read(file, data, size); if (result == -1) { if (errno == EAGAIN) { return readData(file, data, size, tries - 1); } return -1; } offset = result; size -= offset; data = (char*)data + offset; result = readData(file, data, size, tries - 1); if (result == -1) { return -1; } offset += result; return offset; } int readMpegHeader(int file, void* data, int size) { // Max byte reading for searching sync unsigned int flagmax = 0; unsigned long* syncword = (unsigned long*)data; int result; if (size < 4) return MPEG_BUFFER_OVERFLOW; // Mpeg header reading result = readData(file, data, 4, 2); if (result == 0) return MPEG_FILE_EMPTY; if (result == -1) { fprintf(stderr, "header\n"); return MPEG_FILE_ERROR; } if (result < 4) { return MPEG_BUFFER_UNDERFLOW; } while ((*syncword & 0xe0ff) != 0xe0ff) { *syncword >>= 8; result = readData(file, (char*)data + 3, 1, 2); if (result == 0) { return MPEG_FILE_EMPTY; } else if (result == -1) { return MPEG_FILE_ERROR; } if (++flagmax > 1200) { return MPEG_SYNC_NOT_FOUND; } } return 4; } int readMpegFrame(int file, void* data, int size) { mpegHeader* header = (mpegHeader*)data; int framelength; int result; framelength = getMpegFrameLength(header); if (framelength < 0) return MPEG_INVALID_FRAME; if (framelength > size) { //lseek(file, framelength - 4, SEEK_CUR); //return MPEG_BUFFER_OVERFLOW; result = readData(file, ((char*)data) + 4, size - 4, 2); if (size == 4) { lseek(file, framelength - size, SEEK_CUR); result = framelength - 4; } } else { result = readData(file, ((char*)data) + 4, framelength - 4, 2); } if (result == 0) return MPEG_FILE_EMPTY; if (result == -1) { return MPEG_FILE_ERROR; } if (result < framelength - 4) { return MPEG_BUFFER_UNDERFLOW; } return framelength; } int checkDabMpegFrame(void* data) { mpegHeader* header = (mpegHeader*)data; unsigned long* headerData = (unsigned long*)data; if ((*headerData & 0x0f0ffcff) == 0x0004fcff) return 0; if ((*headerData & 0x0f0ffcff) == 0x0004f4ff) return 0; if (getMpegFrequency(header) != 48000) { if (getMpegFrequency(header) != 24000) { return MPEG_FREQUENCY; } } if (header->padding != 0) { return MPEG_PADDING; } if (header->emphasis != 0) { return MPEG_EMPHASIS; } if (header->copyright != 0) { return MPEG_COPYRIGHT; } if (header->original != 0) { return MPEG_ORIGINAL; } return -1; } Opendigitalradio-ODR-DabMux-29c710c/src/mpeg.h000066400000000000000000000043571476627344300210430ustar00rootroot00000000000000/* 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 _MPEG #define _MPEG #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef _WIN32 # include # include # include # define ssize_t SSIZE_T #else # include #endif #ifdef __cplusplus extern "C" { #endif typedef struct { unsigned long sync1:8; unsigned long protection:1; unsigned long layer:2; unsigned long id:2; unsigned long sync2:3; unsigned long priv:1; unsigned long padding:1; unsigned long samplingrate:2; unsigned long bitrate:4; unsigned long emphasis:2; unsigned long original:1; unsigned long copyright:1; unsigned long mode:2; unsigned long channel:2; } mpegHeader; extern short getMpegBitrate(void* data); extern int getMpegFrequency(void* data); int getMpegFrameLength(mpegHeader* header); ssize_t readData(int file, void* data, size_t size, unsigned int tries); #define MPEG_BUFFER_OVERFLOW -2 #define MPEG_FILE_EMPTY -3 #define MPEG_FILE_ERROR -4 #define MPEG_SYNC_NOT_FOUND -5 #define MPEG_INVALID_FRAME -6 #define MPEG_BUFFER_UNDERFLOW -7 int readMpegHeader(int file, void* data, int size); int readMpegFrame(int file, void* data, int size); #define MPEG_FREQUENCY -2 #define MPEG_PADDING -3 #define MPEG_COPYRIGHT -4 #define MPEG_ORIGINAL -5 #define MPEG_EMPHASIS -6 int checkDabMpegFrame(void* data); #ifdef __cplusplus } #endif #endif // _MPEG Opendigitalradio-ODR-DabMux-29c710c/src/test_statsserver.sh000077500000000000000000000002011476627344300237050ustar00rootroot00000000000000clang++ -Wall --include=../config.h StatsServer.cpp TestStatsServer.cpp Log.cpp -lboost_system -lboost_thread -o test && ./test Opendigitalradio-ODR-DabMux-29c710c/src/utils.cpp000066400000000000000000000535321476627344300216050ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2021 Matthias P. Braendli, matthias.braendli@mpb.li http://www.opendigitalradio.org */ /* 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 #include #include #include #include #include "utils.h" #include "fig/FIG0structs.h" using namespace std; static time_t dab_time_seconds = 0; static int dab_time_millis = 0; static void printServices(const vector >& services); void update_dab_time() { if (dab_time_seconds == 0) { dab_time_seconds = time(nullptr); } else { dab_time_millis+= 24; if (dab_time_millis >= 1000) { dab_time_millis -= 1000; ++dab_time_seconds; } } } void get_dab_time(time_t *time, uint32_t *millis) { *time = dab_time_seconds; *millis = dab_time_millis; } uint32_t gregorian2mjd(int year, int month, int day) { //This is the algorithm for the JD, just substract 2400000.5 for MJD year += 8000; if(month < 3) { year--; month += 12; } uint32_t JD = (year * 365) + (year / 4) - (year / 100) + (year / 400) - 1200820 + ((month * 153 + 3) / 5) - 92 + (day - 1); return (uint32_t)(JD - 2400000.5); //truncation, loss of data OK! } /* We use fprintf here because this doesn't have * to go to the log. * But all information below must go into the log. * * Do not use \n in the log, it messes presentation and logging * up. */ void header_message() { fprintf(stderr, "Welcome to %s %s, compiled at %s, %s", PACKAGE_NAME, #if defined(GITVERSION) GITVERSION, #else PACKAGE_VERSION, #endif __DATE__, __TIME__); fprintf(stderr, "\n\n"); fprintf(stderr, "Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012\n"); fprintf(stderr, "Her Majesty the Queen in Right of Canada\n"); fprintf(stderr, "(Communications Research Centre Canada)\n\n"); fprintf(stderr, "Copyright (C) 2024 Matthias P. Braendli\n"); fprintf(stderr, "LICENCE: GPLv3+\n\n"); fprintf(stderr, "http://opendigitalradio.org\n\n"); fprintf(stderr, "Input URLs supported: prbs udp file zmq\n"); fprintf(stderr, "Inputs format supported: raw mpeg packet epm\n"); std::cerr << "Outputs supported: " << #if defined(HAVE_OUTPUT_FILE) " file" << #endif #if defined(HAVE_OUTPUT_FIFO) " fifo" << #endif #if defined(HAVE_OUTPUT_UDP) " udp" << #endif #if defined(HAVE_OUTPUT_TCP) " tcp" << #endif #if defined(HAVE_OUTPUT_RAW) " raw" << #endif #if defined(HAVE_OUTPUT_SIMUL) " simul" << #endif " edi zmq\n\n"; } void printUsage(char *name, FILE* out) { fprintf(out, "NAME\n"); fprintf(out, " %s - A software DAB multiplexer\n", name); fprintf(out, "\nSYNOPSYS\n"); fprintf(out, " This software requires a configuration file:\n"); fprintf(out, " %s configuration.mux\n", name); fprintf(out, " See doc/example.config for an example format for the configuration file\n"); fprintf(out, "\nDESCRIPTION\n"); fprintf(out, " %s is a software multiplexer that generates an ETI stream from\n" " audio and data streams. Because of its software based architecture,\n" " many typical DAB services can be generated and multiplexed on a single\n" " PC platform with live or pre-recorded sources.\n" "\n" " A DAB multiplex configuration is composed of one ensemble. An ensemble\n" " is the entity that receivers tune to and process. An ensemble contains\n" " several services. A service is the listener-selectable output. Each\n" " service contains one mandatory service component which is called pri-\n" " mary component. An audio primary component define a program service\n" " while a data primary component define a data service. Service can con-\n" " tain additional components which are called secondary components. Maxi-\n" " mum total number of components is 12 for program services and 11 for\n" " data services. A service component is a link to one subchannel (of Fast\n" " Information Data Channel). A subchannel is the physical space used\n" " within the common interleaved frame.\n" "\n" " __________________________________________________\n" " | Ensemble | ENSEMBLE\n" " |__________________________________________________|\n" " | | |\n" " | | |\n" " _______V______ _______V______ _______V______\n" " | Service 1 | | Service 2 | | Service 3 | SERVICES\n" " |______________| |______________| |______________|\n" " | | | | |______ |\n" " | | | | | |\n" " __V__ __V__ __V__ __V__ __V__ __V__\n" " | SC1 | | SC2 | | SC3 | | SC4 | | SC5 | | SC6 | SERVICE\n" " |_____| |_____| |_____| |_____| |_____| |_____| COMPONENTS\n" " | | _____| | | ____|\n" " | | | | | |\n" " __V________V__V______________V________V___V_______ COMMON\n" " | SubCh1 | SubCh9 | ... | SubCh3 | SubCh60 | ... | INTERLEAVED\n" " |________|________|_______|________|_________|_____| FRAME\n" " Figure 1: An example of a DAB multiplex configuration\n", name); } void printOutputs(const vector >& outputs) { int index = 0; for (auto output : outputs) { etiLog.log(info, "Output %i", index); etiLog.level(info) << " URI: " << output->get_info(); ++index; } } void printServices(const vector >& services) { int index = 0; etiLog.log(info, "--- Services list ---"); for (auto service : services) { etiLog.level(info) << "Service " << service->get_rc_name(); etiLog.level(info) << " label: " << service->label.long_label(); etiLog.level(info) << " short label: " << service->label.short_label(); etiLog.log(info, " (0x%x)", service->label.flag()); etiLog.level(info) << " FIG2 label: " << service->label.fig2_label(); etiLog.log(info, " id: 0x%lx (%lu)", service->id, service->id); etiLog.log(info, " pty: 0x%x (%u) %s", service->pty_settings.pty, service->pty_settings.pty, service->pty_settings.dynamic_no_static ? "Dynamic" : "Static"); etiLog.log(info, " language: 0x%x (%u)", service->language, service->language); etiLog.log(info, " announcements: 0x%x", service->ASu); std::vector clusters_s; for (const auto& cluster : service->clusters) { clusters_s.push_back(std::to_string(cluster)); } etiLog.level(info) << " clusters: " << boost::join(clusters_s, ","); ++index; } } static void write_uatype_to_stringstream(stringstream& ss, uint16_t uaType) { ss << "app type: "; switch (uaType) { case FIG0_13_APPTYPE_SLIDESHOW: ss << "MOT Slideshow"; break; case FIG0_13_APPTYPE_WEBSITE: ss << "MOT Broadcast Website"; break; case FIG0_13_APPTYPE_TPEG: ss << "TPEG"; break; case FIG0_13_APPTYPE_DGPS: ss << "DGPS"; break; case FIG0_13_APPTYPE_TMC: ss << "TMC"; break; case FIG0_13_APPTYPE_SPI: ss << "SPI/EPG"; break; case FIG0_13_APPTYPE_DABJAVA: ss << "DAB Java"; break; case FIG0_13_APPTYPE_JOURNALINE: ss << "Journaline"; break; default: ss << "Unknown: " << uaType; break; } } void printComponent(const shared_ptr& component, const std::shared_ptr& ensemble) { etiLog.log(info, " service id: 0x%x (%u)", component->serviceId, component->serviceId); etiLog.log(info, " subchannel id: 0x%x (%u)", component->subchId, component->subchId); etiLog.level(info) << " label: " << component->label.long_label(); etiLog.level(info) << " short label: " << component->label.short_label(); etiLog.log(info, " (0x%x)", component->label.flag()); etiLog.level(info) << " FIG2 label: " << component->label.fig2_label(); etiLog.log(info, " service component type: 0x%x (%u)", component->type, component->type); if (component->isPacketComponent(ensemble->subchannels)) { etiLog.log(info, " (packet) id: 0x%x (%u)", component->packet.id, component->packet.id); etiLog.log(info, " (packet) address: %u", component->packet.address); etiLog.log(info, " (packet) datagroup: %u", component->packet.datagroup); for (const auto& userapp : component->packet.uaTypes) { stringstream ss; ss << " (packet) "; write_uatype_to_stringstream(ss, userapp.uaType); etiLog.level(info) << ss.str(); } } else { for (const auto& userapp : component->audio.uaTypes) { stringstream ss; ss << " (packet) "; write_uatype_to_stringstream(ss, userapp.uaType); etiLog.level(info) << ss.str(); } } } void printSubchannels(const vec_sp_subchannel& subchannels) { int index = 0; int total_num_cu = 0; etiLog.log(info, "--- Subchannels list ---"); for (auto subchannel : subchannels) { dabProtection* protection = &subchannel->protection; etiLog.level(info) << "Subchannel " << subchannel->uid; etiLog.log(info, " input"); etiLog.level(info) << " URI: " << subchannel->inputUri; switch (subchannel->type) { case subchannel_type_t::DABAudio: etiLog.log(info, " type: DABAudio"); break; case subchannel_type_t::DABPlusAudio: etiLog.log(info, " type: DABPlusAudio"); break; case subchannel_type_t::DataDmb: etiLog.log(info, " type: data"); break; case subchannel_type_t::Packet: etiLog.log(info, " type: packet"); break; default: etiLog.log(info, " type: unknown"); break; } etiLog.log(info, " id: 0x%x (%u)", subchannel->id, subchannel->id); etiLog.log(info, " bitrate: %i", subchannel->bitrate); if (protection->form == UEP) { etiLog.log(info, " protection: UEP %i", protection->level + 1); etiLog.log(info, " index: %i", protection->uep.tableIndex); } else { etiLog.log(info, " protection: EEP %i-%c", protection->level + 1, protection->eep.profile == EEP_A ? 'A' : 'B'); etiLog.log(info, " option: %i", protection->eep.GetOption()); etiLog.log(info, " level: %i", subchannel->protection.level); } etiLog.log(info, " SAD: %u", subchannel->startAddress); etiLog.log(info, " size (CU): %i", subchannel->getSizeCu()); total_num_cu += subchannel->getSizeCu(); ++index; } etiLog.log(info, "Total ensemble size (CU): %i", total_num_cu); } static void printLinking(const shared_ptr& ensemble) { etiLog.log(info, " Linkage Sets"); if (ensemble->linkagesets.empty()) { etiLog.level(info) << " None "; } for (const auto& ls : ensemble->linkagesets) { etiLog.level(info) << " set " << ls->get_name(); etiLog.log(info, " LSN 0x%04x", ls->lsn); etiLog.level(info) << " active " << (ls->active ? "true" : "false"); etiLog.level(info) << " " << (ls->hard ? "hard" : "soft"); etiLog.level(info) << " international " << (ls->international ? "true" : "false"); etiLog.level(info) << " key service " << ls->keyservice; etiLog.level(info) << " ID list"; for (const auto& link : ls->id_list) { switch (link.type) { case ServiceLinkType::DAB: etiLog.level(info) << " type DAB"; break; case ServiceLinkType::FM: etiLog.level(info) << " type FM"; break; case ServiceLinkType::DRM: etiLog.level(info) << " type DRM"; break; case ServiceLinkType::AMSS: etiLog.level(info) << " type AMSS"; break; } etiLog.log(info, " id 0x%04x", link.id); if (ls->international) { etiLog.log(info, " ecc 0x%04x", link.ecc); } } } etiLog.log(info, " Services in other ensembles"); if (ensemble->service_other_ensemble.empty()) { etiLog.level(info) << " None "; } for (const auto& s_oe : ensemble->service_other_ensemble) { int oe = 1; for (const auto& local_service : ensemble->services) { if (local_service->id == s_oe.service_id) { oe = 0; break; } } etiLog.log(info, " Service 0x%lx", s_oe.service_id); for (const auto oe : s_oe.other_ensembles) { etiLog.log(info, " Available in ensemble 0x%04x", oe); } etiLog.log(info, " OE=%d", oe); } } static void printFrequencyInformation(const shared_ptr& ensemble) { etiLog.log(info, " Frequency Information"); if (ensemble->frequency_information.empty()) { etiLog.level(info) << " None "; } for (const auto& fi : ensemble->frequency_information) { etiLog.level(info) << " FI " << fi.uid; etiLog.level(info) << " OE=" << (fi.other_ensemble ? 1 : 0); switch (fi.rm) { case RangeModulation::dab_ensemble: etiLog.level(info) << " RM: DAB"; etiLog.log(info, " EId 0x%04x", fi.fi_dab.eid); break; case RangeModulation::drm: etiLog.level(info) << " RM: DRM"; etiLog.log(info, " DRM Id 0x%06x", fi.fi_drm.drm_service_id); break; case RangeModulation::fm_with_rds: etiLog.level(info) << " RM: FM (with RDS)"; etiLog.log(info, " PI-Code 0x%04x", fi.fi_fm.pi_code); break; case RangeModulation::amss: etiLog.level(info) << " RM: AMSS"; etiLog.log(info, " AMSS Id 0x%06x", fi.fi_amss.amss_service_id); break; } etiLog.level(info) << " continuity " << (fi.continuity ? "true" : "false"); switch (fi.rm) { case RangeModulation::dab_ensemble: for (const auto& f : fi.fi_dab.frequencies) { stringstream ss; ss << " " << f.uid << " "; switch (f.control_field) { case FrequencyInfoDab::ControlField_e::adjacent_no_mode: ss << "Adjacent, w/o mode indication, "; break; case FrequencyInfoDab::ControlField_e::adjacent_mode1: ss << "Adjacent, mode I, "; break; case FrequencyInfoDab::ControlField_e::disjoint_no_mode: ss << "Disjoint, w/o mode indication, "; break; case FrequencyInfoDab::ControlField_e::disjoint_mode1: ss << "Disjoint, mode I, "; break; } ss << f.frequency; etiLog.level(info) << ss.str(); } break; case RangeModulation::drm: etiLog.log(info, " ID 0x%02x", fi.fi_drm.drm_service_id); for (const auto& f : fi.fi_drm.frequencies) { etiLog.level(info) << " " << f; } break; case RangeModulation::fm_with_rds: for (const auto& f : fi.fi_fm.frequencies) { etiLog.level(info) << " " << f; } break; case RangeModulation::amss: etiLog.log(info, " ID 0x%02x", fi.fi_amss.amss_service_id); for (const auto& f : fi.fi_amss.frequencies) { etiLog.level(info) << " " << f; } break; } } } void printEnsemble(const shared_ptr& ensemble) { etiLog.log(info, "--- Multiplex configuration ---"); etiLog.log(info, "Ensemble"); etiLog.log(info, " id: 0x%lx (%lu)", ensemble->id, ensemble->id); etiLog.log(info, " ecc: 0x%x (%u)", ensemble->ecc, ensemble->ecc); etiLog.level(info) << " label: " << ensemble->label.long_label(); etiLog.level(info) << " short label: " << ensemble->label.short_label(); etiLog.log(info, " (0x%x)", ensemble->label.flag()); etiLog.level(info) << " FIG2 label: " << ensemble->label.fig2_label(); switch (ensemble->transmission_mode) { case TransmissionMode_e::TM_I: etiLog.log(info, " mode: TM I"); break; case TransmissionMode_e::TM_II: etiLog.log(info, " mode: TM II"); break; case TransmissionMode_e::TM_III: etiLog.log(info, " mode: TM III"); break; case TransmissionMode_e::TM_IV: etiLog.log(info, " mode: TM IV"); break; } if (ensemble->lto_auto) { time_t now = time(nullptr); struct tm ltime; localtime_r(&now, <ime); time_t now2 = timegm(<ime); etiLog.log(info, " lto: %2.1f hours", 0.5 * (now2 - now) / 1800); } else { etiLog.log(info, " lto: %2.1f hours", 0.5 * ensemble->lto); } etiLog.log(info, " intl. table. %d", ensemble->international_table); if (ensemble->clusters.empty()) { etiLog.level(info) << " No announcement clusters defined"; } else { etiLog.level(info) << " Announcement clusters:"; for (const auto& cluster : ensemble->clusters) { etiLog.level(info) << " " << cluster->tostring(); } } printLinking(ensemble); printFrequencyInformation(ensemble); printSubchannels(ensemble->subchannels); printServices(ensemble->services); etiLog.log(info, "--- Components list ---"); for (const auto& component : ensemble->components) { etiLog.level(info) << "Component " << component->get_rc_name(); printComponent(component, ensemble); } } unsigned long hexparse(const std::string& input) { unsigned long value = 0; errno = 0; char* endptr = nullptr; const bool is_hex = (input.find("0x") == 0); // Do not use strtoul's base=0 because // we do not want to accept octal. const int base = is_hex ? 16 : 10; const char* const startptr = is_hex ? input.c_str() + 2 : input.c_str(); // Comments taken from manpage value = strtoul(startptr, &endptr, base); if (value == ULONG_MAX and errno == ERANGE) { // If an overflow occurs, strtoul() returns ULONG_MAX. // In both cases, errno is set to ERANGE. throw out_of_range("hexparse: value out of range"); } else if (value == 0 and errno != 0) { // The implementation may also set errno to EINVAL in case no // conversion was performed (no digits seen, and 0 returned). stringstream ss; ss << "hexparse: " << strerror(errno); throw invalid_argument(ss.str()); } else if (startptr == endptr) { // If there were no digits at all, strtoul() stores the original value // of nptr in *endptr (and returns 0). throw out_of_range("hexparse: no value found"); } else if (*endptr != '\0') { throw out_of_range("hexparse: superfluous characters after value found: '" + input + "'"); } return value; } bool stringEndsWith(const std::string& fullString, const std::string& ending) { return fullString.length() >= ending.length() and fullString.compare(fullString.length() - ending.length(), ending.length(), ending) == 0; } Opendigitalradio-ODR-DabMux-29c710c/src/utils.h000066400000000000000000000050021476627344300212370ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2020 Matthias P. Braendli, matthias.braendli@mpb.li This file contains a set of utility functions that are used to show useful information to the user, and handles time and date for the the signalling. */ /* 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 . */ #pragma once #include #include #include #include #include "MuxElements.h" /* Must be called once per ETI frame to update the time */ void update_dab_time(void); void get_dab_time(time_t *time, uint32_t *millis); /* Convert a date and time into the modified Julian date * used in FIG 0/10 * * Year is four digit format. * Months are Jan=1, Feb=2, etc. * First day of the month is 1, as usual. * * Returns corresponding MJD */ uint32_t gregorian2mjd(int year, int month, int day); /* Shows the introductory header on program start */ void header_message(); /* The usage information refers to the command-line * ensemble definition, and explains how to create * an ensemble without using a configuration file */ void printUsage(char *name, FILE* out = stderr); /* This usage information explains how to run the program * with a configuration file */ void printUsageConfigfile(char *name, FILE* out = stderr); /* The following four utility functions display a * description of all outputs, services, components * resp. subchannels*/ void printOutputs(const std::vector >& outputs); /* Print information about the whole ensemble */ void printEnsemble(const std::shared_ptr& ensemble); void printSubchannels(const vec_sp_subchannel& subchannels); unsigned long hexparse(const std::string& input); bool stringEndsWith(std::string const &fullString, std::string const &ending); Opendigitalradio-ODR-DabMux-29c710c/src/zmq2farsync/000077500000000000000000000000001476627344300222105ustar00rootroot00000000000000Opendigitalradio-ODR-DabMux-29c710c/src/zmq2farsync/README.md000066400000000000000000000002761476627344300234740ustar00rootroot00000000000000Drive a Farsync card from a ZMQ ETI stream ========================================== This *zmq2farsync* tool can receive a ZMQ ETI stream from ODR-DabMux and drive a Farsync card with it. Opendigitalradio-ODR-DabMux-29c710c/src/zmq2farsync/zmq2farsync.cpp000066400000000000000000000134731476627344300252030ustar00rootroot00000000000000/* Copyright (C) 2002, 2003, 2004, 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 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 "dabOutput/dabOutput.h" #include "Log.h" #include "zmq.hpp" #include #include #include #include constexpr size_t MAX_ERROR_COUNT = 10; constexpr size_t MAX_NUM_RESETS = 180; constexpr long ZMQ_TIMEOUT_MS = 1000; static void usage() { using namespace std; cerr << "Usage:" << endl; cerr << "odr-zmq2farsync " << endl << endl; cerr << "Where" << endl; cerr << " is a ZMQ URL that points to a ODR-DabMux ZMQ output." << endl; cerr << " is the device information for the FarSync card." << endl << endl; cerr << " The syntax is the same as for ODR-DabMux" << endl << endl; cerr << "The input socket will be reset if no data is received for " << (int)(MAX_ERROR_COUNT * ZMQ_TIMEOUT_MS / 1000.0) << " seconds." << endl; cerr << "After " << MAX_NUM_RESETS << " consecutive resets, the process will quit." << endl; cerr << "It is best practice to run this tool under a process supervisor that will restart it automatically." << endl; } int main(int argc, char **argv) { // 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; } etiLog.level(info) << "ZMQ2FarSync ETI converter from " << PACKAGE_NAME << " " << #if defined(GITVERSION) GITVERSION << #else PACKAGE_VERSION << #endif " starting up"; if (argc != 3) { usage(); return 1; } const char* source_url = argv[1]; const char* destination_device = argv[2]; etiLog.level(info) << "Opening FarSync card: " << destination_device; DabOutputRaw output; if (output.Open(destination_device) == -1) { etiLog.level(error) << "Unable to open output "; return -1; } etiLog.level(info) << "Opening ZMQ input: " << source_url; zmq::context_t zmq_ctx(1); size_t frame_count = 0; size_t loop_counter = 0; size_t num_consecutive_resets = 0; while (num_consecutive_resets < MAX_NUM_RESETS) { zmq::socket_t zmq_sock(zmq_ctx, ZMQ_SUB); zmq_sock.connect(source_url); zmq_sock.setsockopt(ZMQ_SUBSCRIBE, NULL, 0); // subscribe to all messages size_t error_count = 0; while (error_count < MAX_ERROR_COUNT) { zmq::message_t incoming; zmq::pollitem_t items[1]; items[0].socket = zmq_sock; items[0].events = ZMQ_POLLIN; const int num_events = zmq::poll(items, 1, ZMQ_TIMEOUT_MS); if (num_events == 0) { // timeout error_count++; } else { num_consecutive_resets = 0; // Event received: recv will not block zmq_sock.recv(&incoming); zmq_dab_message_t* dab_msg = (zmq_dab_message_t*)incoming.data(); if (dab_msg->version != 1) { etiLog.level(error) << "ZeroMQ wrong packet version " << dab_msg->version; error_count++; } int offset = sizeof(dab_msg->version) + NUM_FRAMES_PER_ZMQ_MESSAGE * sizeof(*dab_msg->buflen); for (int i = 0; i < NUM_FRAMES_PER_ZMQ_MESSAGE; i++) { if (dab_msg->buflen[i] <= 0 or dab_msg->buflen[i] > 6144) { etiLog.level(error) << "ZeroMQ buffer " << i << " has invalid length " << dab_msg->buflen[i]; error_count++; } else { std::vector buf(6144, 0x55); const int framesize = dab_msg->buflen[i]; memcpy(&buf.front(), ((uint8_t*)incoming.data()) + offset, framesize); offset += framesize; if (output.Write(&buf.front(), buf.size()) == -1) { etiLog.level(error) << "Cannot write to output!"; error_count++; } frame_count++; } } loop_counter++; if (loop_counter > 250) { etiLog.level(info) << "Transmitted " << frame_count << " ETI frames"; loop_counter = 0; } } } num_consecutive_resets++; zmq_sock.close(); std::this_thread::sleep_for(std::chrono::seconds(1)); etiLog.level(info) << "ZMQ input (" << source_url << ") timeout after " << num_consecutive_resets << " consecutive resets."; } return 0; } Opendigitalradio-ODR-DabMux-29c710c/src/zmqinput-keygen.c000066400000000000000000000056541476627344300232560ustar00rootroot00000000000000/* Create a key file for the ZMQinput * and save to file. * * Copyright (c) 2014 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. */ #include #include #include #include #include #include #include #include #include int main(int argc, char** argv) { if (argc == 1) { fprintf(stderr, "Generate a random key for dabInputZMQ and save it to two files.\n\n"); fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } const char* keyname = argv[1]; if (strlen(keyname) > 2048) { fprintf(stderr, "name too long\n"); return 1; } char pubkeyfile[strlen(keyname) + 10]; char seckeyfile[strlen(keyname) + 10]; sprintf(pubkeyfile, "%s.pub", keyname); sprintf(seckeyfile, "%s.sec", keyname); char public_key [41]; char secret_key [41]; int rc = zmq_curve_keypair(public_key, secret_key); if (rc != 0) { fprintf(stderr, "key generation failed\n"); return 1; } int fdpub = creat(pubkeyfile, S_IRUSR | S_IWUSR); if (fdpub < 0) { perror("File creation failed"); return 1; } int fdsec = creat(seckeyfile, S_IRUSR | S_IWUSR); if (fdsec < 0) { perror("File creation failed"); return 1; } int r = write(fdpub, public_key, 41); int ret = 0; if (r < 0) { perror("write failed"); ret = 1; } else if (r != 41) { fprintf(stderr, "Not enough key data written to file\n"); ret = 1; } close(fdpub); if (ret == 0) { r = write(fdsec, secret_key, 41); if (r < 0) { perror("write failed"); ret = 1; } else if (r != 41) { fprintf(stderr, "Not enough key data written to file\n"); ret = 1; } } close(fdsec); return ret; }