pax_global_header00006660000000000000000000000064150113650240014507gustar00rootroot0000000000000052 comment=c204998c92931285efdf6670c81cefd199298895 audiowmark-0.6.5/000077500000000000000000000000001501136502400136625ustar00rootroot00000000000000audiowmark-0.6.5/.dockerignore000066400000000000000000000000441501136502400163340ustar00rootroot00000000000000.git .gitignore .dockerignore .idea audiowmark-0.6.5/.github/000077500000000000000000000000001501136502400152225ustar00rootroot00000000000000audiowmark-0.6.5/.github/workflows/000077500000000000000000000000001501136502400172575ustar00rootroot00000000000000audiowmark-0.6.5/.github/workflows/testing.yml000066400000000000000000000007311501136502400214600ustar00rootroot00000000000000# https://rhysd.github.io/actionlint/ name: Testing on: [push] jobs: linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Test Linux Build run: misc/dbuild.sh macos: runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Test macOS Build run: misc/macbuild.sh macos-13: runs-on: macos-13 steps: - uses: actions/checkout@v3 - name: Test macOS Build run: misc/macbuild.sh audiowmark-0.6.5/.gitignore000066400000000000000000000002041501136502400156460ustar00rootroot00000000000000autom4te.cache/ build-aux/ aclocal.m4 configure config.h config.h.in config.log config.status Makefile Makefile.in libtool stamp-h1 audiowmark-0.6.5/COPYING000066400000000000000000001045151501136502400147230ustar00rootroot00000000000000 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 . audiowmark-0.6.5/Dockerfile000066400000000000000000000005671501136502400156640ustar00rootroot00000000000000FROM gcc:latest RUN apt-get update && apt-get install -y \ build-essential automake autoconf libtool autoconf-archive gettext \ libfftw3-dev libsndfile1-dev libgcrypt20-dev libzita-resampler-dev \ libmpg123-dev ADD . /audiowmark WORKDIR /audiowmark RUN ./autogen.sh RUN make RUN make install VOLUME ["/data"] WORKDIR /data ENTRYPOINT ["/usr/local/bin/audiowmark"] audiowmark-0.6.5/Makefile.am000066400000000000000000000004241501136502400157160ustar00rootroot00000000000000AUTOMAKE_OPTIONS = dist-zstd no-dist-gzip # build & test everything (HLS included) during make distcheck AM_DISTCHECK_CONFIGURE_FLAGS = --with-ffmpeg SUBDIRS = src tests ACLOCAL_AMFLAGS = -I m4 if COND_WITH_DOCS SUBDIRS += docs endif EXTRA_DIST = README.adoc Dockerfile audiowmark-0.6.5/NEWS000066400000000000000000000114031501136502400143600ustar00rootroot00000000000000Overview of Changes in audiowmark-0.6.5: * improve robustness for low quality material - robust sync score peak selection using local mean - use lower sync threshold (introduce --sync-threshold option) - always decode n best matches (introduce --n-best option) - distance based AB pattern merging - distance based ALL pattern merging - change output order: print most relevant matches first - deprecate short payload * fix speed detection result output for default key * ship more m4 macros to fix automake on some target systems Overview of Changes in audiowmark-0.6.4: * reduce memory usage: - for speed detection, especially when using multiple keys - for resampling of input files with sample rate != 44100 - split long input files into smaller chunks (get --chunk-size option) * optimize performance for speed detection / normal sync * improve detection for libgcrypt, use pkg-config if available (#72) * fix problems in make check (#72) Overview of Changes in audiowmark-0.6.3: * update HLS support to build with ffmpeg-7 API (#68) * preserve wav subformat (#64) - write float/double wav output for wav float/double input - write signed 16, 24 and 32 bit wav output for 16, 24, 32 bit wav input - added test that wav subformat format is preserved (make check) * improved RawConverter: - support float/double input/output (very fast on little endian systems) - fix bug for unsigned integer raw input/output (16, 24 and 32 bit) - better tests (make check) - faster 16/32 bit conversion on little endian systems * add format wav-pipe for very long input/output streams on stdin/stdout * performance optimizations for SFOutputStream * add documentation for Windows/Cygwin builds (#45) Overview of Changes in audiowmark-0.6.2: * improved robustness of the watermark detection a lot for many cases * improved handling if more than one key needs to be used for detection - support using --key multiple times for audiowmark get - support naming watermark keys (key name defaults to filename) - extend JSON and regular output to report key name for matches * merge architecture documentation for developers written by Tim Janik (#49) * support RF64 output (--output-format rf64) for huge wav files (#30, #2) * use hann window function to improve robustness/quality for some files (#48) * fix building against zita-resampler installed in non-standard location (#39) * replace sprintf with string_printf: sprintf is deprecated on macOS (#33) * fix build errors related to PRNG on new clang compilers (#29) * fix problems in videowmark due to command line option ordering (#23) Overview of Changes in audiowmark-0.6.1: * improve speed detection/correction - performance optimizations to make --detect-speed faster - improve accuracy of speed detection - make it work properly with short payload - add second, slower / more accurate algorithm (--detect-speed-patient) * fix segfaults during hls-prepare (#11) * read all input if a process provides audio on stdin to avoid SIGPIPE (#19) * improve infrastructure for testing audiowmark - run some scripts for 'make check' to ensure everything works correctly - add CI which builds/tests audiowmark automatically using github actions - support various sanitizer builds / STL C++ debug builds - fix some issues triggered by sanitizers * add --strict option: provide strict and more permissive mode - "input frames != output frames" is only an error if --strict is used - enforce payload size if --strict is used * improve command line parsing error messages * documentation updates * minor fixes Overview of Changes in audiowmark-0.6.0: * implement speed detection/correction (--detect-speed) * Add '--json' CLI option for machine readable results. Overview of Changes in audiowmark-0.5.0: * support HTTP Live Streaming for audio/video streaming * fix floating point wav input * improve command line option handling (ArgParser) * support seeking on internal watermark state Overview of Changes in audiowmark-0.4.2: * compile fixes for g++-9 and clang++-10 * add experimental support for short payload Overview of Changes in audiowmark-0.4.1: * initial public release Overview of Changes in audiowmark-0.4.0: * add initial video watermarking support (videowmark) Overview of Changes in audiowmark-0.3.0: * replace padding at start with a partial B block * add algorithm for decoding the watermark from short clips Overview of Changes in audiowmark-0.2.1: * add limiter to avoid clipping during watermark generation Overview of Changes in audiowmark-0.2.0: * support input/output streams * support raw streams * some performance optimizations * unified logging and --quiet option * improved mp3 detection to avoid false positives * split up watermarking source (wmadd/wmget/wmcommon) Overview of Changes in audiowmark-0.1.0: * initial release audiowmark-0.6.5/README.adoc000066400000000000000000000730251501136502400154560ustar00rootroot00000000000000= audiowmark - Audio Watermarking == Description `audiowmark` is an Open Source (GPL) solution for audio watermarking. A sound file is read by the software, and a 128-bit message is stored in a watermark in the output sound file. For human listeners, the files typically sound the same. However, the 128-bit message can be retrieved from the output sound file. Our tests show, that even if the file is converted to mp3 or ogg (with bitrate 128 kbit/s or higher), the watermark usually can be retrieved without problems. The process of retrieving the message does not need the original audio file (blind decoding). Internally, audiowmark is using the patchwork algorithm to hide the data in the spectrum of the audio file. The signal is split into 1024 sample frames. For each frame, some pseoudo-randomly selected amplitudes of the frequency bands of a 1024-value FFTs are increased or decreased slightly, which can be detected later. The algorithm used here is inspired by Martin Steinebach: Digitale Wasserzeichen für Audiodaten. Darmstadt University of Technology 2004, ISBN 3-8322-2507-2 If you are interested in the details how `audiowmark` works, there is a separate https://uplex.de/audiowmark/audiowmark-developer.pdf[*documentation for developers*]. == Open Source License `audiowmark` is *open source* software available under the *GPLv3 or later* license. Copyright (C) 2018-2020 Stefan Westerfeld 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 . == Adding a Watermark To add a watermark to the soundfile in.wav with a 128-bit message (which is specified as hex-string): [subs=+quotes] .... *$ audiowmark add in.wav out.wav 0123456789abcdef0011223344556677* Input: in.wav Output: out.wav Message: 0123456789abcdef0011223344556677 Strength: 10 Time: 3:59 Sample Rate: 48000 Channels: 2 Data Blocks: 4 .... If you want to use `audiowmark` in any serious application, please read the section <> on how to generate the 128-bit message. Typically these bits should be a *hash* or *HMAC* of some sort. The most important options for adding a watermark are: --key :: Use watermarking key from file (see <>). --strength :: Set the watermarking strength (see <>). == Retrieving a Watermark To get the 128-bit message from the watermarked file, use: [subs=+quotes] .... *$ audiowmark get out.wav* pattern 0:05 0123456789abcdef0011223344556677 1.358 0.059 A pattern 0:57 0123456789abcdef0011223344556677 1.426 0.050 B pattern 0:57 0123456789abcdef0011223344556677 1.392 0.055 AB pattern 1:49 0123456789abcdef0011223344556677 1.453 0.074 A pattern 2:40 0123456789abcdef0011223344556677 1.359 0.080 B pattern 2:40 0123456789abcdef0011223344556677 1.406 0.077 AB pattern all 0123456789abcdef0011223344556677 1.399 0.035 pattern 2:58 c6141f3d25720eb0dbd0e38ee2355ff4 0.185 0.382 B pattern 2:14 df659ac435f0569c67cdac1fb2e36cd8 0.178 0.391 B pattern 1:58 d7d7ed21e9d6da0a4ffdd5e55ffdb623 0.176 0.389 A pattern 2:59 1c13f1532eeb6c9aa719253736849d6e 0.167 0.389 A .... The output of `audiowmark get` is designed to be machine readable. Each line that starts with `pattern` contains one decoded message. The fields are seperated by one or more space characters. The first field is a *timestamp* indicating the position of the data block. The second field is the *decoded message*. The results are sorted by relevance, the most relevant matches are shown first. The software was designed under the assumption that the message is a *hash* or *HMAC* of some sort. Before you start using `audiowmark` in any serious application, please read the section <>. You - the user - should be able to decide whether a message is correct or not. To do this, on watermarking song files, you could *create a database entry* for each message you embedded in a watermark. During retrieval, you should perform a *database lookup* for each pattern `audiowmark get` outputs. If the message is not found, then you should assume that a decoding error occurred. In our example the last results did not match the correct message and need to be ignored. The third field is the *sync score* (higher is better). The synchronization algorithm tries to find valid data blocks in the audio file, that become candidates for decoding. The fourth field is the *decoding error* (lower is better). During message decoding, we use convolutional codes for error correction, to make the watermarking more robust. The fifth field is the *block type*. There are two types of data blocks, A blocks and B blocks. A single data block can be decoded alone, as it contains a complete message. However, if during watermark detection an A block followed by a B block was found, and the two blocks are at the expected distance, these two can be decoded together (then this field will be AB), resulting in even higher error correction capacity than one block alone would have. To improve the error correction capacity even further, the `all` pattern merges multiple data blocks that are found at the expected distance. The combined decoded message will often be the most reliable result (meaning that even if all other patterns were incorrect, this could still be right). The most important options for getting a watermark are: --key :: Use watermarking key from file (see <>). --detect-speed:: --detect-speed-patient:: Detect and correct replay speed difference (see <>). --json :: Write results to in machine readable JSON format. --chunk-size :: Set chunk size for memory/speed tradeoff. Larger chunk sizes result in faster detection but higher memory usage. Default: 30 minutes. --sync-threshold :: Set threshold for minimum sync quality. Patterns with sync scores higher than this threshold are considered relevant and are decoded. The default (0.35) is usually fine. --n-best :: In addition to all patterns that are considered relevant due to their sync score, this parameter ensures that at least matches are decoded, even if their sync score is low. This can help in situations where the quality of the input material is low, so that no patterns have a high enough sync score to be considered relevant. On the other hand this makes watermark detection slower, especially when many keys are used, so if performance is more important than accuracy, using `--n-best 0` can be used to disable n-best decoding. Default: at least decode 8 matches. [[key]] == Watermark Key Since the software is Open Source, a watermarking key should be used to ensure that the message bits cannot be retrieved by somebody else (which would also allow removing the watermark without loss of quality). The watermark key controls all pseudo-random parameters of the algorithm. This means that it determines which frequency bands are increased or decreased to store a 0 bit or a 1 bit. Without the key, it is impossible to decode the message bits from the audio file alone. Our watermarking key is a 128-bit AES key. A key can be generated using audiowmark gen-key test.key and can be used for the add/get commands as follows: audiowmark add --key test.key in.wav out.wav 0123456789abcdef0011223344556677 audiowmark get --key test.key out.wav Keys can be named using the `gen-key --name` option, and the key name will be reported for each match: audiowmark gen-key oct23.key --name "October 2023" Finally, it is possible to use the `--key` option more than once for watermark detection. In this case, all keys that are specified will be tried. This is useful if you change keys on a regular basis, and passing multiple keys is more efficient than performing watermark detection multiple times with one key. audiowmark get --key oct23.key --key nov23.key --key dec23.key out.wav [[strength]] == Watermark Strength The watermark strength parameter affects how much the watermarking algorithm modifies the input signal. A stronger watermark is more audible, but also more robust against modifications. The default strength is 10. A watermark with that strength is recoverable after mp3/ogg encoding with 128kbit/s or higher. In our informal listening tests, this setting also has a very good subjective quality. A higher strength (for instance 15) would be helpful for instance if robustness against multiple conversions or conversions to low bit rates (i.e. 64kbit/s) is desired. A lower strength (for instance 6) makes the watermark less audible, but also less robust. Strengths below 5 are not recommended. The strength has to be provided for `audiowmark add`. Fractional strengths (like 7.5) are possible. audiowmark add --strength 15 in.wav out.wav 0123456789abcdef0011223344556677 [[rec-payload]] == Recommendations for the Watermarking Payload Although `audiowmark` does not specify what the 128-bit message stored in the watermark should be, it was designed under the assumption that the message should be a *hash* or *HMAC* of some sort. Lets look at a typical use case. We have a song called *Dreams* by an artist called *Alice*. A user called *John Smith* downloads a watermarked copy. Later, we find this file somewhere on the internet. Typically we want to answer the questions: * is this one of the files we previously watermarked? * what song/artist is this? * which user shared it? _When the user downloads a watermarked copy_, we construct a string that contains all information we need to answer our questions, for example like this: Artist:Alice|Title:Dreams|User:John Smith To obtain the 128-bit message, we can hash this string, for instance by using the first 128 bits of a SHA-256 hash like this: $ STRING='Artist:Alice|Title:Dreams|User:John Smith' $ MSG=`echo -n "$STRING" | sha256sum | head -c 32` $ echo $MSG ecd057f0d1fbb25d6430b338b5d72eb2 This 128-bit message can be used as watermark: $ audiowmark add --key my.key song.wav song.wm.wav $MSG At this point, we should also *create a database entry* consisting of the hash value `$MSG` and the corresponding string `$STRING`. The shell commands for creating the hash are listed here to provide a simplified example. Fields (like the song title) can contain the characters `'` and `|`, so these cases need to be dealt with. _If we find a watermarked copy of the song on the net_, the first step is to detect the watermark message using $ audiowmark get --key my.key song.wm.wav pattern 0:05 ecd057f0d1fbb25d6430b338b5d72eb2 1.377 0.068 A pattern 0:57 ecd057f0d1fbb25d6430b338b5d72eb2 1.392 0.109 B [...] The second step is to perform a *database lookup* for each result returned by `audiowmark`. If we find a matching entry in our database, this is one of the files we previously watermarked. As a last step, we can use the string stored in the database, which contains the song/artist and the user that shared it. _The advantages of using a hash as message are:_ 1. Although `audiowmark` sometimes produces *false positives*, this doesn't matter, because it is extremely unlikely that a false positive will match an existing database entry. 2. Even if a few *bit errors* occur, it is extremely unlikely that a song watermarked for user A will be attributed to user B, simply because all hash bits depend on the user. So this is a much better payload than storing a user ID, artist ID and song ID in the message bits directly. 3. It is *easy to extend*, because we can add any fields we need to the hash string. For instance, if we want to store the name of the album, we can simply add it to the string. 4. If the hash matches exactly, it is really *hard to deny* that it was this user who shared the song. How else could all 128 bits of the hash match the message bits decoded by `audiowmark`? [[speed]] == Speed Detection If a watermarked audio signal is played back a little faster or slower than the original speed, watermark detection will fail. This could happen by accident if the digital watermark was converted to an analog signal and back and the original speed was not (exactly) preserved. It could also be done intentionally as an attack to avoid the watermark from being detected. In order to be able to find the watermark in these cases, `audiowmark` can try to figure out the speed difference to the original audio signal and correct the replay speed before detecting the watermark. The search range for the replay speed is approximately *[0.8..1.25]*. Example: add a watermark to `in.wav` and increase the replay speed by 5% using `sox`. [subs=+quotes] .... *$ audiowmark add in.wav out.wav 0123456789abcdef0011223344556677* [...] *$ sox out.wav out1.wav speed 1.05* .... Without speed detection, we get no results. With speed detection the speed difference is detected and corrected so we get results. [subs=+quotes] .... *$ audiowmark get out1.wav* *$ audiowmark get out1.wav --detect-speed* speed 1.049966 pattern 0:05 0123456789abcdef0011223344556677 1.209 0.147 A-SPEED pattern 0:57 0123456789abcdef0011223344556677 1.301 0.143 B-SPEED pattern 0:57 0123456789abcdef0011223344556677 1.255 0.145 AB-SPEED pattern 1:49 0123456789abcdef0011223344556677 1.380 0.173 A-SPEED pattern all 0123456789abcdef0011223344556677 1.297 0.130 SPEED .... The speed detection algorithm is not enabled by default because it is relatively slow (total cpu time required) and needs a lot of memory. However the search is automatically run in parallel using many threads on systems with many cpu cores. So on good hardware it makes sense to always enable this option to be robust to replay speed attacks. There are two versions of the speed detection algorithm, `--detect-speed` and `--detect-speed-patient`. The difference is that the patient version takes more cpu time to detect the speed, but produces more accurate results. == Short Payload (deprecated) **The support for short payload is now deprecated and will probably be removed in some newer version**, mainly because with a 128-bit HMAC based message, you can really be 100% certain that there are no false positives, so that if you see a certain payload, you can be sure that it was the watermark that was originally added. For short payload, there is a (very very small) probablity for generating a false positive, and there is no way of detecting it if this has happened. By default, the watermark will store a 128-bit message. In this mode, we recommend using a 128bit hash (or HMAC) as payload. No error checking is performed, the user needs to test patterns that the watermarker decodes to ensure that they really are one of the expected patterns, not a decoding error. As an alternative, an experimental short payload option is available, for very short payloads (12, 16 or 20 bits). It is enabled using the `--short ` command line option, for instance for 16 bits: audiowmark add --short 16 in.wav out.wav abcd audiowmark get --short 16 out.wav Internally, a larger set of bits is sent to ensure that decoded short patterns are really valid, so in this mode, error checking is performed after decoding, and only valid patterns are reported. Besides error checking, the advantage of a short payload is that fewer bits need to be sent, so decoding will more likely to be successful on shorter clips. == Video Files For video files, `videowmark` can be used to add a watermark to the audio track of video files. To add a watermark, use [subs=+quotes] .... *$ videowmark add in.avi out.avi 0123456789abcdef0011223344556677* Audio Codec: -c:a mp3 -ab 128000 Input: in.avi Output: out.avi Message: 0123456789abcdef0011223344556677 Strength: 10 Time: 3:53 Sample Rate: 44100 Channels: 2 Data Blocks: 4 .... To detect a watermark, use [subs=+quotes] .... *$ videowmark get out.avi* pattern 0:05 0123456789abcdef0011223344556677 1.294 0.142 A pattern 0:57 0123456789abcdef0011223344556677 1.191 0.144 B pattern 0:57 0123456789abcdef0011223344556677 1.242 0.145 AB pattern 1:49 0123456789abcdef0011223344556677 1.215 0.120 A pattern 2:40 0123456789abcdef0011223344556677 1.079 0.128 B pattern 2:40 0123456789abcdef0011223344556677 1.147 0.126 AB pattern all 0123456789abcdef0011223344556677 1.195 0.104 .... The key and strength can be set using the command line options --key :: Use watermarking key from file (see <>). --strength :: Set the watermarking strength (see <>). Videos can be watermarked on-the-fly using <>. == Output as Stream Usually, an input file is read, watermarked and an output file is written. This means that it takes some time before the watermarked file can be used. An alternative is to output the watermarked file as stream to stdout. One use case is sending the watermarked file to a user via network while the watermarker is still working on the rest of the file. Here is an example how to watermark a wav file to stdout: audiowmark add in.wav - 0123456789abcdef0011223344556677 | play - In this case the file in.wav is read, watermarked, and the output is sent to stdout. The "play -" can start playing the watermarked stream while the rest of the file is being watermarked. If - is used as output, the output is a valid .wav file, so the programs running after `audiowmark` will be able to determine sample rate, number of channels, bit depth, encoding and so on from the wav header. Note that all input formats supported by audiowmark can be used in this way, for instance flac/mp3: audiowmark add in.flac - 0123456789abcdef0011223344556677 | play - audiowmark add in.mp3 - 0123456789abcdef0011223344556677 | play - == Input from Stream Similar to the output, the `audiowmark` input can be a stream. In this case, the input must be a valid .wav file. The watermarker will be able to start watermarking the input stream before all data is available. An example would be: cat in.wav | audiowmark add - out.wav 0123456789abcdef0011223344556677 It is possible to do both, input from stream and output as stream. cat in.wav | audiowmark add - - 0123456789abcdef0011223344556677 | play - Streaming input is also supported for watermark detection. cat in.wav | audiowmark get - == Wav Pipe Format In some cases, the length of the streaming input is not known by the program that produces the stream. For instance consider a mp3 that is being decoded by madplay. cat in.mp3 | madplay -o wave:- - | audiowmark add - out.wav f0 Since madplay doesn't know the length of the output when it starts decoding the mp3, the best it can do is to fill the wav header with a big number. And indeed, audiowmark will watermark the stream, but also print a warning like this: audiowmark: warning: unexpected EOF; input frames (1073741823) != output frames (8316288) This may sound harmless, but for very long input streams, this will also truncate the audio input after this length. If you already know that you need to input a wav file from a pipe (without correct length in the header) and simply want to watermark all of it, it is better to use the `wav-pipe` format: cat in.mp3 | madplay -o wave:- - | audiowmark add --input-format wav-pipe --output-format rf64 - out.wav f0 This will not print a warning, and it also works correctly for long input streams. Note that using `rf64` as output format is necessary for huge output files (larger than 4G). Similar to pipe input, audiowmark can write a wav header with a huge number (in cases where it does not know the length in advance) if the output format is set to `wav-pipe`. cat in.mp3 | madplay -o wave:- - | audiowmark add --input-format wav-pipe --output-format wav-pipe - - f0 | lame - > out.mp3 If you need both, `wav-pipe` input and output, a shorter way to write it is using `--format wav-pipe`, like this: cat in.mp3 | madplay -o wave:- - | audiowmark add --format wav-pipe - - f0 | lame - > out.mp3 == Raw Streams So far, all streams described here are essentially wav streams, which means that the wav header allows `audiowmark` to determine sample rate, number of channels, bit depth, encoding and so forth from the stream itself, and the a wav header is written for the program after `audiowmark`, so that this can figure out the parameters of the stream. If the program before or after `audiowmark` doesn't support wav headers, raw streams can be used instead. The idea is to set all information that is needed like sample rate, number of channels,... manually. Then, headerless data can be processed from stdin and/or sent to stdout. --input-format raw:: --output-format raw:: --format raw:: These can be used to set the input format or output format to raw. The last version sets both, input and output format to raw. --raw-rate :: This should be used to set the sample rate. The input sample rate and the output sample rate will always be the same (no resampling is done by the watermarker). There is no default for the sampling rate, so this parameter must always be specified for raw streams. --raw-input-bits :: --raw-output-bits :: --raw-bits :: The options can be used to set the input number of bits, the output number of bits or both. The number of bits can be `16`, `24` or `32`. The default number of bits is `16`. --raw-input-endian :: --raw-output-endian :: --raw-endian :: These options can be used to set the input/output endianness or both. The parameter can either be `little` or `big`. The default endianness is `little`. --raw-input-encoding :: --raw-output-encoding :: --raw-encoding :: These options can be used to set the input/output encoding or both. The parameter can be `signed`, `unsigned`, `float` or `double`. The default encoding is `signed`. Using `float` (or `double`) encoding automatically sets the number of bits to `32` (or `64`). --raw-channels :: This can be used to set the number of channels. Note that the number of input channels and the number of output channels must always be the same. The watermarker has been designed and tested for stereo files, so the number of channels should really be `2`. This is also the default. == Other Command Line Options --output-format rf64:: Regular wav files are limited to 4GB in size. By using this option, `audiowmark` will write RF64 wave files, which do not have this size limit. This is not the default because not all programs might be able to read RF64 wave files. --q, --quiet:: Disable all information messages generated by `audiomark`. --strict:: This option will enable strict error checking, which may in some situations make `audiowmark` return an error, where it could continue. [[hls]] == HTTP Live Streaming === Introduction for HLS HTTP Live Streaming (HLS) is a protocol to deliver audio or video streams via HTTP. One example for using HLS in practice would be: a user watches a video in a web browser with a player like `hls.js`. The user is free to play/pause/seek the video as he wants. `audiowmark` can watermark the audio content while it is being transmitted to the user. HLS splits the contents of each stream into small segments. For the watermarker this means that if the user seeks to a position far ahead in the stream, the server needs to start sending segments from where the new play position is, but everything in between can be ignored. Another important property of HLS is that it allows separate segments for the video and audio stream of a video. Since we watermark only the audio track of a video, the video segments can be sent as they are (and different users can get the same video segments). What is watermarked are the audio segments only, so here instead of sending the original audio segments to the user, the audio segments are watermarked individually for each user, and then transmitted. Everything necessary to watermark HLS audio segments is available within `audiowmark`. The server side support which is necessary to send the right watermarked segment to the right user is not included. [[hls-requirements]] === HLS Requirements HLS support requires some headers/libraries from ffmpeg: * libavcodec * libavformat * libavutil * libswresample To enable these as dependencies and build `audiowmark` with HLS support, use the `--with-ffmpeg` configure option: [subs=+quotes] .... *$ ./configure --with-ffmpeg* .... In addition to the libraries, `audiowmark` also uses the two command line programs from ffmpeg, so they need to be installed: * ffmpeg * ffprobe === Preparing HLS segments The first step for preparing content for streaming with HLS would be splitting a video into segments. For this documentation, we use a very simple example using ffmpeg. No matter what the original codec was, at this point we force transcoding to AAC with our target bit rate, because during delivery the stream will be in AAC format. [subs=+quotes] .... *$ ffmpeg -i video.mp4 -f hls -master_pl_name replay.m3u8 -c:a aac -ab 192k \ -var_stream_map "a:0,agroup:aud v:0,agroup:aud" \ -hls_playlist_type vod -hls_list_size 0 -hls_time 10 vs%v/out.m3u8* .... This splits the `video.mp4` file into an audio stream of segments in the `vs0` directory and a video stream of segments in the `vs1` directory. Each segment is approximately 10 seconds long, and a master playlist is written to `replay.m3u8`. Now we can add the relevant audio context to each audio ts segment. This is necessary so that when the segment is watermarked in order to be transmitted to the user, `audiowmark` will have enough context available before and after the segment to create a watermark which sounds correct over segment boundaries. [subs=+quotes] .... *$ audiowmark hls-prepare vs0 vs0prep out.m3u8 video.mp4* AAC Bitrate: 195641 (detected) Segments: 18 Time: 2:53 .... This steps reads the audio playlist `vs0/out.m3u8` and writes all segments contained in this audio playlist to a new directory `vs0prep` which contains the audio segments prepared for watermarking. The last argument in this command line is `video.mp4` again. All audio that is watermarked is taken from this audio master. It could also be supplied in `wav` format. This makes a difference if you use lossy compression as target format (for instance AAC), but your original video has an audio stream with higher quality (i.e. lossless). === Watermarking HLS segments So with all preparations made, what would the server have to do to send a watermarked version of the 6th audio segment `vs0prep/out5.ts`? [subs=+quotes] .... *$ audiowmark hls-add vs0prep/out5.ts send5.ts 0123456789abcdef0011223344556677* Message: 0123456789abcdef0011223344556677 Strength: 10 Time: 0:15 Sample Rate: 44100 Channels: 2 Data Blocks: 0 AAC Bitrate: 195641 .... So instead of sending out5.ts (which has no watermark) to the user, we would send send5.ts, which is watermarked. In a real-world use case, it is likely that the server would supply the input segment on stdin and send the output segment as written to stdout, like this [subs=+quotes] .... *$ [...] | audiowmark hls-add - - 0123456789abcdef0011223344556677 | [...]* [...] .... The usual parameters are supported in `audiowmark hls-add`, like --key :: Use watermarking key from file (see <>). --strength :: Set the watermarking strength (see <>). The AAC bitrate for the output segment can be set using: --bit-rate :: Set the AAC bit-rate for the generated watermarked segment. The rules for the AAC bit-rate of the newly encoded watermarked segment are: * if the --bit-rate option is used during `hls-add`, this bit-rate will be used * otherwise, if the `--bit-rate` option is used during `hls-prepare`, this bit-rate will be used * otherwise, the bit-rate of the input material is detected during `hls-prepare` == Compiling from Source Stable releases are available from http://uplex.de/audiowmark The steps to compile the source code are: ./configure make make install If you build from git (which doesn't include `configure`), the first step is `./autogen.sh`. In this case, you need to ensure that (besides the dependencies listed below) the `autoconf-archive` package is installed. == Compiling from Source on Windows/Cygwin Windows is not an officially supported platform. However, if you want to build audiowmark (and videowmark) from source on windows, one way to do so is to use Cygwin. Andreas Strohmeier provided https://raw.githubusercontent.com/swesterfeld/audiowmark/master/docs/win-x64-build-guide.txt[*build instructions for Cygwin*]. == Dependencies If you compile from source, `audiowmark` needs the following libraries: * libfftw3 * libsndfile * libgcrypt * libzita-resampler * libmpg123 If you want to build with HTTP Live Streaming support, see also <>. == Building fftw `audiowmark` needs the single prevision variant of fftw3. If you are building fftw3 from source, use the `--enable-float` configure parameter to build it, e.g.:: cd ${FFTW3_SOURCE} ./configure --enable-float --enable-sse && \ make && \ sudo make install or, when building from git cd ${FFTW3_GIT} ./bootstrap.sh --enable-shared --enable-sse --enable-float && \ make && \ sudo make install == Docker Build You should be able to execute `audiowmark` via Docker. Example that outputs the usage message: docker build -t audiowmark . docker run -v :/data --rm -i audiowmark -h audiowmark-0.6.5/autogen.sh000077500000000000000000000013731501136502400156670ustar00rootroot00000000000000#!/bin/bash if automake --version >/dev/null 2>&1; then : else echo "You need to have automake installed to build this package" DIE=1 fi if autoconf --version >/dev/null 2>&1; then : else echo "You need to have autoconf installed to build this package" DIE=1 fi if libtoolize --version >/dev/null 2>&1; then : elif glibtoolize --version >/dev/null 2>&1; then # macOS : else echo "You need to have libtool installed to build this package" DIE=1 fi if pkg-config --version >/dev/null 2>&1; then : else echo "You need to have pkg-config installed to build this package" DIE=1 fi # bail out as scheduled test "0$DIE" -gt 0 && exit 1 echo "Running: autoreconf -i && ./configure $@" autoreconf -f -i -Wno-portability && ./configure "$@" audiowmark-0.6.5/build-aux/000077500000000000000000000000001501136502400155545ustar00rootroot00000000000000audiowmark-0.6.5/build-aux/config.rpath000077500000000000000000000442161501136502400200730ustar00rootroot00000000000000#! /bin/sh # Output a system dependent set of variables, describing how to set the # run time search path of shared libraries in an executable. # # Copyright 1996-2020 Free Software Foundation, Inc. # Taken from GNU libtool, 2001 # Originally by Gordon Matzigkeit , 1996 # # This file is free software; the Free Software Foundation gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # The first argument passed to this file is the canonical host specification, # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM # or # CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM # The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld # should be set by the caller. # # The set of defined variables is at the end of this script. # Known limitations: # - On IRIX 6.5 with CC="cc", the run time search patch must not be longer # than 256 bytes, otherwise the compiler driver will dump core. The only # known workaround is to choose shorter directory names for the build # directory and/or the installation directory. # All known linkers require a '.a' archive for static linking (except MSVC, # which needs '.lib'). libext=a shrext=.so host="$1" host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` # Code taken from libtool.m4's _LT_CC_BASENAME. for cc_temp in $CC""; do case $cc_temp in compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; \-*) ;; *) break;; esac done cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` # Code taken from libtool.m4's _LT_COMPILER_PIC. wl= if test "$GCC" = yes; then wl='-Wl,' else case "$host_os" in aix*) wl='-Wl,' ;; mingw* | cygwin* | pw32* | os2* | cegcc*) ;; hpux9* | hpux10* | hpux11*) wl='-Wl,' ;; irix5* | irix6* | nonstopux*) wl='-Wl,' ;; linux* | k*bsd*-gnu | kopensolaris*-gnu) case $cc_basename in ecc*) wl='-Wl,' ;; icc* | ifort*) wl='-Wl,' ;; lf95*) wl='-Wl,' ;; nagfor*) wl='-Wl,-Wl,,' ;; pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) wl='-Wl,' ;; ccc*) wl='-Wl,' ;; xl* | bgxl* | bgf* | mpixl*) wl='-Wl,' ;; como) wl='-lopt=' ;; *) case `$CC -V 2>&1 | sed 5q` in *Sun\ F* | *Sun*Fortran*) wl= ;; *Sun\ C*) wl='-Wl,' ;; esac ;; esac ;; newsos6) ;; *nto* | *qnx*) ;; osf3* | osf4* | osf5*) wl='-Wl,' ;; rdos*) ;; solaris*) case $cc_basename in f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) wl='-Qoption ld ' ;; *) wl='-Wl,' ;; esac ;; sunos4*) wl='-Qoption ld ' ;; sysv4 | sysv4.2uw2* | sysv4.3*) wl='-Wl,' ;; sysv4*MP*) ;; sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) wl='-Wl,' ;; unicos*) wl='-Wl,' ;; uts4*) ;; esac fi # Code taken from libtool.m4's _LT_LINKER_SHLIBS. hardcode_libdir_flag_spec= hardcode_libdir_separator= hardcode_direct=no hardcode_minus_L=no case "$host_os" in cygwin* | mingw* | pw32* | cegcc*) # FIXME: the MSVC++ port hasn't been tested in a loooong time # When not using gcc, we currently assume that we are using # Microsoft Visual C++. if test "$GCC" != yes; then with_gnu_ld=no fi ;; interix*) # we just hope/assume this is gcc and not c89 (= MSVC++) with_gnu_ld=yes ;; openbsd*) with_gnu_ld=no ;; esac ld_shlibs=yes if test "$with_gnu_ld" = yes; then # Set some defaults for GNU ld with shared library support. These # are reset later if shared libraries are not supported. Putting them # here allows them to be overridden if necessary. # Unlike libtool, we use -rpath here, not --rpath, since the documented # option of GNU ld is called -rpath, not --rpath. hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' case "$host_os" in aix[3-9]*) # On AIX/PPC, the GNU linker is very broken if test "$host_cpu" != ia64; then ld_shlibs=no fi ;; amigaos*) case "$host_cpu" in powerpc) ;; m68k) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; beos*) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; cygwin* | mingw* | pw32* | cegcc*) # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec='-L$libdir' if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then : else ld_shlibs=no fi ;; haiku*) ;; interix[3-9]*) hardcode_direct=no hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; netbsd*) ;; solaris*) if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then ld_shlibs=no elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) case `$LD -v 2>&1` in *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) ld_shlibs=no ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' else ld_shlibs=no fi ;; esac ;; sunos4*) hardcode_direct=yes ;; *) if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then : else ld_shlibs=no fi ;; esac if test "$ld_shlibs" = no; then hardcode_libdir_flag_spec= fi else case "$host_os" in aix3*) # Note: this linker hardcodes the directories in LIBPATH if there # are no directories specified by -L. hardcode_minus_L=yes if test "$GCC" = yes; then # Neither direct hardcoding nor static linking is supported with a # broken collect2. hardcode_direct=unsupported fi ;; aix[4-9]*) if test "$host_cpu" = ia64; then # On IA64, the linker does run time linking by default, so we don't # have to do anything special. aix_use_runtimelinking=no else aix_use_runtimelinking=no # Test if we are trying to use run time linking or normal # AIX style linking. If -brtl is somewhere in LDFLAGS, we # need to do runtime linking. case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) for ld_flag in $LDFLAGS; do if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then aix_use_runtimelinking=yes break fi done ;; esac fi hardcode_direct=yes hardcode_libdir_separator=':' if test "$GCC" = yes; then case $host_os in aix4.[012]|aix4.[012].*) collect2name=`${CC} -print-prog-name=collect2` if test -f "$collect2name" && \ strings "$collect2name" | grep resolve_lib_name >/dev/null then # We have reworked collect2 : else # We have old collect2 hardcode_direct=unsupported hardcode_minus_L=yes hardcode_libdir_flag_spec='-L$libdir' hardcode_libdir_separator= fi ;; esac fi # Begin _LT_AC_SYS_LIBPATH_AIX. echo 'int main () { return 0; }' > conftest.c ${CC} ${LDFLAGS} conftest.c -o conftest aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` if test -z "$aix_libpath"; then aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } }'` fi if test -z "$aix_libpath"; then aix_libpath="/usr/lib:/lib" fi rm -f conftest.c conftest # End _LT_AC_SYS_LIBPATH_AIX. if test "$aix_use_runtimelinking" = yes; then hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" else if test "$host_cpu" = ia64; then hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' else hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" fi fi ;; amigaos*) case "$host_cpu" in powerpc) ;; m68k) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; esac ;; bsdi[45]*) ;; cygwin* | mingw* | pw32* | cegcc*) # When not using gcc, we currently assume that we are using # Microsoft Visual C++. # hardcode_libdir_flag_spec is actually meaningless, as there is # no search path for DLLs. hardcode_libdir_flag_spec=' ' libext=lib ;; darwin* | rhapsody*) hardcode_direct=no if { case $cc_basename in ifort*) true;; *) test "$GCC" = yes;; esac; }; then : else ld_shlibs=no fi ;; dgux*) hardcode_libdir_flag_spec='-L$libdir' ;; freebsd2.[01]*) hardcode_direct=yes hardcode_minus_L=yes ;; freebsd* | dragonfly*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; hpux9*) hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; hpux10*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes fi ;; hpux11*) if test "$with_gnu_ld" = no; then hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' hardcode_libdir_separator=: case $host_cpu in hppa*64*|ia64*) hardcode_direct=no ;; *) hardcode_direct=yes # hardcode_minus_L: Not really in the search PATH, # but as the default location of the library. hardcode_minus_L=yes ;; esac fi ;; irix5* | irix6* | nonstopux*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; netbsd*) hardcode_libdir_flag_spec='-R$libdir' hardcode_direct=yes ;; newsos6) hardcode_direct=yes hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; *nto* | *qnx*) ;; openbsd*) if test -f /usr/libexec/ld.so; then hardcode_direct=yes if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then hardcode_libdir_flag_spec='${wl}-rpath,$libdir' else case "$host_os" in openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) hardcode_libdir_flag_spec='-R$libdir' ;; *) hardcode_libdir_flag_spec='${wl}-rpath,$libdir' ;; esac fi else ld_shlibs=no fi ;; os2*) hardcode_libdir_flag_spec='-L$libdir' hardcode_minus_L=yes ;; osf3*) hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' hardcode_libdir_separator=: ;; osf4* | osf5*) if test "$GCC" = yes; then hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' else # Both cc and cxx compiler support -rpath directly hardcode_libdir_flag_spec='-rpath $libdir' fi hardcode_libdir_separator=: ;; solaris*) hardcode_libdir_flag_spec='-R$libdir' ;; sunos4*) hardcode_libdir_flag_spec='-L$libdir' hardcode_direct=yes hardcode_minus_L=yes ;; sysv4) case $host_vendor in sni) hardcode_direct=yes # is this really true??? ;; siemens) hardcode_direct=no ;; motorola) hardcode_direct=no #Motorola manual says yes, but my tests say they lie ;; esac ;; sysv4.3*) ;; sysv4*MP*) if test -d /usr/nec; then ld_shlibs=yes fi ;; sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) ;; sysv5* | sco3.2v5* | sco5v6*) hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' hardcode_libdir_separator=':' ;; uts4*) hardcode_libdir_flag_spec='-L$libdir' ;; *) ld_shlibs=no ;; esac fi # Check dynamic linker characteristics # Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER. # Unlike libtool.m4, here we don't care about _all_ names of the library, but # only about the one the linker finds when passed -lNAME. This is the last # element of library_names_spec in libtool.m4, or possibly two of them if the # linker has special search rules. library_names_spec= # the last element of library_names_spec in libtool.m4 libname_spec='lib$name' case "$host_os" in aix3*) library_names_spec='$libname.a' ;; aix[4-9]*) library_names_spec='$libname$shrext' ;; amigaos*) case "$host_cpu" in powerpc*) library_names_spec='$libname$shrext' ;; m68k) library_names_spec='$libname.a' ;; esac ;; beos*) library_names_spec='$libname$shrext' ;; bsdi[45]*) library_names_spec='$libname$shrext' ;; cygwin* | mingw* | pw32* | cegcc*) shrext=.dll library_names_spec='$libname.dll.a $libname.lib' ;; darwin* | rhapsody*) shrext=.dylib library_names_spec='$libname$shrext' ;; dgux*) library_names_spec='$libname$shrext' ;; freebsd[23].*) library_names_spec='$libname$shrext$versuffix' ;; freebsd* | dragonfly*) library_names_spec='$libname$shrext' ;; gnu*) library_names_spec='$libname$shrext' ;; haiku*) library_names_spec='$libname$shrext' ;; hpux9* | hpux10* | hpux11*) case $host_cpu in ia64*) shrext=.so ;; hppa*64*) shrext=.sl ;; *) shrext=.sl ;; esac library_names_spec='$libname$shrext' ;; interix[3-9]*) library_names_spec='$libname$shrext' ;; irix5* | irix6* | nonstopux*) library_names_spec='$libname$shrext' case "$host_os" in irix5* | nonstopux*) libsuff= shlibsuff= ;; *) case $LD in *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; *) libsuff= shlibsuff= ;; esac ;; esac ;; linux*oldld* | linux*aout* | linux*coff*) ;; linux* | k*bsd*-gnu | kopensolaris*-gnu) library_names_spec='$libname$shrext' ;; knetbsd*-gnu) library_names_spec='$libname$shrext' ;; netbsd*) library_names_spec='$libname$shrext' ;; newsos6) library_names_spec='$libname$shrext' ;; *nto* | *qnx*) library_names_spec='$libname$shrext' ;; openbsd*) library_names_spec='$libname$shrext$versuffix' ;; os2*) libname_spec='$name' shrext=.dll library_names_spec='$libname.a' ;; osf3* | osf4* | osf5*) library_names_spec='$libname$shrext' ;; rdos*) ;; solaris*) library_names_spec='$libname$shrext' ;; sunos4*) library_names_spec='$libname$shrext$versuffix' ;; sysv4 | sysv4.3*) library_names_spec='$libname$shrext' ;; sysv4*MP*) library_names_spec='$libname$shrext' ;; sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) library_names_spec='$libname$shrext' ;; tpf*) library_names_spec='$libname$shrext' ;; uts4*) library_names_spec='$libname$shrext' ;; esac sed_quote_subst='s/\(["`$\\]\)/\\\1/g' escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` shlibext=`echo "$shrext" | sed -e 's,^\.,,'` escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' < [ ... ] Commands: * create a watermarked wav file with a message audiowmark add * retrieve message audiowmark get * compare watermark message with expected message audiowmark cmp * generate 128-bit watermarking key, to be used with --key option audiowmark gen-key [ --name ] Global options: -q, --quiet disable information messages --strict treat (minor) problems as errors Options for get / cmp: --detect-speed detect and correct replay speed difference --detect-speed-patient slower, more accurate speed detection --json write JSON results into file Options for add / get / cmp: --key load watermarking key from file --short enable short payload mode --strength set watermark strength [10] --input-format raw use raw stream as input --output-format raw use raw stream as output --format raw use raw stream as input and output The options to set the raw stream parameters (such as --raw-rate or --raw-channels) are documented in the README file. HLS command help can be displayed using --help-hls ``` \pagebreak # Audiowmark Architecture The **audiowmark** program is used to integrate (`add` command) and extract (`get` command) watermarks (messages of up to 128 bits) into/from audio files. Internally, the program is organized as nested components, the outermost deals with file IO and command processing. The commands are implemented via various components that process the watermark, audio signal, an optional encoding key and user facing information. ## Adding Watermarks The `audiowmark add [--key…]` command allows adding watermarks to audio files. This command takes an audio file, a 128-bit hexadecimal watermark and an optional key as input, it combines these into a newly generated WAV file. Using the same key, the watermark bits can later be re-retrieved with the `audiowmark get` command without requiring access to the original audio input (this is called blind decoding). By using the encoding key as input, various AES based random number streams are generated to shuffle, interleave and mix the watermark information into the audio signal. For robust extraction and forward error correction, the watermark is encoded via convolutional codes with an order of `15` and a rate of `1/6` (similar to the communication of the [Mars Pathfinder](https://en.wikipedia.org/wiki/Convolutional_code#Popular_convolutional_codes)). The expanded watermark bits are transformed into a delta spectrum at a sample rate of 44100Hz and distributed across various segments (of ca 23 millisecond lengths) of the audio signal and spread across bands above 800Hz and below 5000Hz. Based on the delta spectrum, the watermark signal can be modulated and adapted to the current segment of the input signal before the two are mixed together. To avoid clipping of the output signal, the final output stage consists of a time local limiter with ca 1 second window. \pagebreak An outline of the component interactions to integrate the watermark information via delta band spectrum into the audio signal is provided in the following chart. ~~~~{.graphviz prog=dot} digraph "Audiowmark Watermark Embedding" { graph[fontsize=13,_fontname="sans"]; node[fontsize=13,target="_top",_fontname="sans"]; edge[arrowhead=vee,arrowtail=vee,color="#00000080",_fontname="sans"]; compound=true; concentrate=false; rankdir="TB"; bitvec -> get_frame_mod [color=green4]; AudioInput -> Limiter [color=blue]; AudioInput -> in_resampler [color=blue]; AudioInput -> snr_signal_power [style=dashed,color=gold3]; Key -> Random [minlen=2,color=goldenrod]; subgraph cluster_wmadd { label=< audiowmark add <AudioInput> <AudioOutput> <Bits> [--key…]                         >; Random -> get_frame_mod [color=goldenrod]; in_resampler -> fft_analyzer [color=blue]; { rank=same Random in_resampler } subgraph cluster_WatermarkGen { label=< Watermark Generation >; style=dashed; color=grey; // fontsize=9; node[fontsize=5,margin=0]; edge[fontsize=5,margin=0]; fft_analyzer -> apply_frame_mod [color=blue4,xlabel="  513 FFT Bands \r"]; get_frame_mod -> apply_frame_mod [color=red,xlabel="Up/Down-Band Modulators \r"]; apply_frame_mod -> wm_synth [color=fuchsia,xlabel="FFT Delta Bands  \r"]; } wm_synth -> out_resampler [color=fuchsia]; out_resampler -> Limiter [color=fuchsia]; out_resampler -> snr_signal_power [style=dashed,color=gold3]; } Limiter -> AudioOutput [color=darkmagenta]; snr_signal_power -> SnrOutput [style=dashed,color=gold3]; bitvec [color=green4,margin=0,label=" Watermark Bits "]; Key [color=goldenrod,margin=0,label=" Key "]; AudioInput [color=blue,margin=0,label=" WAV/MP3 Audio Input File "]; AudioOutput [color=darkmagenta,margin=0,label=" WAV Audio Output File "]; { rank=same bitvec Key AudioInput } Random [color=goldenrod,shape=record,label="Random Stream \l AES128/CTR \l"]; in_resampler [color=blue,shape=record,label="Resample to 44.1kHz"]; out_resampler [color=fuchsia,shape=record,label="Resample from 44.1kHz"]; Limiter [color=darkmagenta,shape=rect,label=<
Mixing ⊕ Limiting
• Audio and watermark signals are added
• The result is scaled down to [-0.99…+0.99]
• Uses 1 second detection window
>]; snr_signal_power [style=filled,fillcolor="#ffffbb",color=gold3,shape=rect,label=<
Power Measurement
• Signal Power
• Delta Power
• Ratio Calculation
>]; SnrOutput [color=gold3,margin=0,style=filled,fillcolor="#ffffbb",label=" Signal/Noise Ratio Info "]; fft_analyzer [color=blue4,shape=rect,label=<
Fourier Transform Analyzer
• Input: Time domain samples
• FFT with block Size 1024
• Hann Window
>]; apply_frame_mod [color=fuchsia,shape=rect,label=<
Band Modulation ⊗
• Input bands are Up/Down modulated
• Factor amounts to ±Amplitude^1%
• Output: ± Delta bands
>]; get_frame_mod [color=red,shape=rect,label=<
Modulation Frame Generator
• Encodes Watermark Bits
• Pregenerate A/B-Blocks
• Yields A/B-Block frames
>]; wm_synth [color=fuchsia,shape=rect,label=<
Watermark Signal Synthesis
• Inverse FFT with block size 1024
• Cosine window with overlap of 10%
• Output: Time domain samples
>]; } ~~~~ At a sample frequency of 44100Hz, the audio signal used for the watermark creation is split into "Frames" of 1024 samples each, which corresponds to segments of ca 23 millisecond length. These frames are transformed from the time domain (samples) into the frequency domain (spectral bands) and vice versa to apply the watermark embedding in certain spectral bands. Data and synchronization bits are encoded across several frames, with different levels of redundancy. In the "Modulation Frame Generator" the number of frames that compose all encoded information needed to find and extract the watermark bits are combined into two types of "Blocks". \pagebreak A detailed chart of the component interactions for the Frame and Block generation in the "Modulation Frame Generator" is provided in the next chart. ~~~~{.graphviz prog=dot} digraph "Modulation Frame Generator" { graph[fontsize=13,_fontname="sans"]; node[fontsize=13,target="_top",_fontname="sans"]; edge[arrowhead=vee,arrowtail=vee,color="#00000080",_fontname="sans"]; compound=true; concentrate=false; rankdir="TB"; Random -> ab_generators [color=goldenrod,lhead=cluster_ModulationFrameGenerator]; //Random -> randomize_bit_order [color=goldenrod,xlabel="bit_order R5"]; //Random -> mark_sync [color=goldenrod,xlabel="sync_up_down R2"]; //Random -> mark_data [color=goldenrod,xlabel="data_up_down R1"]; //Random -> frame_pos [color=goldenrod,xlabel="frame_position R6"]; bitvec -> conv_encode [color=green4,minlen=2]; { rank=same Random bitvec } subgraph cluster_ModulationFrameGenerator { label=< Modulation Frame Generator >; style=dashed; color=red; ab_generators -> conv_encode [color=green4,minlen=2]; conv_encode -> randomize_bit_order [color=green4,minlen=2]; { rank=same ab_generators conv_encode } UpDownGen -> mark_sync [color=goldenrod]; UpDownGen -> gen_mix_entries [color=goldenrod]; frame_pos -> mark_sync [color=goldenrod,minlen=1]; // --linear: frame_pos -> mark_data; frame_pos -> gen_mix_entries [color=goldenrod]; randomize_bit_order -> mark_data [color=goldenrod]; init_frame_mod_vec -> get_frame_mod [color=cyan4]; mark_sync -> init_frame_mod_vec [color=cyan3,minlen=1]; mark_data -> init_frame_mod_vec [color=teal]; gen_mix_entries -> mark_data [color=goldenrod]; { rank=same mark_data mark_sync } } get_frame_mod -> apply_frame_mod [color=red,xlabel="Frame Up/Down-Band Modulators",minlen=2]; apply_frame_mod [shape=plain,label=" "]; bitvec [color=green4,margin=0,label=" Watermark Bits "]; Random [color=goldenrod,shape=record,label=" Random Stream \l AES128/CTR \l Streams R1…R6 \l "]; conv_encode [color=green4,shape=rect,label=<
Convolutional Code Expansion
• Pads watermark with termination zeros
• Combines bit stream with A/B constants
• Generates output stream of encoded bits
• Generates 858 encoded bits A-Block
• Generates 858 encoded bits B-Block
>]; ab_generators [color=green4,shape=rect,label=<
Convolutional Code Parameters
• Convolutional code with rate 1/6
• Order 15, needs 15 termination bits
• Six constants for A-Block and B-Block
• Forward correction of ca ≈20% bit errors
• Encodes 128 bits in 858 bit blocks
>]; UpDownGen [color=goldenrod,shape=rect,label=<
Up/Down-Band Generator
• Uses per-frame shuffling seed
• Picks random bands, 30 UP, 30 DOWN
• Bands are between ca 861Hz…4307Hz
>]; mark_sync [color=cyan3,shape=rect,label=<
Synchronization Frame Generator
• Encodes 6 sync bits in 6 * 85 frames
• A-Block bit pattern: 010101
• B-Block bit pattern: 101010
• Randomizes Up/Down-Band shifts [R2]
• Output: 510 Frames * 60 Up/Down-Bands
>]; gen_mix_entries [color=goldenrod,shape=rect,label=<
Mix Entry Generator (skipped for --linear)
• Generates list of data bit encoding bands
• Uses 30 up + 30 down bands in 2 frames per bit
• Randomizes Up/Down-Band shifts [R1]
• Shuffles data bit association of entries [R4]
• Output: 2 * 858 * 30 Up/Down band pairs
>]; mark_data [color=teal,shape=rect,label=<
Data Frame Generator
• Encodes 858 data bits in 858 * 2 frames
• Encodes A-Blocks, B-Blocks in turn
• Omits Mix Entry Generator with --linear
• Randomizes Up/Down-Band shifts [R1]
• Output: 1716 Frames * 60 Up/Down-Bands
>]; frame_pos [color=goldenrod,shape=rect,label=<
Frame Position Randomization
• Mixes sync + data frames
• Shuffles frame positions
• Uses random stream [R6]
>]; randomize_bit_order [color=goldenrod,shape=rect,label=<
Randomize Bit Order for ENCODE
• Reversible shuffle for encode/decode
• Shuffles/interleaves bit stream [R5]
• Interleaving improves robustness
• Reduces bit stream damage impact
>]; init_frame_mod_vec [color=cyan4,shape=rect,label=<
A/B-Block Frame Modulator Composition
• Interleaves synchronization and data frames
• Pulls and interleaves each block type separately
• Output: Up/down band modulators for 1 block
>]; get_frame_mod [color=red,shape=rect,label=<
Modulation Frame Selector
• Yields A-Block band modulators per frame
• Yields B-Block band modulators and starts over
• Output: Up/down band modulators for 1 frame
>]; } ~~~~ The watermark is encoded and embedded into the audio signal in two block types, A-Blocks and B-Blocks. The information contained in each block alone is usually sufficient to extract the watermark. However, in case of very distorted and noisy transmissions where watermark extraction from either block type fails, a combination of segments with A-Block and B-Block data may still lead to successful recovery of the original watermark. In order to support watermark extraction from clipped excerpts of the input stream, a fixed pattern of synchronization bits is integrated into the data blocks with much higher redundancy than the data bits. The fixed pattern allows detection of the location of A-Blocks and B-Blocks as such to aid the watermark extraction. The user provided encoding `Key` seeds an AES based pseudo random number generator in [Counter Mode](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CTR) that is used to determine encoding places, randomize the noise introduced by the watermark and to interleave encoding for robustness. Without the key, the watermark information cannot be retrieved. Using a key is important because the implementation itself is open source, and being able to read the watermark message bits would allow an attacker to remove the watermark without degrading the audio quality. The different types of random data streams used for the distribution of the embedded watermark information are as follows: * R1 - Used to randomizes Up/Down band shifts for watermark data bits. * R2 - Used to randomizes Up/Down band shifts for watermark synchronization bits. * R3 - Currently unused. * R4 - Used to mix (shuffle) data bit associations of Up/Down bands distributed across several frames. * R5 - Used to shuffle (interleave) the bit stream. Due to redundancy in the generated bit stream, interleaving reduces the number of adversely affected bits by bursts (holes) in transmission loss. * R6 - Used to randomize and mix data frames with synchronization frames, this makes synchronization frames unlikely to be detectable without the encoded key. ## Extracting Watermarks The `audiowmark get [--key…]` command extracts a watermark from an audio file. This command takes an audio file and an optional key as input. With the same key used during watermark embedding, synchronization bits are determined and searched for in the audio content. If synchronization bit matches are detected, encoded watermark information can be located, extracted and decrypted with error correction. The retrieval does not require access to the original audio input (this is called blind decoding). The detection results are produced on *stdout* with accompanying information about the location, match quality and a measure for likely decoding errors. An outline of the component interactions to locate and extract the watermark information from the frequency spectrum in the audio signal is provided in the following charts. ~~~~{.graphviz prog=dot} digraph "Audiowmark Watermark Extraction" { newrank=true; graph[fontsize=13,_fontname="sans"]; node[fontsize=13,target="_top",_fontname="sans"]; edge[arrowhead=vee,arrowtail=vee,color="#00000080",_fontname="sans"]; compound=true; concentrate=false; rankdir="TB"; WavData -> in_resampler [color=darkmagenta]; Key -> Random [minlen=2,color=goldenrod]; subgraph cluster_wmget { label=< audiowmark get <AudioFile> [--key…]                                                 >; { rank=same Random in_resampler } in_resampler -> fft_range [color=darkmagenta]; Random -> conv_decode_soft [minlen=2,color=goldenrod, lhead=cluster_WatermarkExtraction]; // fake arrow target for cluster alignment subgraph cluster_WatermarkExtraction { label=< Watermark Extraction >; style=dashed; color=green4; BlockDecoder -> ClipDecoder [color=darkorchid3]; fft_range -> BlockDecoder [color=blue4,constraint=1]; { rank=same fft_range conv_decode_soft } conv_decode_soft -> BlockDecoder [color=green4,dir=both]; conv_decode_soft -> ClipDecoder [color=green4,dir=both]; } BlockDecoder -> SyncFinder [color=cyan4,minlen=4,dir=both,xlabel="Mode::BLOCK"]; ClipDecoder -> SyncFinder [color=cyan4,minlen=1,dir=both,xlabel="Mode::CLIP",constraint=false]; subgraph cluster_SyncFinder { label=""; style=dashed; color=cyan4; node[margin=0]; edge[margin=0]; margin=0; SyncFinder [shape=plaintext, label=<
Synchronization Position Finder
• Performs coarse search for synchronization
  bit markers in the frequency spectrum
• Searches for A/B-Block synchronizations
• Calculates score for possible locations
  and picks the 5 best matches
• Refines the exact block locations with a fine
  grained search for synchronization markers
• Short audio segments are dealt with by
  adding zero padding (in Mode::CLIP)
>]; } ClipDecoder -> result_set_print [color=darkorchid4]; { rank=same SyncFinder result_set_print } } result_set_print -> bitvec [color=green4]; Key [color=goldenrod,margin=0,label=" Key "]; WavData [color=darkmagenta,margin=0,label=" WAV/MP3 Audio Input File "]; { rank=same Key WavData } in_resampler [color=darkmagenta,shape=record,label="Resample to 44.1kHz"]; Random [color=goldenrod,shape=record,label="Random Stream \l AES128/CTR \l"]; fft_range [color=blue4,shape=rect,label=<
Fourier Transform Analyzer
• Input: Frames with time domain samples
• Short inputs are zero-padded as needed (Mode::CLIP)
• FFT with block Size 1024
• Hann Window
• Output: 513 FFT Bands
>]; BlockDecoder [color=darkorchid3,shape=rect,label=<
Best Block Decoder
• Detect A/B-Blocks via 'Synchronization Position Finder' in Mode::BLOCK
• Only works for audio clips with at least 52 seconds and proper block
  alignment (or up from 104 seconds without alignment)
• Reconstruct Up/Down-Band associations (reverses 'Mix Entry Generator') [R4]
• Estimate bit vectors resulting from average deviations in
  randomized Up/Down-Band shifts [R1]
• Unshuffle bit order (reverses 'Randomize Bit Order for ENCODE') [R5]
• Normalize soft bits (normalization of estimated bit vectors)
• Reconstruct watermark bits using 'Soft-Decision Decoder'
• Decode individual A-Blocks, B-Blocks and if present AB-Blocks
• As last resort, attempt an AB-Block decode on accumulated bit vectors
  averaged over all selected blocks
>]; conv_decode_soft [color=green4,shape=rect,label=<
Soft-Decision Decoder
• Utilize Viterbi algorithm
• Decode blocks of 858 encoded bits
• Reconstructs 128 payload bits
• Uses 'Convolutional Code Parameters'
• Decode A-, B- and AB-Blocks
>]; ClipDecoder [color=darkorchid4,shape=rect,label=<
Short Audio Clip Decoder
• Used only for small audio clips up to 160 seconds (3.1 blocks)
• Uses zero padding (silence) around short audio clips to construct 3 blocks
• Zero padded regions are ignored during synchronization detection and scoring
• Determine alignment with 'Synchronization Position Finder' in Mode::CLIP
• Select the five blocks with the best detected synchronization markers
• Reconstruct Up/Down-Band associations (reverses 'Mix Entry Generator') [R4]
• Estimate bit vectors resulting from average deviations in
  randomized Up/Down-Band shifts [R1]
• Unshuffle bit order (reverses 'Randomize Bit Order for ENCODE') [R5]
• Normalize soft bits (normalization of estimated bit vectors)
• Reconstruct watermark bits using 'Soft-Decision Decoder'
• Attempt AB-Block decoding at alignment position
• Attempt secondary AB-Block decoding in case of audio data surplus
• In practice, ca 10 seconds are needed for reliable detection,
  in good scenarios up to 3 seconds may suffice
>]; result_set_print [style=filled,fillcolor="#eeffdd",color=green4,shape=rect,label=<
Result Set Printing
• Sort results by block type and score
• Print potential watermarks on stdout
• Provide time offset, score quality,
  block type and a measure for likely
  decoding errors per watermark
>]; bitvec [style=filled,fillcolor="#eeffdd",color=green4,margin=0,label=" Watermark Bits "]; } ~~~~ At a sample frequency of 44100Hz, a spectral analysis is performed on the audio signal and the spectrum is then searched for known synchronization markers. Upon detection of A/B-Block synchronization positions, watermark bits are extracted from known data bit locations, while making use of the embedded redundancy to make the detection more robust. Due to high redundancy and wide spread of watermark information, bits often can still be extracted from audio clips that are heavily shortened. To employ the full detection machinery to very short clips, symmetric zero padding is used to provide enough input samples (zero padded regions are ignored during scoring however). Since detection success is directly dependent on the precise bit stream synchronization, an iterative process is used for fast approximation of synchronization locations with later refinements to yield precise results. The purpose of the synchronization algorithm is to find the location of the watermark A/B blocks in the input signal. This is important because the signal may have been cropped so that the location of the blocks is not known. To be able to find the locations of the blocks, while adding the watermark, some sync bits are added to each block with relatively high redundancy. The values of these sync bits are known, for an A block they are 010101, for a B block they are 101010. The up- and down-bands used for the sync bits and offsets of all frames that belong to sync bits inside the A / B block are known and determined by the key. To perform the actual synchronization and locate the start of an A (or B) block, two steps are performed. * As a first step, the synchronization algorithm tests all possible positions for the start of an A (or B) block using a step size of 256 (1/4 frame size) and tries to decode the sync bits at the expected locations relative to the start of the block. Since the values and locations of the bits are known, a sync score can be computed that indicates how good the bits in the actual audio input at this position match the expected bit sequence. * For all start locations with a significantly high sync score, in a second step the actual start position is searched by trying all different start locations near to the original match with a smaller finer step size. Again a sync score can be computed and compared to a second threshold to decide whether this is location is really likely to contain a data block. If the match is good enough the start location will be used to decode the data bits in the block. Besides using this strategy to find "whole" data blocks, there is also a variant of the synchronization algorithm that is used if the audio signal is very short. It can find the location of the watermark even if the length of the input signal is too short to contain a complete data block. To be able to do this, the input signal is zero padded before sync detection and then the usual algorithm to find whole blocks is used. The following chart provides the detail of the steps involved in determining the synchronization locations. ~~~~{.graphviz prog=dot} digraph "Synchronization Position Finder" { graph[fontsize=13,_fontname="sans"]; node[fontsize=13,target="_top",_fontname="sans"]; edge[arrowhead=vee,arrowtail=vee,color="#00000080",_fontname="sans"]; compound=true; concentrate=false; rankdir="TB"; in_resampler; Random; { rank=same in_resampler Random } Random -> frame_pos [color=goldenrod,lhead=cluster_SyncFinder,minlen=2]; subgraph cluster_SyncFinder { label=< Synchronization Position Finder >; style=dashed; color=cyan4; in_resampler -> fft_analyzer [color=darkmagenta]; fft_analyzer -> sync_fft_256_8 [color=blue4,dir=both]; frame_pos -> init_up_down [color=goldenrod]; UpDownGen -> init_up_down [color=goldenrod]; init_up_down -> search_approx [color=cyan3]; search_approx -> sync_select_by_threshold [color=cyan3]; sync_select_by_threshold -> search_refine [color=cyan3]; sync_fft_256_8 -> search_refine [style=dashed,dir=back,color=darkcyan,xlabel=" Refining\l Feedback\l                "]; sync_fft_256_8 -> sync_decode [color=darkcyan,style=dashed,label="Repeat\lRefined\l                "]; sync_decode -> search_refine [color=darkcyan,style=dashed,label="Repeat\lRefined\l                "]; sync_fft_256_8 -> sync_decode [color=cyan3]; sync_decode -> search_approx [color=cyan3]; { rank=same fft_analyzer init_up_down } } search_refine -> sync_scores [color=cyan4,xlabel="Scoring and A/B-Type for potential blocks",minlen=2]; sync_scores [shape=plain,label=" "]; in_resampler [color=darkmagenta,shape=record,label=" WAV/MP3 Audio Input File \l \l Resampled to 44.1kHz \l "]; Random [color=goldenrod,shape=record,label=" Random Stream \l AES128/CTR \l Streams R1…R6 \l "]; { rank=same in_resampler Random } frame_pos [color=goldenrod,shape=rect,label=<
Frame Position Randomization
• Mixes sync + data frames
• Shuffles frame positions
• Uses random stream [R6]
>]; UpDownGen [color=goldenrod,shape=rect,label=<
Up/Down-Band Generator
• Uses per-frame shuffling seed
• Picks random bands, 30 UP, 30 DOWN
• Bands are between ca 861Hz…4307Hz
>]; init_up_down [color=cyan3,shape=rect,label=<
Synchronization Bit Frame Generator
(Mode::BLOCK & Mode::CLIP)
• Generates 6 sync bits in 6 * 85 frames (* 2 for Mode::CLIP)
• Randomizes Up/Down-Band shifts [R2]
• Sorts synchronization bit frames by frame index
• Mode::BLOCK Output: 510 Bit Frames with 60 Up & 60 Down-Bands
• Mode::CLIP Output: 1020 Bit Frames with 60 Up & 60 Down-Bands
>]; fft_analyzer [color=blue4,shape=rect,label=<
Fourier Transform Analyzer
• Input: Time domain samples
• FFT with block Size 1024
• Hann Window
• Output: 513 FFT Bands
>]; sync_fft_256_8 [color=blue4,shape=rect,label=<
Decibel Quantifier
• Uses coarse stepping of 256 values for approximate search
• A stepping of 256 equates 1/4th FFT block
• Uses fine stepping of 8 values for refined search
• Pulls FFT Bands for all input blocks
• Computes dB for all bands of all blocks
>]; sync_decode [color=cyan3,shape=rect,label=<
Synchronization Bit Matching
• Collect Up/Down-Band magnitudes for sync bits
• Determine match quality for alternating bit patterns
• Apply watermark strength dependent thresholds
• Decide A/B-Block based on 010101 / 101010 detection
>]; search_approx [color=cyan3,shape=rect,label=<
Approximate Synchronization Frame Search
• Skips over zero-padded samples (Mode::CLIP)
• Computes multiple time-shifted FFT vectors
• Uses coarse subframe stepping of 256 values
• Overlaps frames for sync detection by 1/4th frame
• Scores positions for synchronization matches
>]; sync_select_by_threshold [color=cyan3,shape=rect,label=<
Synchronization Frame Selection
• Due to the subframe stepping, good and bad matches can be expected to alternate
• Identification of local match maxima
• Strength dependent threshold determines minimum match quality
• Selection of likely match positions via maxima and threshold
>]; search_refine [color=darkcyan,shape=rect,label=<
Refined Synchronization Frame Search
• Computes fine-stepped time-shifted FFT vectors around selected frames
• Searches ±16 subframes around previously detected good scores
• Keeps subframe if the score (synchronization frame detection quality) improves
>]; } ~~~~ The user provided 128-Bit AES key is essential to determine spectral bands, encoding patterns, and bit locations. During decoding, the same Pseudo Random Number Generator sequences R1…R6 are used that facilitated watermark embedding. By using the same AES key and a cryptographically secure PRNG, the sequences are uniformly distributed and deterministically reproducible but cannot be extrapolated. This prevents watermark extraction or modification by anyone without possession of the exact encoding key. ## The Patchwork Algorithm ![Example Spectrum](example-spectrum.png) To store one single bit inside a spectrum, **audiowmark** uses the patchwork algorithm. From the frequency bands of the spectrum (generated by computing the FFT of one frame), two groups are choosen in the frequency range of the watermark using the pseudo random number generator. These are called up- and down-bands. In the example above, the up-bands are red and the down-bands are green. Typically there are 30 up- and 30 down-bands and the other bands do not carry information. To embed a single bit, the following changes are made to the spectrum: * to **store a 1 bit**, each magnitude of each up-band is increased by a small amount, and each magnitude of each down-band is decreased by a small amount (this is shown by the small arrows in the example image) * to **store a 0 bit**, each magnitude of each up-band is decreased, and each magnitude of each up-band is increased (the opposite of the small arrows in the example image) Since we have pseudo-randomly choosen the up- and down-bands from the spectrum, we can expect that if we sum up all values of the up-bands and sum up all values of the down-bands **before** embedding the bit, we will get a similar result (because the mean value of all spectrum bins is shared between the two). However, since we increased all elements of the up-bands and decreased all elements of the down-bands **after embedding a 1 bit**, the sum of the up-bands should be **greater than** the sum of the down-bands. So to decode the bit from the spectrum, we can simply use the rule * **decode as 1 bit**, if the sum of the up-bands is greater than the sum of the down-bands * **decode as 0 bit**, if the sum of the up-bands is smaller than the sum of the down-bands In the actual implementation, increasing/decreasing the magnitude of the up-/down-bands is done by generating a watermark signal with the right magnitude/phase for each frame that only contains the changes. So we compute a delta spectrum, which is then passed to the IFFT, windowed and then added to the original audio, so that the sum has the desired modified spectrum magnitude. The detection is performed on dB values of the magnitudes of the spectrum obtained from the FFT, so the sums of the dB values of up-/down-bands are computed and compared to decide whether a 0 bit or 1 bit was received. The patchwork algorithm does not guarantee that encoding/decoding will always yield the right result at the lowest level of embedding/decoding one bit (as the difference of the up-/down-bands can be too big before embedding due to the original signal). However error correction and redundancy by embedding a bit in more than one frame makes the whole process reliable at a higher level. There are three improvements over the basic patch work algorithm described above, which make the watermark detection more accurate: * To use soft-decoding for the convolutional decoder, instead of deciding whether a 0 or 1 bit was received by comparing the two sums directly before decoding the convolutional code to obtain the message bits, the difference between the two sums is normalized and is used as a soft-bit input for the Viterbi algorithm. * Instead of storing one data bit in each frame spectrum, a data bit uses up- and down-bands from different frames. This is called mix-encoding, which spreads the information of each data bit over many frames. * As described above, the original signal can have some negative effect on the performance of the decoder, since the sum of the up-bands and the sum of the down-bands will be different even before embedding the bits. To make detection more reliable, the original signal level for each bin is estimated by taking the average value of the previous and next spectrum and subtracted before computing the sum of the up- and down-bands. ## Mixing with Limiter The input material for **audiowmark** is normalized (all samples are in the range from -1 to 1). If we simply added the watermark to the input, it could happen that this sum exceeds the range from -1 to 1 which would result in clipping. To avoid this, a limiter during mixing is used. The limiter computes the highest peak for each one second long block. Then a linear volume envelope is constructed connecting the blocks, such that the envelope is greater or equal to the height of the peaks in each block. The typical value for really high peaks is about 1.04 for the default watermarking strength of 10. To avoid clipping, the signal is divided by the slowly changing volume envelope. The result is somewhat similar to a lookahead peak limiter with attack of one second, and linear release of one second. Or to describe the effect more directly, if a single peak of 1.04 was produced in the watermarked signal, the limiter would slowly start decreasing the volume to 1/1.04 over the time of one second before the block that contains the peak, stay there for a while (due to the use of blocks) and afterwards slowly increase the volume again over the time of one second. By using a limiter that works on one second blocks like this, it is possible to seek to any point in the watermark (which is required for streaming via HLS) and getting the exact same output that watermarking all previous samples would have produced, because the output of the limiter only depends on the current, previous and next one second block. So only a small context window needs to be processed when seeking. ## Speed Detection As one of the later developments, a dedicated speed detection facility has been integrated that explores the ability to extract watermarks from audio segments played back at unknown rates. In scenarios where audio has been resampled and pitched at a constant rate, synchronization markers may still be detectable by searching the audio content at varying resampled playback rates. The `audiowmark --detect-speed` command line option attempts to detect playback rate changes compared to the original material used for embedding within 80% to 125%. Detection of playback rate modifications is approached in several steps. First, the detector picks two short audio clips (ca 25 seconds) with high signal energy and performs multiple coarse scans while detecting <0.2% rate modulations. This rough speed estimate is improved upon with secondary scans around 1/20‰ rate modulations on an audio clip of 50 second. Executing coarse and fine detection runs at varying resampled playback rates with multiple refining steps consumes a lot of processing resources. To speed up the detection, resampling and scanning runs are carried out on a downsampled version of the audio material (by a factor of 2) and detection runs are parallelized across all available CPU cores. Finally, the watermark extraction is carried out on a resampled version of the audio material at the most likely detected playback rate in addition to regular watermark detection, because the detected playback rate may have been guessed wrongly. audiowmark-0.6.5/docs/example-spectrum.dat000066400000000000000000000002441501136502400205770ustar00rootroot000000000000001 10513 3 2 13061 2 3 11180 1 4 8732 2 5 13586 3 6 14266 1 7 14647 1 8 7102 3 9 16472 2 10 6856 2 11 13923 1 12 16685 2 13 16025 3 14 16708 3 15 11310 1 16 16395 3 audiowmark-0.6.5/docs/example-spectrum.gp000066400000000000000000000014551501136502400204420ustar00rootroot00000000000000set terminal pngcairo size 800,450 set output 'example-spectrum.png' # Set bar width set boxwidth 0.5 set style fill solid border -1 set linetype 1 lc rgb "#990000" lw 1 set linetype 2 lc rgb "#009900" lw 1 set linetype 3 lc rgb "#666666" lw 1 set arrow from graph 0,1 to graph 0,1.1 filled set arrow from graph 1,0 to graph 1.1,0 filled set tmargin 5 set rmargin 10 set border 3 set tics nomirror set noxtics set noytics set grid set ylabel "Magnitude" set xlabel "Frequency" # Set the range for y-axis set yrange [0:*] # Plot the data with different colors plot "example-spectrum.dat" using 1:2:3 with boxes lc variable notitle, \ "' % dest) return Para ([Image ([ident, [], keyvals], caption, [dest, typef])]) if __name__ == "__main__": toJSONFilter (graphviz) audiowmark-0.6.5/docs/videowmark-win.cc000066400000000000000000000577231501136502400201020ustar00rootroot00000000000000/* * Copyright (C) Andreas Strohmeier * * This program is a port of the Linux bash script videowmark, * which is part of the audiowmark program by Stefan Westerfeld, * into the C++ programming language. * To keep it simple, there is only this single CPP file without a * header file. * * 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 //--------------------------------------------------------------------------- using namespace std; //--------------------------------------------------------------------------- string g_sVersion = "videowmark 0.0.5"; string g_sFFMPEG_VERBOSE = "-v error"; string g_sFFProbe = "ffprobe.exe"; string g_sFFMpeg = "ffmpeg.exe"; string g_sAudiowmark = "audiowmark.exe"; int g_iQuiet = 0; //--------------------------------------------------------------------------- #define STRING_LENGTH 4096 //--------------------------------------------------------------------------- //show message and exit int die(string sErrorMsg) { printf("videowmark: error: %s\n", sErrorMsg.c_str()); exit(1); } //--------------------------------------------------------------------------- // Fix me // Maybe there is es better solution // // The result of getenv is not correct in cygwin environment. // // Example: getenv("TEMP"); // // Result : /cygdrive/c/path/to/temp // But should be: c:\path\to\temp // // This is a workaround to repair the path string repair_cygwin_path(string sPath) { int i = 0; string sResult = ""; if(int(sPath.find("/cygdrive/")) == 0 ) { sPath.erase(0, 10); for(i = 0; i < int(sPath.length()); i++) { if(sPath[i] == '/') { sPath[i] = '\\'; } } if(sPath[1] != ':') { sPath.insert(1, ":"); } } sResult = sPath; return sResult; } //--------------------------------------------------------------------------- // Converts the Windows path to the UNIX path string get_unix_path(string sPath) { int i = 0; string sResult = ""; for(i = 0; i < int(sPath.length()); i++) { if(sPath[i] == '\\') { sPath[i] = '/'; } } sResult = sPath; return sResult; } //--------------------------------------------------------------------------- // Converts the UNIX path to the Windows path string get_windows_path(string sPath) { int i = 0; string sResult = ""; for(i = 0; i < int(sPath.length()); i++) { if(sPath[i] == '/') { sPath[i] = '\\'; } } sResult = sPath; return sResult; } //--------------------------------------------------------------------------- // Completes the path if necessary string complete_path(string sPath) { int iPathLength = 0; bool bUNIX = true; string sResult = ""; iPathLength = sPath.length(); if(iPathLength > 0) { if(int(sPath.find("\\")) >= 0 ) { bUNIX = false; } if(bUNIX == true) { // UNIX if(sPath[iPathLength-1] != '/') { sPath += "/"; } } else { // Windows if(sPath[iPathLength-1] != '\\') { sPath += "\\"; } } } sResult = sPath; return sResult; } //--------------------------------------------------------------------------- // Set the current working directory string set_working_dir(string sDestExe) { string sResult = ""; string sWorkingDir = ""; string sPath = ""; string sTempUnixPath = ""; char cWorkingDir[STRING_LENGTH] = {}; //If something goes wrong while getting the working dir sResult = sDestExe; //Get current application directory GetModuleFileNameA(NULL, cWorkingDir, STRING_LENGTH); sWorkingDir = cWorkingDir; if(sDestExe.length() > 0 && sWorkingDir.length() > 0) { //Repair the path if needed sWorkingDir = repair_cygwin_path(sWorkingDir); //Convert Windows path to UNIX path sWorkingDir = get_unix_path(sWorkingDir); //Fix me: //Create filesystem::path object ( works in Cygwin only with UNIX path ) filesystem::path p(sWorkingDir); //Get file path sPath = p.parent_path(); //Completes the path if necessary sPath = complete_path(sPath); //Convert UNIX path to Windows path sPath = get_windows_path(sPath); //Build the filename sResult = "\"" + sPath + sDestExe + "\""; } return sResult; } //--------------------------------------------------------------------------- // Create the temp file string create_temp_file(string sFilename) { string sTempPath = ""; string sFilenameWoPath = ""; string sTempUnixFilename = ""; string sTempFilename = ""; //Fix me: //The result of getenv is not correct in cygwin environment. // //Example: getenv("TEMP"); // //Result : /cygdrive/c/path/to/temp //But should be: c:\path\to\temp // //Get environment variable TEMP sTempPath = getenv("TEMP"); //Repair the path if needed sTempPath = repair_cygwin_path(sTempPath); //Completes the path if necessary sTempPath = complete_path(sTempPath); //Convert Windows path to UNIX path sTempUnixFilename = get_unix_path(sFilename); //Fix me: //Create filesystem::path object ( works in Cygwin only with UNIX path ) filesystem::path p(sTempUnixFilename); //Get filename without path sFilenameWoPath = p.filename(); //Create filename sTempFilename = sTempPath + sFilenameWoPath; //Create temp file at destination if(CopyFile(sFilename.c_str(), sTempFilename.c_str(), false) == false) { die("Could not create temp file"); } //Return file path return sTempFilename; } //--------------------------------------------------------------------------- // Delete file bool delete_temp_file(string sFilename) { return DeleteFile(sFilename.c_str()); } //--------------------------------------------------------------------------- // Execute a command and get the results. string ExecCmd(string sCMD /* [in] command to execute */ ) { int i = 0; string strResult = ""; char cmd[STRING_LENGTH] = {}; HANDLE hPipeRead = 0; HANDLE hPipeWrite = 0; strcpy(cmd, sCMD.c_str()); SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES)}; saAttr.bInheritHandle = TRUE; // Pipe handles are inherited by child process. saAttr.lpSecurityDescriptor = NULL; // Create a pipe to get results from child's stdout. if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) return strResult; STARTUPINFOA si = {sizeof(STARTUPINFOA)}; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.hStdOutput = hPipeWrite; si.hStdError = hPipeWrite; si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing. // Requires STARTF_USESHOWWINDOW in dwFlags. PROCESS_INFORMATION pi = { 0 }; BOOL fSuccess = CreateProcessA(NULL, (LPSTR)cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); if (! fSuccess) { CloseHandle(hPipeWrite); CloseHandle(hPipeRead); return strResult; } bool bProcessEnded = false; for (; !bProcessEnded ;) { // Give some timeslice (50 ms), so we won't waste 100% CPU. bProcessEnded = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0; // Even if process exited - we continue reading, if // there is some data available over pipe. char buf[STRING_LENGTH] = {}; DWORD dwRead = 0; DWORD dwAvail = 0; for (;;) { if (!PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL)) break; if (!dwAvail) // No data available, return break; if (!ReadFile(hPipeRead, buf, min( sizeof(buf) - 1, (long unsigned int )dwAvail ), &dwRead, NULL) || !dwRead) // Error, the child process might ended break; //Get results strResult += buf; //Clean up entire buffer to remove trash for(i = 0; i < STRING_LENGTH; i++){buf[i] = 0;} } } CloseHandle(hPipeWrite); CloseHandle(hPipeRead); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return strResult; } //--------------------------------------------------------------------------- //auto detect codec and bitrate from input stream, generate ffmpeg options for audio encoder string audio_encode_options(string sFilename) { string sParam = ""; string sCMD = ""; string sResult = ""; string sValue = ""; string sCodecName = ""; string sProbeResult = ""; string sTempResult = ""; string sBitRate = ""; string sData = ""; int i = 0; int iPos = 0; //ffprobe -v error -print_format compact -show_streams "$1" sParam = "-print_format compact -show_streams \"" + sFilename + "\""; sCMD = g_sFFProbe + " " + g_sFFMPEG_VERBOSE + " " + sParam; //Execute and get results sProbeResult = ExecCmd(sCMD); //Parse to find the audio line for(i = 0; i < int(sProbeResult.length()); i++) { if(sProbeResult[i] != 0x0d && sProbeResult[i] != 0x0a) { sTempResult += sProbeResult[i]; } if(sProbeResult[i] == 0x0d) { if(int(sTempResult.find("codec_type=audio")) > 0) { sResult = sTempResult; break; } else { sTempResult = ""; } } } //Parse to find codec_name and bit_rate if(sResult.length() > 0) { sValue = "codec_name="; iPos = int(sResult.find(sValue)); if(iPos > 0) { iPos += sValue.length(); while(sResult[iPos] != '|') { sCodecName += sResult[iPos]; iPos++; } } sValue = "bit_rate="; iPos = int(sResult.find(sValue)); if(iPos > 0) { iPos += sValue.length(); while(sResult[iPos] != '|') { sBitRate += sResult[iPos]; iPos++; } } // opus encoder is experimental, ffmpeg recommends libopus for encoding if (sCodecName == "opus") { sCodecName = "libopus"; } sResult = "-c:a " + sCodecName; if (sBitRate != "N/A") { sResult += " -ab " + sBitRate; } } return sResult; } //--------------------------------------------------------------------------- // count number of audio and video streams, typical output: "audio=1:video=1" string audio_video_stream_count(string sFilename) { string sParam = ""; string sCMD = ""; string sResult = ""; char cResult[STRING_LENGTH] = {}; int iVideoCodec = 0; int iAudioCodec = 0; //ffprobe -v error -print_format compact -show_streams "$1" sParam = "-print_format compact -show_streams \"" + sFilename + "\""; sCMD = g_sFFProbe + " " + g_sFFMPEG_VERBOSE + " " + sParam; //Execute and get results sResult = ExecCmd(sCMD); if(sResult.length() > 0) { if(int(sResult.find("codec_type=audio")) > 0) { iAudioCodec = 1; } if(int(sResult.find("codec_type=video")) > 0) { iVideoCodec = 1; } sprintf(cResult, "audio=%d:video=%d", iAudioCodec, iVideoCodec); sResult = cResult; } return sResult; } //--------------------------------------------------------------------------- // get file extension string extension(string sFilename) { string sResult = ""; string sTempUnixFilename = ""; //Fix me: //filesystem::path in Cygwin only works with / sTempUnixFilename = get_unix_path(sFilename); filesystem::path p(sTempUnixFilename); sResult = p.extension(); return sResult; } //--------------------------------------------------------------------------- // add watermark to file void add_watermark(string sInFile, string sOutFile, string sHash, string sARGS) { string sCMD = ""; string sParam = ""; string sExtIn = ""; string sExtOut = ""; string sResult = ""; string sStreamCount = ""; string sEncodeOptions = ""; string sTempFilenameVideo = ""; string sTempFilenameAudio = ""; string sTempFilenameAudioWM = ""; // check file extensions sExtIn = extension(sInFile); sExtOut = extension(sOutFile); if(sExtIn != sExtOut) { die("input/output extension must match ('" + sExtIn + "' vs. '" + sExtOut + "')"); } // check audio/video stream count sStreamCount = audio_video_stream_count(sInFile); if(sStreamCount != "audio=1:video=1") { printf("videowmark: detected input file stream count: %s\n", sStreamCount.c_str()); die ("input file must have one audio stream and one video stream"); } // create tmpfiles sTempFilenameVideo = create_temp_file(sInFile); // create audio tmpfilename ( just the name, not the file ) sTempFilenameAudio = sTempFilenameVideo + ".wav"; // create wm_audio tmpfilename ( just the name, not the file ) sTempFilenameAudioWM = sTempFilenameVideo + "_wm.wav"; // get audio as wav //ffmpeg $FFMPEG_VERBOSE -y -i "$in_file" -f wav -rf64 always "$wav" sParam = "-y -i \"" + sTempFilenameVideo + "\" -f wav -rf64 always \"" + sTempFilenameAudio + "\""; sCMD = g_sFFMpeg + " " + g_sFFMPEG_VERBOSE + " " + sParam; //Execute and get results sResult = ExecCmd(sCMD); //Show detailed FFMpeg results if(g_sFFMPEG_VERBOSE == "-v info"){printf("%s\n", sResult.c_str());} if(sResult.length() > 0) { if(int(sResult.find("Error")) > 0) { //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); delete_temp_file(sTempFilenameAudioWM); die("extracting audio from video failed (ffmpeg)"); } } // add watermark // audiowmark add "${AUDIOWMARK_ARGS[@]}" "$orig_wav" "$wm_wav" "$bits" --set-input-label "$in_file" --set-output-label "$out_file" --output-format rf64" sEncodeOptions = audio_encode_options(sInFile); if(g_iQuiet == 0){printf("Audio Codec: %s\n", sEncodeOptions.c_str());} sParam = "add " + sARGS + " \"" + sTempFilenameAudio + "\" \"" + sTempFilenameAudioWM + "\" " + sHash + " --set-input-label \"" + sInFile + "\" --set-output-label \"" + sOutFile + "\" --output-format rf64"; sCMD = g_sAudiowmark + " " + sParam; //Execute and get results sResult = ExecCmd(sCMD); if(sResult.length() > 0) { if(int(sResult.find("Error")) > 0) { //Show detailed cause of error if(g_sFFMPEG_VERBOSE == "-v info"){printf("%s\n", sResult.c_str());} //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); delete_temp_file(sTempFilenameAudioWM); die("watermark generation failed (audiowmark)"); } } //Show Audiowmark results if(g_iQuiet == 0) { printf("%s\n", sResult.c_str()); } // rejoin // ffmpeg $FFMPEG_VERBOSE -y -i "$in_file" -i "$wm_wav" -c:v copy $(audio_encode_options "$in_file") -map 0:v:0 -map 1:a:0 "$out_file" sParam = "-y -i \"" + sTempFilenameVideo + "\" -i \"" + sTempFilenameAudioWM + "\" -c:v copy " + sEncodeOptions + " -map 0:v:0 -map 1:a:0 \"" + sOutFile + "\""; sCMD = g_sFFMpeg + " " + g_sFFMPEG_VERBOSE + " " + sParam; //Execute and get results sResult = ExecCmd(sCMD); //Show detailed FFMpeg results if(g_sFFMPEG_VERBOSE == "-v info"){printf("%s\n", sResult.c_str());} if(sResult.length() > 0) { if(int(sResult.find("Error")) > 0) { //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); delete_temp_file(sTempFilenameAudioWM); die("merging video and watermarked audio failed (ffmpeg)"); } } //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); delete_temp_file(sTempFilenameAudioWM); } //--------------------------------------------------------------------------- // get watermark from file void get_watermark(string sFilename, string sARGS) { string sParam = ""; string sCMD = ""; string sTempPath = ""; string sResult = ""; string sTempFilenameVideo = ""; string sTempFilenameAudio = ""; // check audio/video stream count sResult = audio_video_stream_count(sFilename); if(sResult.length() > 0) { if(sResult != "audio=1:video=1" ) { printf("videowmark: detected input file stream count: %s\n", sResult.c_str()); die("input file must have one audio stream and one video stream"); } } // create video tmpfile sTempFilenameVideo = create_temp_file(sFilename); // create audio tmpfilename ( just the name, not the file ) sTempFilenameAudio = sTempFilenameVideo + ".wav"; // get audio as wav //ffmpeg $FFMPEG_VERBOSE -y -i "$in_file" -f wav -rf64 always "$wav" || die "extracting audio from video failed (ffmpeg)" sParam = "-y -i \"" + sTempFilenameVideo + "\" -f wav -rf64 always \"" + sTempFilenameAudio + "\""; sCMD = g_sFFMpeg + " " + g_sFFMPEG_VERBOSE + " " + sParam; //Execute and get results sResult = ExecCmd(sCMD); //Show detailed FFMpeg results if(g_sFFMPEG_VERBOSE == "-v info"){printf("%s\n", sResult.c_str());} if(sResult.length() > 0) { if(int(sResult.find("Error")) > 0) { //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); die("extracting audio from video failed (ffmpeg)"); } } // get watermark //audiowmark get "${AUDIOWMARK_ARGS[@]}" "$wav" || die "retrieving watermark from audio failed (audiowmark)" sParam = "get " + sARGS + " \"" + sTempFilenameAudio + "\""; sCMD = g_sAudiowmark + " " + sParam; //Execute and get results sResult = ExecCmd(sCMD); if(sResult.length() > 0) { if(int(sResult.find("Error")) > 0) { //Show detailed cause of error if(g_sFFMPEG_VERBOSE == "-v info"){printf("%s\n", sResult.c_str());} //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); die("retrieving watermark from audio failed (audiowmark)"); } } //Show Audiowmark results printf("%s\n", sResult.c_str()); //Clean up delete_temp_file(sTempFilenameVideo); delete_temp_file(sTempFilenameAudio); } //--------------------------------------------------------------------------- void show_version_and_exit() { printf("%s\n", g_sVersion.c_str()); exit(0); } //--------------------------------------------------------------------------- void show_help_and_exit() { printf( "usage: videowmark [ ... ]\n" "\n" "Commands:\n" " * create a watermarked video file with a message\n" " videowmark add \n" "\n" " * retrieve message\n" " videowmark get \n" "\n" "Global options:\n" " --strength set watermark strength\n" " --key load watermarking key from file\n" " -q, --quiet disable information messages\n" " -v, --verbose enable ffmpeg verbose output\n" " --version show the current videowmark version\n" " --detect-speed detect and correct replay speed difference\n" " --detect-speed-patient slower, more accurate speed detection\n" ); exit(0); } //--------------------------------------------------------------------------- int main(int argc , char *argv[]) { string sResult = ""; string sAction = ""; string sKeyfile = ""; string sFilename = ""; string sInFile = ""; string sOutFile = ""; string sHash = ""; string sARGS = ""; int i = 0; // Set the the current working directory g_sFFProbe = set_working_dir(g_sFFProbe); g_sFFMpeg = set_working_dir(g_sFFMpeg); g_sAudiowmark = set_working_dir(g_sAudiowmark); if(argc > 1) { // Get all args for(i = 1; i < argc; i++) { if(string(argv[i]) == "-v" || string(argv[i]) == "--verbose") { g_sFFMPEG_VERBOSE = "-v info"; } else if(string(argv[i]) == "--version") { show_version_and_exit(); } else if(string(argv[i]) == "-q" || string(argv[i]) == "--quiet") { sARGS += " -q"; g_iQuiet = 1; } else if(string(argv[i]) == "--detect-speed" || string(argv[i]) == "--detect-speed-patient") { sARGS += " " + string(argv[i]); } else if(string(argv[i]) == "-h" || string(argv[i]) == "--help") { show_help_and_exit(); } else if(string(argv[i]) == "--key") { if(argc >= i+1 ) { sARGS += " " + string(argv[i]) + " " + string(argv[i+1]); i++; } else { die("videowmark: error parsing command line arguments (use videowmark -h)"); } } else if(string(argv[i]) == "--strength") { if(argc >= i+1 ) { sARGS += " " + string(argv[i]) + " " + string(argv[i+1]); i++; } else { die("videowmark: error parsing command line arguments (use videowmark -h)"); } } else if(string(argv[i]) == "add" || string(argv[i]) == "get" || string(argv[i]) == "probe") { sAction = string(argv[i]); } else if(sInFile.length() == 0 && string(argv[i]).length() > 0) { sInFile = string(argv[i]); } else if(sOutFile.length() == 0 && string(argv[i]).length() > 0) { sOutFile = string(argv[i]); } else if(sHash.length() == 0 && string(argv[i]).length() > 0) { sHash = string(argv[i]); } else { // } } // Get and execute action if(sAction == "add" && sInFile.length() > 0 && sOutFile.length() > 0 && sHash.length() > 0) { add_watermark(sInFile, sOutFile, sHash, sARGS); } else if(sAction == "get" && sInFile.length() > 0) { get_watermark(sInFile, sARGS); } else if(sAction == "probe" && sInFile.length() > 0) { printf("%s %s\n", sInFile.c_str(),audio_encode_options(sInFile).c_str()); } else { printf("videowmark: error parsing command line arguments (use videowmark -h)\n"); } } else { show_help_and_exit(); } return 0; } //--------------------------------------------------------------------------- audiowmark-0.6.5/docs/win-x64-build-guide.txt000066400000000000000000000160561501136502400207670ustar00rootroot00000000000000Last update: 05.03.2024 In this step-by-step guide I show how to build Audiowmark to run it on Windows x64. I won't explain every single tool, just the creation process. That's enough work. Following these instructions exactly should lead to success. Prerequisites (programs / source codes / libraries): - Download source code from Audiowmark DL: https://github.com/swesterfeld/audiowmark/releases I chose this one : audiowmark-0.6.2.tar.zst - Download source code from zita-resampler DL: https://github.com/digital-stage/zita-resampler/ - Download 7zip ( newest beta version to extract .zst files ) DL: https://www.7-zip.org/ - Download Notepad++ DL: https://notepad-plus-plus.org/ - Download Cygwin DL: https://cygwin.com/ - Download CMAKE DL: https://cmake.org/download/ - Download MinGW-w64 DL: https://github.com/niXman/mingw-builds-binaries/releases I chose this one : x86_64-13.2.0-release-posix-seh-msvcrt-rt_v11-rev0.7z - Download FFmpeg DL: https://github.com/BtbN/FFmpeg-Builds/releases I chose this one : ffmpeg-master-latest-win64-gpl-shared.zip Prepare everything: - Install Cygwin ( do a clean install ) We need to add the following libraries to Cygwin ( choose always the latest stable release ) - gcc-core - gcc-debuginfo - gcc-g++ - mingw64-x86_64-gcc-core - mingw64-x86_64-gcc-g++ - make - make-debuginfo - libfftw3-devel - libsndfile-devel - libgcrypt-devel - libmpg123-devel - libzita-resampler ( Not included in Cygwin. We have to compile and copy it later manually ) - FFmpeg ( It is available in Cygwin, but i had some problems with it. So we add it later manually ) - Install CMAKE - Install Notepad++ - Install 7zip ( newest beta version to extract .zst files ) - Extract zita-resempler-main.zip to c:\zita-resempler-main - Extract audiowmark-0.6.2.tar.zst to c:\audiowmark-0.6.2 Edit "c:\audiowmark-0.6.2\src\utils.cc" and insert the following line directly below the comment section ( needed for vasprintf ) #define _GNU_SOURCE - Extract ffmpeg-master-latest-win64-gpl-shared.zip to c:\ffmpeg-master-latest-win64-gpl-shared - Extract x86_64-13.2.0-release-posix-seh-msvcrt-rt_v11-rev0.7z\mingw64 to c:\mingw64 - Add "C:\mingw64\bin" to the system path variable and place it at the very first position - Important : Restart Windows ! - After restart windows edit the file c:\zita-resempler-main\CMakeLists.txt with Notepad++. Find the line below # make ( should be line 22 ) and insert the parameter SHARED. # make before: add_library(zita-resampler ${SOURCES} ${HEADER_LIST}) after: add_library(zita-resampler SHARED ${SOURCES} ${HEADER_LIST}) - Start CMAKE Select the source dir c:\zita-resempler-main Select / create the build dir ( f.e.: c:\zita-resampler-main\build64 ) Press "Configure" Choose "MinGW Makefiles as generator" Choose "Use default native compilers" Press "Finish" to complete the configuration We should see : Configuring done ( ignore the red values in the list above ) After successfull configuration press "Generate" We should see : Gernerating done Close CMAKE - Open the command prompt ( cmd.exe ) and go to the directory "c:\zita-resampler-main\build64". Type in: mingw32-make We should see : [100%] Built target zita-resampler Now we should have the following two new files: "c:\zita-resampler-main\build64\libzita-resampler.dll" "c:\zita-resampler-main\build64\libzita-resampler.dll.a" Close CMD - Install zita-resampler - Copy the file "c:\zita-resampler-main\build64\libzita-resampler.dll" to "C:\cygwin64\usr\x86_64-pc-cygwin\bin" - Copy the file "c:\zita-resampler-main\build64\libzita-resampler.dll.a" to "C:\cygwin64\usr\x86_64-pc-cygwin\lib" - Copy the whole directory "C:\zita-resampler-main\source\zita-resampler" to "C:\audiowmark-0.6.2\src\zita-resampler" - Install FFmpeg - Copy ALL files from "C:\ffmpeg-master-latest-win64-gpl-shared\bin\*.*" to "C:\cygwin64\usr\x86_64-w64-mingw32\sys-root\mingw\bin" - Copy ALL files ( except dir "pkgconfig" ) from "C:\ffmpeg-master-latest-win64-gpl-shared\lib\*.*" to "C:\cygwin64\usr\x86_64-w64-mingw32\sys-root\mingw\lib" - Copy ALL files ( except dir "pkgconfig" ) from "C:\ffmpeg-master-latest-win64-gpl-shared\lib\*.*" to "C:\cygwin64\usr\x86_64-pc-cygwin\lib" - Copy ALL .pc-files from "C:\ffmpeg-master-latest-win64-gpl-shared\lib\pkgconfig\*.pc" to "C:\cygwin64\lib\pkgconfig" - Copy ALL sub directories from "C:\ffmpeg-master-latest-win64-gpl-shared\include\*" to "C:\audiowmark-0.6.2\src\*" Now we should be ready to build the audiowmark source code: - Start Cygwin64-Terminal with admin rights Change the current directory to: /cygdrive/c/audiowmark-0.6.2 - Type in: ./configure --host=x86_64-pc-cygwin --with-ffmpeg - Type in: make All created EXE-files will be saved to "C:\audiowmark-0.6.2\src\.libs" Note : There is another, significantly smaller version of each EXE file in "C:\audiowmark-0.6.2\src\". Don't use these. They don't work. The last part to do is to build the videowmark.exe. The file "C:\audiowmark-0.6.2\src\videowmark" is a linux bash script and can not executed on windows. So I ported it to C++. To keep it simple, I made only this single CPP file without a header file. - Copy the file "C:\audiowmark-0.6.2\docs\videowmark-win.cc" to "C:\audiowmark-0.6.2\src\videowmark.cc" - Open Cygwin and and go to the directory "/cygdrive/c/audiowmark-0.6.2/src/" - Type in : g++ -o videowmark.exe videowmark.cc Okay, we are almost finished. In order to deliver audiowmark.exe and videowmark.exe all corresponding DLL- and EXE-files must also be delivered. And that's a lot. Every single DLL- and EXE-file we will find somewhere in "C:\cygwin64" Each file must be copied into the SAME directory where the EXE file is located. --------------- Location in Windows : C:\audiowmark-0.6.2\src\.libs\ Location in Cygwin : /cygdrive/c/audiowmark-0.6.2/src/.libs --------------- audiowmark.exe --------------- Location in Windows : C:\audiowmark-0.6.2\src\ Location in Cygwin : /cygdrive/c/audiowmark-0.6.2/src/ --------------- videowmark.exe --------------- Location in Windows : C:\cygwin64\usr\x86_64-w64-mingw32\sys-root\mingw\bin Location in Cygwin : /usr/x86_64-w64-mingw32/sys-root/mingw/bin/ --------------- ffmpeg.exe ffplay.exe ffprobe.exe avcodec-60.dll avdevice-60.dll avfilter-9.dll avformat-60.dll avutil-58.dll libatomic-1.dll libgcc_s_seh-1.dll libgomp-1.dll libquadmath-0.dll libssp-0.dll libstdc++-6.dll libwinpthread-1.dll postproc-57.dll swresample-4.dll swscale-7.dll --------------- Location in Windows : C:\cygwin64\bin Location in Cygwin : /usr/bin/ --------------- cygiconv-2.dll cygintl-8.dll cyggpg-error-0.dll cygvorbis-0.dll cygvorbisenc-2.dll cygogg-0.dll cygFLAC-8.dll cygopus-0.dll cygmp3lame-0.dll cyggcrypt-20.dll cygsndfile-1.dll cygmpg123-0.dll cygstdc++-6.dll cygwin1.dll cyggcc_s-seh-1.dll cygfftw3f-3.dll --------------- Location in Windows : C:\cygwin64\usr\x86_64-pc-cygwin\bin Location in Cygwin : /usr/x86_64-pc-cygwin/bin/ --------------- libzita-resampler.dll That's it. We're done. That was easy, wasn't it? BR Andreas audiowmark-0.6.5/m4/000077500000000000000000000000001501136502400142025ustar00rootroot00000000000000audiowmark-0.6.5/m4/ax_check_compile_flag.m4000066400000000000000000000040701501136502400207130ustar00rootroot00000000000000# =========================================================================== # 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 audiowmark-0.6.5/m4/ax_cxx_compile_stdcxx.m4000066400000000000000000000512061501136502400210470ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html # =========================================================================== # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the specified # version of the C++ standard. If necessary, add switches to CXX and # CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for # the respective C++ standard version. # # The second argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for no added switch, and then for an extended mode. # # The third argument, if specified 'mandatory' or if left unspecified, # indicates that baseline support for the specified C++ standard is # required and that the macro should error out if no mode with that # support is found. If specified 'optional', then configuration proceeds # regardless, after defining HAVE_CXX${VERSION} if and only if a # supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # Copyright (c) 2015 Moritz Klammler # Copyright (c) 2016, 2018 Krzesimir Nowak # Copyright (c) 2019 Enji Cooper # Copyright (c) 2020 Jason Merrill # Copyright (c) 2021 Jörn Heusipp # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 15 dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro dnl (serial version number 13). AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], [$1], [14], [ax_cxx_compile_alternatives="14 1y"], [$1], [17], [ax_cxx_compile_alternatives="17 1z"], [$1], [20], [ax_cxx_compile_alternatives="20"], [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$2], [], [], [$2], [ext], [], [$2], [noext], [], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], [$3], [optional], [ax_cxx_compile_cxx$1_required=false], [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) AC_LANG_PUSH([C++])dnl ac_success=no m4_if([$2], [], [dnl AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, ax_cv_cxx_compile_cxx$1, [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [ax_cv_cxx_compile_cxx$1=yes], [ax_cv_cxx_compile_cxx$1=no])]) if test x$ax_cv_cxx_compile_cxx$1 = xyes; then ac_success=yes fi]) m4_if([$2], [noext], [], [dnl if test x$ac_success = xno; then for alternative in ${ax_cxx_compile_alternatives}; do switch="-std=gnu++${alternative}" cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, $cachevar, [ac_save_CXX="$CXX" CXX="$CXX $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], [eval $cachevar=yes], [eval $cachevar=no]) CXX="$ac_save_CXX"]) if eval test x\$$cachevar = xyes; then CXX="$CXX $switch" if test -n "$CXXCPP" ; then CXXCPP="$CXXCPP $switch" fi ac_success=yes break fi done fi]) m4_if([$2], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" 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 ) dnl Test body for checking C++17 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 ) dnl Test body for checking C++20 support m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 ) dnl Tests for new features in C++11 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ // If the compiler admits that it is not ready for C++11, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" // MSVC always sets __cplusplus to 199711L in older versions; newer versions // only set it correctly if /Zc:__cplusplus is specified as well as a // /std:c++NN switch: // https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ #elif __cplusplus < 201103L && !defined _MSC_VER #error "This is not a C++11 compiler" #else namespace cxx11 { namespace test_static_assert { template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; } namespace test_final_override { struct Base { virtual ~Base() {} virtual void f() {} }; struct Derived : public Base { virtual ~Derived() override {} virtual void f() override {} }; } namespace test_double_right_angle_brackets { template < typename T > struct check {}; typedef check single_type; typedef check> double_type; typedef check>> triple_type; typedef check>>> quadruple_type; } namespace test_decltype { int f() { int a = 1; decltype(a) b = 2; return a + b; } } namespace test_type_deduction { template < typename T1, typename T2 > struct is_same { static const bool value = false; }; template < typename T > struct is_same { static const bool value = true; }; template < typename T1, typename T2 > auto add(T1 a1, T2 a2) -> decltype(a1 + a2) { return a1 + a2; } int test(const int c, volatile int v) { static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == false, ""); auto ac = c; auto av = v; auto sumi = ac + av + 'x'; auto sumf = ac + av + 1.0; static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == true, ""); static_assert(is_same::value == false, ""); static_assert(is_same::value == true, ""); return (sumf > 0.0) ? sumi : add(c, v); } } namespace test_noexcept { int f() { return 0; } int g() noexcept { return 0; } static_assert(noexcept(f()) == false, ""); static_assert(noexcept(g()) == true, ""); } namespace test_constexpr { template < typename CharT > unsigned long constexpr strlen_c_r(const CharT *const s, const unsigned long acc) noexcept { return *s ? strlen_c_r(s + 1, acc + 1) : acc; } template < typename CharT > unsigned long constexpr strlen_c(const CharT *const s) noexcept { return strlen_c_r(s, 0UL); } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("1") == 1UL, ""); static_assert(strlen_c("example") == 7UL, ""); static_assert(strlen_c("another\0example") == 7UL, ""); } namespace test_rvalue_references { template < int N > struct answer { static constexpr int value = N; }; answer<1> f(int&) { return answer<1>(); } answer<2> f(const int&) { return answer<2>(); } answer<3> f(int&&) { return answer<3>(); } void test() { int i = 0; const int c = 0; static_assert(decltype(f(i))::value == 1, ""); static_assert(decltype(f(c))::value == 2, ""); static_assert(decltype(f(0))::value == 3, ""); } } namespace test_uniform_initialization { struct test { static const int zero {}; static const int one {1}; }; static_assert(test::zero == 0, ""); static_assert(test::one == 1, ""); } namespace test_lambdas { void test1() { auto lambda1 = [](){}; auto lambda2 = lambda1; lambda1(); lambda2(); } int test2() { auto a = [](int i, int j){ return i + j; }(1, 2); auto b = []() -> int { return '0'; }(); auto c = [=](){ return a + b; }(); auto d = [&](){ return c; }(); auto e = [a, &b](int x) mutable { const auto identity = [](int y){ return y; }; for (auto i = 0; i < a; ++i) a += b--; return x + identity(a + b); }(0); return a + b + c + d + e; } int test3() { const auto nullary = [](){ return 0; }; const auto unary = [](int x){ return x; }; using nullary_t = decltype(nullary); using unary_t = decltype(unary); const auto higher1st = [](nullary_t f){ return f(); }; const auto higher2nd = [unary](nullary_t f1){ return [unary, f1](unary_t f2){ return f2(unary(f1())); }; }; return higher1st(nullary) + higher2nd(nullary)(unary); } } namespace test_variadic_templates { template struct sum; template struct sum { static constexpr auto value = N0 + sum::value; }; template <> struct sum<> { static constexpr auto value = 0; }; static_assert(sum<>::value == 0, ""); static_assert(sum<1>::value == 1, ""); static_assert(sum<23>::value == 23, ""); static_assert(sum<1, 2>::value == 3, ""); static_assert(sum<5, 5, 11>::value == 21, ""); static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, ""); } // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function // because of this. namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } } // namespace cxx11 #endif // __cplusplus >= 201103L ]]) dnl Tests for new features in C++14 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[ // If the compiler admits that it is not ready for C++14, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201402L && !defined _MSC_VER #error "This is not a C++14 compiler" #else namespace cxx14 { namespace test_polymorphic_lambdas { int test() { const auto lambda = [](auto&&... args){ const auto istiny = [](auto x){ return (sizeof(x) == 1UL) ? 1 : 0; }; const int aretiny[] = { istiny(args)... }; return aretiny[0]; }; return lambda(1, 1L, 1.0f, '1'); } } namespace test_binary_literals { constexpr auto ivii = 0b0000000000101010; static_assert(ivii == 42, "wrong value"); } namespace test_generalized_constexpr { template < typename CharT > constexpr unsigned long strlen_c(const CharT *const s) noexcept { auto length = 0UL; for (auto p = s; *p; ++p) ++length; return length; } static_assert(strlen_c("") == 0UL, ""); static_assert(strlen_c("x") == 1UL, ""); static_assert(strlen_c("test") == 4UL, ""); static_assert(strlen_c("another\0test") == 7UL, ""); } namespace test_lambda_init_capture { int test() { auto x = 0; const auto lambda1 = [a = x](int b){ return a + b; }; const auto lambda2 = [a = lambda1(x)](){ return a; }; return lambda2(); } } namespace test_digit_separators { constexpr auto ten_million = 100'000'000; static_assert(ten_million == 100000000, ""); } namespace test_return_type_deduction { auto f(int& x) { return x; } decltype(auto) g(int& x) { return x; } template < typename T1, typename T2 > struct is_same { static constexpr auto value = false; }; template < typename T > struct is_same { static constexpr auto value = true; }; int test() { auto x = 0; static_assert(is_same::value, ""); static_assert(is_same::value, ""); return x; } } } // namespace cxx14 #endif // __cplusplus >= 201402L ]]) dnl Tests for new features in C++17 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_17], [[ // If the compiler admits that it is not ready for C++17, why torture it? // Hopefully, this will speed up the test. #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 201703L && !defined _MSC_VER #error "This is not a C++17 compiler" #else #include #include #include namespace cxx17 { namespace test_constexpr_lambdas { constexpr int foo = [](){return 42;}(); } namespace test::nested_namespace::definitions { } namespace test_fold_expression { template int multiply(Args... args) { return (args * ... * 1); } template bool all(Args... args) { return (args && ...); } } namespace test_extended_static_assert { static_assert (true); } namespace test_auto_brace_init_list { auto foo = {5}; auto bar {5}; static_assert(std::is_same, decltype(foo)>::value); static_assert(std::is_same::value); } namespace test_typename_in_template_template_parameter { template typename X> struct D; } namespace test_fallthrough_nodiscard_maybe_unused_attributes { int f1() { return 42; } [[nodiscard]] int f2() { [[maybe_unused]] auto unused = f1(); switch (f1()) { case 17: f1(); [[fallthrough]]; case 42: f1(); } return f1(); } } namespace test_extended_aggregate_initialization { struct base1 { int b1, b2 = 42; }; struct base2 { base2() { b3 = 42; } int b3; }; struct derived : base1, base2 { int d; }; derived d1 {{1, 2}, {}, 4}; // full initialization derived d2 {{}, {}, 4}; // value-initialized bases } namespace test_general_range_based_for_loop { struct iter { int i; int& operator* () { return i; } const int& operator* () const { return i; } iter& operator++() { ++i; return *this; } }; struct sentinel { int i; }; bool operator== (const iter& i, const sentinel& s) { return i.i == s.i; } bool operator!= (const iter& i, const sentinel& s) { return !(i == s); } struct range { iter begin() const { return {0}; } sentinel end() const { return {5}; } }; void f() { range r {}; for (auto i : r) { [[maybe_unused]] auto v = i; } } } namespace test_lambda_capture_asterisk_this_by_value { struct t { int i; int foo() { return [*this]() { return i; }(); } }; } namespace test_enum_class_construction { enum class byte : unsigned char {}; byte foo {42}; } namespace test_constexpr_if { template int f () { if constexpr(cond) { return 13; } else { return 42; } } } namespace test_selection_statement_with_initializer { int f() { return 13; } int f2() { if (auto i = f(); i > 0) { return 3; } switch (auto i = f(); i + 4) { case 17: return 2; default: return 1; } } } namespace test_template_argument_deduction_for_class_templates { template struct pair { pair (T1 p1, T2 p2) : m1 {p1}, m2 {p2} {} T1 m1; T2 m2; }; void f() { [[maybe_unused]] auto p = pair{13, 42u}; } } namespace test_non_type_auto_template_parameters { template struct B {}; B<5> b1; B<'a'> b2; } namespace test_structured_bindings { int arr[2] = { 1, 2 }; std::pair pr = { 1, 2 }; auto f1() -> int(&)[2] { return arr; } auto f2() -> std::pair& { return pr; } struct S { int x1 : 2; volatile double y1; }; S f3() { return {}; } auto [ x1, y1 ] = f1(); auto& [ xr1, yr1 ] = f1(); auto [ x2, y2 ] = f2(); auto& [ xr2, yr2 ] = f2(); const auto [ x3, y3 ] = f3(); } namespace test_exception_spec_type_system { struct Good {}; struct Bad {}; void g1() noexcept; void g2(); template Bad f(T*, T*); template Good f(T1*, T2*); static_assert (std::is_same_v); } namespace test_inline_variables { template void f(T) {} template inline T g(T) { return T{}; } template<> inline void f<>(int) {} template<> int g<>(int) { return 5; } } } // namespace cxx17 #endif // __cplusplus < 201703L && !defined _MSC_VER ]]) dnl Tests for new features in C++20 m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ #ifndef __cplusplus #error "This is not a C++ compiler" #elif __cplusplus < 202002L && !defined _MSC_VER #error "This is not a C++20 compiler" #else #include namespace cxx20 { // As C++20 supports feature test macros in the standard, there is no // immediate need to actually test for feature availability on the // Autoconf side. } // namespace cxx20 #endif // __cplusplus < 202002L && !defined _MSC_VER ]]) audiowmark-0.6.5/m4/ax_cxx_compile_stdcxx_14.m4000066400000000000000000000025131501136502400213500ustar00rootroot00000000000000# ============================================================================= # https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_14.html # ============================================================================= # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX_14([ext|noext], [mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the C++14 # standard; if necessary, add switches to CXX and CXXCPP to enable # support. # # This macro is a convenience alias for calling the AX_CXX_COMPILE_STDCXX # macro with the version set to C++14. The two optional arguments are # forwarded literally as the second and third argument respectively. # Please see the documentation for the AX_CXX_COMPILE_STDCXX macro for # more information. If you want to use this macro, you also need to # download the ax_cxx_compile_stdcxx.m4 file. # # LICENSE # # Copyright (c) 2015 Moritz Klammler # # 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 5 AX_REQUIRE_DEFINED([AX_CXX_COMPILE_STDCXX]) AC_DEFUN([AX_CXX_COMPILE_STDCXX_14], [AX_CXX_COMPILE_STDCXX([14], [$1], [$2])]) audiowmark-0.6.5/m4/ax_require_defined.m4000066400000000000000000000023021501136502400202630ustar00rootroot00000000000000# =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_require_defined.html # =========================================================================== # # SYNOPSIS # # AX_REQUIRE_DEFINED(MACRO) # # DESCRIPTION # # AX_REQUIRE_DEFINED is a simple helper for making sure other macros have # been defined and thus are available for use. This avoids random issues # where a macro isn't expanded. Instead the configure script emits a # non-fatal: # # ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found # # It's like AC_REQUIRE except it doesn't expand the required macro. # # Here's an example: # # AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) # # LICENSE # # Copyright (c) 2014 Mike Frysinger # # 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 2 AC_DEFUN([AX_REQUIRE_DEFINED], [dnl m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) ])dnl AX_REQUIRE_DEFINED audiowmark-0.6.5/m4/host-cpu-c-abi.m4000066400000000000000000000536401501136502400171670ustar00rootroot00000000000000# host-cpu-c-abi.m4 serial 13 dnl Copyright (C) 2002-2020 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible and Sam Steingold. dnl Sets the HOST_CPU variable to the canonical name of the CPU. dnl Sets the HOST_CPU_C_ABI variable to the canonical name of the CPU with its dnl C language ABI (application binary interface). dnl Also defines __${HOST_CPU}__ and __${HOST_CPU_C_ABI}__ as C macros in dnl config.h. dnl dnl This canonical name can be used to select a particular assembly language dnl source file that will interoperate with C code on the given host. dnl dnl For example: dnl * 'i386' and 'sparc' are different canonical names, because code for i386 dnl will not run on SPARC CPUs and vice versa. They have different dnl instruction sets. dnl * 'sparc' and 'sparc64' are different canonical names, because code for dnl 'sparc' and code for 'sparc64' cannot be linked together: 'sparc' code dnl contains 32-bit instructions, whereas 'sparc64' code contains 64-bit dnl instructions. A process on a SPARC CPU can be in 32-bit mode or in 64-bit dnl mode, but not both. dnl * 'mips' and 'mipsn32' are different canonical names, because they use dnl different argument passing and return conventions for C functions, and dnl although the instruction set of 'mips' is a large subset of the dnl instruction set of 'mipsn32'. dnl * 'mipsn32' and 'mips64' are different canonical names, because they use dnl different sizes for the C types like 'int' and 'void *', and although dnl the instruction sets of 'mipsn32' and 'mips64' are the same. dnl * The same canonical name is used for different endiannesses. You can dnl determine the endianness through preprocessor symbols: dnl - 'arm': test __ARMEL__. dnl - 'mips', 'mipsn32', 'mips64': test _MIPSEB vs. _MIPSEL. dnl - 'powerpc64': test _BIG_ENDIAN vs. _LITTLE_ENDIAN. dnl * The same name 'i386' is used for CPUs of type i386, i486, i586 dnl (Pentium), AMD K7, Pentium II, Pentium IV, etc., because dnl - Instructions that do not exist on all of these CPUs (cmpxchg, dnl MMX, SSE, SSE2, 3DNow! etc.) are not frequently used. If your dnl assembly language source files use such instructions, you will dnl need to make the distinction. dnl - Speed of execution of the common instruction set is reasonable across dnl the entire family of CPUs. If you have assembly language source files dnl that are optimized for particular CPU types (like GNU gmp has), you dnl will need to make the distinction. dnl See . AC_DEFUN([gl_HOST_CPU_C_ABI], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([gl_C_ASM]) AC_CACHE_CHECK([host CPU and C ABI], [gl_cv_host_cpu_c_abi], [case "$host_cpu" in changequote(,)dnl i[34567]86 ) changequote([,])dnl gl_cv_host_cpu_c_abi=i386 ;; x86_64 ) # On x86_64 systems, the C compiler may be generating code in one of # these ABIs: # - 64-bit instruction set, 64-bit pointers, 64-bit 'long': x86_64. # - 64-bit instruction set, 64-bit pointers, 32-bit 'long': x86_64 # with native Windows (mingw, MSVC). # - 64-bit instruction set, 32-bit pointers, 32-bit 'long': x86_64-x32. # - 32-bit instruction set, 32-bit pointers, 32-bit 'long': i386. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if (defined __x86_64__ || defined __amd64__ \ || defined _M_X64 || defined _M_AMD64) int ok; #else error fail #endif ]])], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __ILP32__ || defined _ILP32 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=x86_64-x32], [gl_cv_host_cpu_c_abi=x86_64])], [gl_cv_host_cpu_c_abi=i386]) ;; changequote(,)dnl alphaev[4-8] | alphaev56 | alphapca5[67] | alphaev6[78] ) changequote([,])dnl gl_cv_host_cpu_c_abi=alpha ;; arm* | aarch64 ) # Assume arm with EABI. # On arm64 systems, the C compiler may be generating code in one of # these ABIs: # - aarch64 instruction set, 64-bit pointers, 64-bit 'long': arm64. # - aarch64 instruction set, 32-bit pointers, 32-bit 'long': arm64-ilp32. # - 32-bit instruction set, 32-bit pointers, 32-bit 'long': arm or armhf. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef __aarch64__ int ok; #else error fail #endif ]])], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __ILP32__ || defined _ILP32 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=arm64-ilp32], [gl_cv_host_cpu_c_abi=arm64])], [# Don't distinguish little-endian and big-endian arm, since they # don't require different machine code for simple operations and # since the user can distinguish them through the preprocessor # defines __ARMEL__ vs. __ARMEB__. # But distinguish arm which passes floating-point arguments and # return values in integer registers (r0, r1, ...) - this is # gcc -mfloat-abi=soft or gcc -mfloat-abi=softfp - from arm which # passes them in float registers (s0, s1, ...) and double registers # (d0, d1, ...) - this is gcc -mfloat-abi=hard. GCC 4.6 or newer # sets the preprocessor defines __ARM_PCS (for the first case) and # __ARM_PCS_VFP (for the second case), but older GCC does not. echo 'double ddd; void func (double dd) { ddd = dd; }' > conftest.c # Look for a reference to the register d0 in the .s file. AC_TRY_COMMAND(${CC-cc} $CFLAGS $CPPFLAGS $gl_c_asm_opt conftest.c) >/dev/null 2>&1 if LC_ALL=C grep 'd0,' conftest.$gl_asmext >/dev/null; then gl_cv_host_cpu_c_abi=armhf else gl_cv_host_cpu_c_abi=arm fi rm -f conftest* ]) ;; hppa1.0 | hppa1.1 | hppa2.0* | hppa64 ) # On hppa, the C compiler may be generating 32-bit code or 64-bit # code. In the latter case, it defines _LP64 and __LP64__. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef __LP64__ int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=hppa64], [gl_cv_host_cpu_c_abi=hppa]) ;; ia64* ) # On ia64 on HP-UX, the C compiler may be generating 64-bit code or # 32-bit code. In the latter case, it defines _ILP32. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#ifdef _ILP32 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=ia64-ilp32], [gl_cv_host_cpu_c_abi=ia64]) ;; mips* ) # We should also check for (_MIPS_SZPTR == 64), but gcc keeps this # at 32. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined _MIPS_SZLONG && (_MIPS_SZLONG == 64) int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=mips64], [# In the n32 ABI, _ABIN32 is defined, _ABIO32 is not defined (but # may later get defined by ), and _MIPS_SIM == _ABIN32. # In the 32 ABI, _ABIO32 is defined, _ABIN32 is not defined (but # may later get defined by ), and _MIPS_SIM == _ABIO32. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if (_MIPS_SIM == _ABIN32) int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=mipsn32], [gl_cv_host_cpu_c_abi=mips])]) ;; powerpc* ) # Different ABIs are in use on AIX vs. Mac OS X vs. Linux,*BSD. # No need to distinguish them here; the caller may distinguish # them based on the OS. # On powerpc64 systems, the C compiler may still be generating # 32-bit code. And on powerpc-ibm-aix systems, the C compiler may # be generating 64-bit code. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __powerpc64__ || defined _ARCH_PPC64 int ok; #else error fail #endif ]])], [# On powerpc64, there are two ABIs on Linux: The AIX compatible # one and the ELFv2 one. The latter defines _CALL_ELF=2. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined _CALL_ELF && _CALL_ELF == 2 int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=powerpc64-elfv2], [gl_cv_host_cpu_c_abi=powerpc64]) ], [gl_cv_host_cpu_c_abi=powerpc]) ;; rs6000 ) gl_cv_host_cpu_c_abi=powerpc ;; riscv32 | riscv64 ) # There are 2 architectures (with variants): rv32* and rv64*. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if __riscv_xlen == 64 int ok; #else error fail #endif ]])], [cpu=riscv64], [cpu=riscv32]) # There are 6 ABIs: ilp32, ilp32f, ilp32d, lp64, lp64f, lp64d. # Size of 'long' and 'void *': AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __LP64__ int ok; #else error fail #endif ]])], [main_abi=lp64], [main_abi=ilp32]) # Float ABIs: # __riscv_float_abi_double: # 'float' and 'double' are passed in floating-point registers. # __riscv_float_abi_single: # 'float' are passed in floating-point registers. # __riscv_float_abi_soft: # No values are passed in floating-point registers. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __riscv_float_abi_double int ok; #else error fail #endif ]])], [float_abi=d], [AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __riscv_float_abi_single int ok; #else error fail #endif ]])], [float_abi=f], [float_abi='']) ]) gl_cv_host_cpu_c_abi="${cpu}-${main_abi}${float_abi}" ;; s390* ) # On s390x, the C compiler may be generating 64-bit (= s390x) code # or 31-bit (= s390) code. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __LP64__ || defined __s390x__ int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=s390x], [gl_cv_host_cpu_c_abi=s390]) ;; sparc | sparc64 ) # UltraSPARCs running Linux have `uname -m` = "sparc64", but the # C compiler still generates 32-bit code. AC_COMPILE_IFELSE( [AC_LANG_SOURCE( [[#if defined __sparcv9 || defined __arch64__ int ok; #else error fail #endif ]])], [gl_cv_host_cpu_c_abi=sparc64], [gl_cv_host_cpu_c_abi=sparc]) ;; *) gl_cv_host_cpu_c_abi="$host_cpu" ;; esac ]) dnl In most cases, $HOST_CPU and $HOST_CPU_C_ABI are the same. HOST_CPU=`echo "$gl_cv_host_cpu_c_abi" | sed -e 's/-.*//'` HOST_CPU_C_ABI="$gl_cv_host_cpu_c_abi" AC_SUBST([HOST_CPU]) AC_SUBST([HOST_CPU_C_ABI]) # This was # AC_DEFINE_UNQUOTED([__${HOST_CPU}__]) # AC_DEFINE_UNQUOTED([__${HOST_CPU_C_ABI}__]) # earlier, but KAI C++ 3.2d doesn't like this. sed -e 's/-/_/g' >> confdefs.h <&1 /dev/null 2>&1 \ && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \ || PATH_SEPARATOR=';' } fi if test -n "$LD"; then AC_MSG_CHECKING([for ld]) elif test "$GCC" = yes; then AC_MSG_CHECKING([for ld used by $CC]) elif test "$with_gnu_ld" = yes; then AC_MSG_CHECKING([for GNU ld]) else AC_MSG_CHECKING([for non-GNU ld]) fi if test -n "$LD"; then # Let the user override the test with a path. : else AC_CACHE_VAL([acl_cv_path_LD], [ acl_cv_path_LD= # Final result of this test ac_prog=ld # Program to search in $PATH if test "$GCC" = yes; then # Check if gcc -print-prog-name=ld gives a path. case $host in *-*-mingw*) # gcc leaves a trailing carriage return which upsets mingw acl_output=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; *) acl_output=`($CC -print-prog-name=ld) 2>&5` ;; esac case $acl_output in # Accept absolute paths. [[\\/]]* | ?:[[\\/]]*) re_direlt='/[[^/]][[^/]]*/\.\./' # Canonicalize the pathname of ld acl_output=`echo "$acl_output" | sed 's%\\\\%/%g'` while echo "$acl_output" | grep "$re_direlt" > /dev/null 2>&1; do acl_output=`echo $acl_output | sed "s%$re_direlt%/%"` done # Got the pathname. No search in PATH is needed. acl_cv_path_LD="$acl_output" ac_prog= ;; "") # If it fails, then pretend we aren't using GCC. ;; *) # If it is relative, then search for the first ld in PATH. with_gnu_ld=unknown ;; esac fi if test -n "$ac_prog"; then # Search for $ac_prog in $PATH. acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR for ac_dir in $PATH; do IFS="$acl_save_ifs" test -z "$ac_dir" && ac_dir=. if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then acl_cv_path_LD="$ac_dir/$ac_prog" # Check to see if the program is GNU ld. I'd rather use --version, # but apparently some variants of GNU ld only accept -v. # Break only if it was the GNU/non-GNU ld that we prefer. case `"$acl_cv_path_LD" -v 2>&1 conftest.sh . ./conftest.sh rm -f ./conftest.sh acl_cv_rpath=done ]) wl="$acl_cv_wl" acl_libext="$acl_cv_libext" acl_shlibext="$acl_cv_shlibext" acl_libname_spec="$acl_cv_libname_spec" acl_library_names_spec="$acl_cv_library_names_spec" acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" acl_hardcode_direct="$acl_cv_hardcode_direct" acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" dnl Determine whether the user wants rpath handling at all. AC_ARG_ENABLE([rpath], [ --disable-rpath do not hardcode runtime library paths], :, enable_rpath=yes) ]) dnl AC_LIB_FROMPACKAGE(name, package) dnl declares that libname comes from the given package. The configure file dnl will then not have a --with-libname-prefix option but a dnl --with-package-prefix option. Several libraries can come from the same dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar dnl macro call that searches for libname. AC_DEFUN([AC_LIB_FROMPACKAGE], [ pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) define([acl_frompackage_]NAME, [$2]) popdef([NAME]) pushdef([PACK],[$2]) pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) define([acl_libsinpackage_]PACKUP, m4_ifdef([acl_libsinpackage_]PACKUP, [m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1]) popdef([PACKUP]) popdef([PACK]) ]) dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and dnl the libraries corresponding to explicit and implicit dependencies. dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. AC_DEFUN([AC_LIB_LINKFLAGS_BODY], [ AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])]) pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-], [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])]) pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" eval additional_libdir2=\"$exec_prefix/$acl_libdirstem2\" eval additional_libdir3=\"$exec_prefix/$acl_libdirstem3\" ]) AC_ARG_WITH(PACK[-prefix], [[ --with-]]PACK[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib --without-]]PACK[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" eval additional_libdir2=\"$exec_prefix/$acl_libdirstem2\" eval additional_libdir3=\"$exec_prefix/$acl_libdirstem3\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" additional_libdir2="$withval/$acl_libdirstem2" additional_libdir3="$withval/$acl_libdirstem3" fi fi ]) if test "X$additional_libdir2" = "X$additional_libdir"; then additional_libdir2= fi if test "X$additional_libdir3" = "X$additional_libdir"; then additional_libdir3= fi dnl Search the library and its dependencies in $additional_libdir and dnl $LDFLAGS. Using breadth-first-seach. LIB[]NAME= LTLIB[]NAME= INC[]NAME= LIB[]NAME[]_PREFIX= dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been dnl computed. So it has to be reset here. HAVE_LIB[]NAME= rpathdirs= ltrpathdirs= names_already_handled= names_next_round='$1 $2' while test -n "$names_next_round"; do names_this_round="$names_next_round" names_next_round= for name in $names_this_round; do already_handled= for n in $names_already_handled; do if test "$n" = "$name"; then already_handled=yes break fi done if test -z "$already_handled"; then names_already_handled="$names_already_handled $name" dnl See if it was already located by an earlier AC_LIB_LINKFLAGS dnl or AC_LIB_HAVE_LINKFLAGS call. uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'` eval value=\"\$HAVE_LIB$uppername\" if test -n "$value"; then if test "$value" = yes; then eval value=\"\$LIB$uppername\" test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" eval value=\"\$LTLIB$uppername\" test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" else dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined dnl that this library doesn't exist. So just drop it. : fi else dnl Search the library lib$name in $additional_libdir and $LDFLAGS dnl and the already constructed $LIBNAME/$LTLIBNAME. found_dir= found_la= found_so= found_a= eval libname=\"$acl_libname_spec\" # typically: libname=lib$name if test -n "$acl_shlibext"; then shrext=".$acl_shlibext" # typically: shrext=.so else shrext= fi if test $use_additional = yes; then for additional_libdir_variable in additional_libdir additional_libdir2 additional_libdir3; do if test "X$found_dir" = "X"; then eval dir=\$$additional_libdir_variable if test -n "$dir"; then dnl The same code as in the loop below: dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext" && acl_is_expected_elfclass < "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver" && acl_is_expected_elfclass < "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f" && acl_is_expected_elfclass < "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext" && ${AR-ar} -p "$dir/$libname.$acl_libext" | acl_is_expected_elfclass; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi fi fi done fi if test "X$found_dir" = "X"; then for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) case "$x" in -L*) dir=`echo "X$x" | sed -e 's/^X-L//'` dnl First look for a shared library. if test -n "$acl_shlibext"; then if test -f "$dir/$libname$shrext" && acl_is_expected_elfclass < "$dir/$libname$shrext"; then found_dir="$dir" found_so="$dir/$libname$shrext" else if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then ver=`(cd "$dir" && \ for f in "$libname$shrext".*; do echo "$f"; done \ | sed -e "s,^$libname$shrext\\\\.,," \ | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ | sed 1q ) 2>/dev/null` if test -n "$ver" && test -f "$dir/$libname$shrext.$ver" && acl_is_expected_elfclass < "$dir/$libname$shrext.$ver"; then found_dir="$dir" found_so="$dir/$libname$shrext.$ver" fi else eval library_names=\"$acl_library_names_spec\" for f in $library_names; do if test -f "$dir/$f" && acl_is_expected_elfclass < "$dir/$f"; then found_dir="$dir" found_so="$dir/$f" break fi done fi fi fi dnl Then look for a static library. if test "X$found_dir" = "X"; then if test -f "$dir/$libname.$acl_libext" && ${AR-ar} -p "$dir/$libname.$acl_libext" | acl_is_expected_elfclass; then found_dir="$dir" found_a="$dir/$libname.$acl_libext" fi fi if test "X$found_dir" != "X"; then if test -f "$dir/$libname.la"; then found_la="$dir/$libname.la" fi fi ;; esac if test "X$found_dir" != "X"; then break fi done fi if test "X$found_dir" != "X"; then dnl Found the library. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" if test "X$found_so" != "X"; then dnl Linking with a shared library. We attempt to hardcode its dnl directory into the executable's runpath, unless it's the dnl standard /usr/lib. if test "$enable_rpath" = no \ || test "X$found_dir" = "X/usr/$acl_libdirstem" \ || test "X$found_dir" = "X/usr/$acl_libdirstem2" \ || test "X$found_dir" = "X/usr/$acl_libdirstem3"; then dnl No hardcoding is needed. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl Use an explicit option to hardcode DIR into the resulting dnl binary. dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $found_dir" fi dnl The hardcoding into $LIBNAME is system dependent. if test "$acl_hardcode_direct" = yes; then dnl Using DIR/libNAME.so during linking hardcodes DIR into the dnl resulting binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode DIR into the resulting dnl binary. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $found_dir" fi else dnl Rely on "-L$found_dir". dnl But don't add it if it's already contained in the LDFLAGS dnl or the already constructed $LIBNAME haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$found_dir"; then haveit=yes break fi done if test -z "$haveit"; then LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" fi if test "$acl_hardcode_minus_L" != no; then dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" else dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH dnl here, because this doesn't fit in flags passed to the dnl compiler. So give up. No hardcoding. This affects only dnl very old systems. dnl FIXME: Not sure whether we should use dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" dnl here. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" fi fi fi fi else if test "X$found_a" != "X"; then dnl Linking with a static library. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" else dnl We shouldn't come here, but anyway it's good to have a dnl fallback. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" fi fi dnl Assume the include files are nearby. additional_includedir= case "$found_dir" in */$acl_libdirstem | */$acl_libdirstem/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; */$acl_libdirstem2 | */$acl_libdirstem2/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; */$acl_libdirstem3 | */$acl_libdirstem3/) basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem3/"'*$,,'` if test "$name" = '$1'; then LIB[]NAME[]_PREFIX="$basedir" fi additional_includedir="$basedir/include" ;; esac if test "X$additional_includedir" != "X"; then dnl Potentially add $additional_includedir to $INCNAME. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's /usr/local/include and we are using GCC on Linux, dnl 3. if it's already present in $CPPFLAGS or the already dnl constructed $INCNAME, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then for x in $CPPFLAGS $INC[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $INCNAME. INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" fi fi fi fi fi dnl Look for dependencies. if test -n "$found_la"; then dnl Read the .la file. It defines the variables dnl dlname, library_names, old_library, dependency_libs, current, dnl age, revision, installed, dlopen, dlpreopen, libdir. save_libdir="$libdir" case "$found_la" in */* | *\\*) . "$found_la" ;; *) . "./$found_la" ;; esac libdir="$save_libdir" dnl We use only dependency_libs. for dep in $dependency_libs; do case "$dep" in -L*) dependency_libdir=`echo "X$dep" | sed -e 's/^X-L//'` dnl Potentially add $dependency_libdir to $LIBNAME and $LTLIBNAME. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's /usr/local/lib and we are using GCC on Linux, dnl 3. if it's already present in $LDFLAGS or the already dnl constructed $LIBNAME, dnl 4. if it doesn't exist as a directory. if test "X$dependency_libdir" != "X/usr/$acl_libdirstem" \ && test "X$dependency_libdir" != "X/usr/$acl_libdirstem2" \ && test "X$dependency_libdir" != "X/usr/$acl_libdirstem3"; then haveit= if test "X$dependency_libdir" = "X/usr/local/$acl_libdirstem" \ || test "X$dependency_libdir" = "X/usr/local/$acl_libdirstem2" \ || test "X$dependency_libdir" = "X/usr/local/$acl_libdirstem3"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then haveit= for x in $LDFLAGS $LIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$dependency_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$dependency_libdir"; then dnl Really add $dependency_libdir to $LIBNAME. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$dependency_libdir" fi fi haveit= for x in $LDFLAGS $LTLIB[]NAME; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$dependency_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test -d "$dependency_libdir"; then dnl Really add $dependency_libdir to $LTLIBNAME. LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$dependency_libdir" fi fi fi fi ;; -R*) dir=`echo "X$dep" | sed -e 's/^X-R//'` if test "$enable_rpath" != no; then dnl Potentially add DIR to rpathdirs. dnl The rpathdirs will be appended to $LIBNAME at the end. haveit= for x in $rpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then rpathdirs="$rpathdirs $dir" fi dnl Potentially add DIR to ltrpathdirs. dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. haveit= for x in $ltrpathdirs; do if test "X$x" = "X$dir"; then haveit=yes break fi done if test -z "$haveit"; then ltrpathdirs="$ltrpathdirs $dir" fi fi ;; -l*) dnl Handle this in the next round. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` ;; *.la) dnl Handle this in the next round. Throw away the .la's dnl directory; it is already contained in a preceding -L dnl option. names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` ;; *) dnl Most likely an immediate library name. LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" ;; esac done fi else dnl Didn't find the library; assume it is in the system directories dnl known to the linker and runtime loader. (All the system dnl directories known to the linker should also be known to the dnl runtime loader, otherwise the system is severely misconfigured.) LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" fi fi fi done done if test "X$rpathdirs" != "X"; then if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user must dnl pass all path elements in one option. We can arrange that for a dnl single library, but not when more than one $LIBNAMEs are used. alldirs= for found_dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" done dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" else dnl The -rpath options are cumulative. for found_dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$found_dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" done fi fi if test "X$ltrpathdirs" != "X"; then dnl When using libtool, the option that works for both libraries and dnl executables is -R. The -R options are cumulative. for found_dir in $ltrpathdirs; do LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" done fi popdef([PACKLIBS]) popdef([PACKUP]) popdef([PACK]) popdef([NAME]) ]) dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, dnl unless already present in VAR. dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes dnl contains two or three consecutive elements that belong together. AC_DEFUN([AC_LIB_APPENDTOVAR], [ for element in [$2]; do haveit= for x in $[$1]; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X$element"; then haveit=yes break fi done if test -z "$haveit"; then [$1]="${[$1]}${[$1]:+ }$element" fi done ]) dnl For those cases where a variable contains several -L and -l options dnl referring to unknown libraries and directories, this macro determines the dnl necessary additional linker options for the runtime path. dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) dnl sets LDADDVAR to linker options needed together with LIBSVALUE. dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, dnl otherwise linking without libtool is assumed. AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], [ AC_REQUIRE([AC_LIB_RPATH]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) $1= if test "$enable_rpath" != no; then if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then dnl Use an explicit option to hardcode directories into the resulting dnl binary. rpathdirs= next= for opt in $2; do if test -n "$next"; then dir="$next" dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem" \ && test "X$dir" != "X/usr/$acl_libdirstem2" \ && test "X$dir" != "X/usr/$acl_libdirstem3"; then rpathdirs="$rpathdirs $dir" fi next= else case $opt in -L) next=yes ;; -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` dnl No need to hardcode the standard /usr/lib. if test "X$dir" != "X/usr/$acl_libdirstem" \ && test "X$dir" != "X/usr/$acl_libdirstem2" \ && test "X$dir" != "X/usr/$acl_libdirstem3"; then rpathdirs="$rpathdirs $dir" fi next= ;; *) next= ;; esac fi done if test "X$rpathdirs" != "X"; then if test -n ""$3""; then dnl libtool is used for linking. Use -R options. for dir in $rpathdirs; do $1="${$1}${$1:+ }-R$dir" done else dnl The linker is used for linking directly. if test -n "$acl_hardcode_libdir_separator"; then dnl Weird platform: only the last -rpath option counts, the user dnl must pass all path elements in one option. alldirs= for dir in $rpathdirs; do alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" done acl_save_libdir="$libdir" libdir="$alldirs" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="$flag" else dnl The -rpath options are cumulative. for dir in $rpathdirs; do acl_save_libdir="$libdir" libdir="$dir" eval flag=\"$acl_hardcode_libdir_flag_spec\" libdir="$acl_save_libdir" $1="${$1}${$1:+ }$flag" done fi fi fi fi fi AC_SUBST([$1]) ]) audiowmark-0.6.5/m4/lib-prefix.m4000066400000000000000000000272501501136502400165130ustar00rootroot00000000000000# lib-prefix.m4 serial 17 dnl Copyright (C) 2001-2005, 2008-2020 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, dnl with or without modifications, as long as this notice is preserved. dnl From Bruno Haible. dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed dnl to access previously installed libraries. The basic assumption is that dnl a user will want packages to use other packages he previously installed dnl with the same --prefix option. dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate dnl libraries, but is otherwise very convenient. AC_DEFUN([AC_LIB_PREFIX], [ AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) AC_REQUIRE([AC_PROG_CC]) AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) dnl By default, look in $includedir and $libdir. use_additional=yes AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) AC_ARG_WITH([lib-prefix], [[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib --without-lib-prefix don't search for libraries in includedir and libdir]], [ if test "X$withval" = "Xno"; then use_additional=no else if test "X$withval" = "X"; then AC_LIB_WITH_FINAL_PREFIX([ eval additional_includedir=\"$includedir\" eval additional_libdir=\"$libdir\" ]) else additional_includedir="$withval/include" additional_libdir="$withval/$acl_libdirstem" fi fi ]) if test $use_additional = yes; then dnl Potentially add $additional_includedir to $CPPFLAGS. dnl But don't add it dnl 1. if it's the standard /usr/include, dnl 2. if it's already present in $CPPFLAGS, dnl 3. if it's /usr/local/include and we are using GCC on Linux, dnl 4. if it doesn't exist as a directory. if test "X$additional_includedir" != "X/usr/include"; then haveit= for x in $CPPFLAGS; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-I$additional_includedir"; then haveit=yes break fi done if test -z "$haveit"; then if test "X$additional_includedir" = "X/usr/local/include"; then if test -n "$GCC"; then case $host_os in linux* | gnu* | k*bsd*-gnu) haveit=yes;; esac fi fi if test -z "$haveit"; then if test -d "$additional_includedir"; then dnl Really add $additional_includedir to $CPPFLAGS. CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" fi fi fi fi dnl Potentially add $additional_libdir to $LDFLAGS. dnl But don't add it dnl 1. if it's the standard /usr/lib, dnl 2. if it's already present in $LDFLAGS, dnl 3. if it's /usr/local/lib and we are using GCC on Linux, dnl 4. if it doesn't exist as a directory. if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then haveit= for x in $LDFLAGS; do AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) if test "X$x" = "X-L$additional_libdir"; then haveit=yes break fi done if test -z "$haveit"; then if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then if test -n "$GCC"; then case $host_os in linux*) haveit=yes;; esac fi fi if test -z "$haveit"; then if test -d "$additional_libdir"; then dnl Really add $additional_libdir to $LDFLAGS. LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" fi fi fi fi fi ]) dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, dnl acl_final_exec_prefix, containing the values to which $prefix and dnl $exec_prefix will expand at the end of the configure script. AC_DEFUN([AC_LIB_PREPARE_PREFIX], [ dnl Unfortunately, prefix and exec_prefix get only finally determined dnl at the end of configure. if test "X$prefix" = "XNONE"; then acl_final_prefix="$ac_default_prefix" else acl_final_prefix="$prefix" fi if test "X$exec_prefix" = "XNONE"; then acl_final_exec_prefix='${prefix}' else acl_final_exec_prefix="$exec_prefix" fi acl_save_prefix="$prefix" prefix="$acl_final_prefix" eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" prefix="$acl_save_prefix" ]) dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the dnl variables prefix and exec_prefix bound to the values they will have dnl at the end of the configure script. AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], [ acl_save_prefix="$prefix" prefix="$acl_final_prefix" acl_save_exec_prefix="$exec_prefix" exec_prefix="$acl_final_exec_prefix" $1 exec_prefix="$acl_save_exec_prefix" prefix="$acl_save_prefix" ]) dnl AC_LIB_PREPARE_MULTILIB creates dnl - a function acl_is_expected_elfclass, that tests whether standard input dn; has a 32-bit or 64-bit ELF header, depending on the host CPU ABI, dnl - 3 variables acl_libdirstem, acl_libdirstem2, acl_libdirstem3, containing dnl the basename of the libdir to try in turn, either "lib" or "lib64" or dnl "lib/64" or "lib32" or "lib/sparcv9" or "lib/amd64" or similar. AC_DEFUN([AC_LIB_PREPARE_MULTILIB], [ dnl There is no formal standard regarding lib, lib32, and lib64. dnl On most glibc systems, the current practice is that on a system supporting dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. However, on dnl Arch Linux based distributions, it's the opposite: 32-bit libraries go dnl under $prefix/lib32 and 64-bit libraries go under $prefix/lib. dnl We determine the compiler's default mode by looking at the compiler's dnl library search path. If at least one of its elements ends in /lib64 or dnl points to a directory whose absolute pathname ends in /lib64, we use that dnl for 64-bit ABIs. Similarly for 32-bit ABIs. Otherwise we use the default, dnl namely "lib". dnl On Solaris systems, the current practice is that on a system supporting dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib. AC_REQUIRE([AC_CANONICAL_HOST]) AC_REQUIRE([gl_HOST_CPU_C_ABI_32BIT]) AC_CACHE_CHECK([for ELF binary format], [gl_cv_elf], [AC_EGREP_CPP([Extensible Linking Format], [#ifdef __ELF__ Extensible Linking Format #endif ], [gl_cv_elf=yes], [gl_cv_elf=no]) ]) if test $gl_cv_elf; then # Extract the ELF class of a file (5th byte) in decimal. # Cf. https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header if od -A x < /dev/null >/dev/null 2>/dev/null; then # Use POSIX od. func_elfclass () { od -A n -t d1 -j 4 -N 1 } else # Use BSD hexdump. func_elfclass () { dd bs=1 count=1 skip=4 2>/dev/null | hexdump -e '1/1 "%3d "' echo } fi changequote(,)dnl case $HOST_CPU_C_ABI_32BIT in yes) # 32-bit ABI. acl_is_expected_elfclass () { test "`func_elfclass | sed -e 's/[ ]//g'`" = 1 } ;; no) # 64-bit ABI. acl_is_expected_elfclass () { test "`func_elfclass | sed -e 's/[ ]//g'`" = 2 } ;; *) # Unknown. acl_is_expected_elfclass () { : } ;; esac changequote([,])dnl else acl_is_expected_elfclass () { : } fi dnl Allow the user to override the result by setting acl_cv_libdirstems. AC_CACHE_CHECK([for the common suffixes of directories in the library search path], [acl_cv_libdirstems], [dnl Try 'lib' first, because that's the default for libdir in GNU, see dnl . acl_libdirstem=lib acl_libdirstem2= acl_libdirstem3= case "$host_os" in solaris*) dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment dnl . dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link." dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the dnl symlink is missing, so we set acl_libdirstem2 too. if test $HOST_CPU_C_ABI_32BIT = no; then acl_libdirstem2=lib/64 case "$host_cpu" in sparc*) acl_libdirstem3=lib/sparcv9 ;; i*86 | x86_64) acl_libdirstem3=lib/amd64 ;; esac fi ;; *) dnl If $CC generates code for a 32-bit ABI, the libraries are dnl surely under $prefix/lib or $prefix/lib32, not $prefix/lib64. dnl Similarly, if $CC generates code for a 64-bit ABI, the libraries dnl are surely under $prefix/lib or $prefix/lib64, not $prefix/lib32. dnl Find the compiler's search path. However, non-system compilers dnl sometimes have odd library search paths. But we can't simply invoke dnl '/usr/bin/gcc -print-search-dirs' because that would not take into dnl account the -m32/-m31 or -m64 options from the $CC or $CFLAGS. searchpath=`(LC_ALL=C $CC $CPPFLAGS $CFLAGS -print-search-dirs) 2>/dev/null \ | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` if test $HOST_CPU_C_ABI_32BIT != no; then # 32-bit or unknown ABI. if test -d /usr/lib32; then acl_libdirstem2=lib32 fi fi if test $HOST_CPU_C_ABI_32BIT != yes; then # 64-bit or unknown ABI. if test -d /usr/lib64; then acl_libdirstem3=lib64 fi fi if test -n "$searchpath"; then acl_save_IFS="${IFS= }"; IFS=":" for searchdir in $searchpath; do if test -d "$searchdir"; then case "$searchdir" in */lib32/ | */lib32 ) acl_libdirstem2=lib32 ;; */lib64/ | */lib64 ) acl_libdirstem3=lib64 ;; */../ | */.. ) # Better ignore directories of this form. They are misleading. ;; *) searchdir=`cd "$searchdir" && pwd` case "$searchdir" in */lib32 ) acl_libdirstem2=lib32 ;; */lib64 ) acl_libdirstem3=lib64 ;; esac ;; esac fi done IFS="$acl_save_IFS" if test $HOST_CPU_C_ABI_32BIT = yes; then # 32-bit ABI. acl_libdirstem3= fi if test $HOST_CPU_C_ABI_32BIT = no; then # 64-bit ABI. acl_libdirstem2= fi fi ;; esac test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" test -n "$acl_libdirstem3" || acl_libdirstem3="$acl_libdirstem" acl_cv_libdirstems="$acl_libdirstem,$acl_libdirstem2,$acl_libdirstem3" ]) dnl Decompose acl_cv_libdirstems into acl_libdirstem, acl_libdirstem2, and dnl acl_libdirstem3. changequote(,)dnl acl_libdirstem=`echo "$acl_cv_libdirstems" | sed -e 's/,.*//'` acl_libdirstem2=`echo "$acl_cv_libdirstems" | sed -e 's/^[^,]*,//' -e 's/,.*//'` acl_libdirstem3=`echo "$acl_cv_libdirstems" | sed -e 's/^[^,]*,[^,]*,//' -e 's/,.*//'` changequote([,])dnl ]) audiowmark-0.6.5/m4/libgcrypt.m4000066400000000000000000000141041501136502400164430ustar00rootroot00000000000000# libgcrypt.m4 - Autoconf macros to detect libgcrypt # Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018, 2020 g10 Code GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # This file is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # Last-changed: 2022-11-01 dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION, dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS. dnl MINIMUM-VERSION is a string with the version number optionally prefixed dnl with the API version to also check the API compatibility. Example: dnl a MINIMUM-VERSION of 1:1.2.5 won't pass the test unless the installed dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using dnl this features allows to prevent build against newer versions of libgcrypt dnl with a changed API. dnl dnl If a prefix option is not used, the config script is first dnl searched in $SYSROOT/bin and then along $PATH. If the used dnl config script does not match the host specification the script dnl is added to the gpg_config_script_warn variable. dnl AC_DEFUN([AM_PATH_LIBGCRYPT], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_ARG_WITH(libgcrypt-prefix, AS_HELP_STRING([--with-libgcrypt-prefix=PFX], [prefix where LIBGCRYPT is installed (optional)]), libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="") if test x"${LIBGCRYPT_CONFIG}" = x ; then if test x"${libgcrypt_config_prefix}" != x ; then LIBGCRYPT_CONFIG="${libgcrypt_config_prefix}/bin/libgcrypt-config" fi fi use_gpgrt_config="" if test x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then if $GPGRT_CONFIG libgcrypt --exists; then LIBGCRYPT_CONFIG="$GPGRT_CONFIG libgcrypt" AC_MSG_NOTICE([Use gpgrt-config as libgcrypt-config]) use_gpgrt_config=yes fi fi if test -z "$use_gpgrt_config"; then if test x"${LIBGCRYPT_CONFIG}" = x ; then case "${SYSROOT}" in /*) if test -x "${SYSROOT}/bin/libgcrypt-config" ; then LIBGCRYPT_CONFIG="${SYSROOT}/bin/libgcrypt-config" fi ;; '') ;; *) AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.]) ;; esac fi AC_PATH_PROG(LIBGCRYPT_CONFIG, libgcrypt-config, no) fi tmp=ifelse([$1], ,1:1.2.0,$1) if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then req_libgcrypt_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'` min_libgcrypt_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'` else req_libgcrypt_api=0 min_libgcrypt_version="$tmp" fi AC_MSG_CHECKING(for LIBGCRYPT - version >= $min_libgcrypt_version) ok=no if test "$LIBGCRYPT_CONFIG" != "no" ; then req_major=`echo $min_libgcrypt_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` req_minor=`echo $min_libgcrypt_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` req_micro=`echo $min_libgcrypt_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` if test -z "$use_gpgrt_config"; then libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version` else libgcrypt_config_version=`$LIBGCRYPT_CONFIG --modversion` fi major=`echo $libgcrypt_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` minor=`echo $libgcrypt_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` micro=`echo $libgcrypt_config_version | \ sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` if test "$major" -gt "$req_major"; then ok=yes else if test "$major" -eq "$req_major"; then if test "$minor" -gt "$req_minor"; then ok=yes else if test "$minor" -eq "$req_minor"; then if test "$micro" -ge "$req_micro"; then ok=yes fi fi fi fi fi fi if test $ok = yes; then AC_MSG_RESULT([yes ($libgcrypt_config_version)]) else AC_MSG_RESULT(no) fi if test $ok = yes; then # If we have a recent libgcrypt, we should also check that the # API is compatible if test "$req_libgcrypt_api" -gt 0 ; then if test -z "$use_gpgrt_config"; then tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0` else tmp=`$LIBGCRYPT_CONFIG --variable=api_version 2>/dev/null || echo 0` fi if test "$tmp" -gt 0 ; then AC_MSG_CHECKING([LIBGCRYPT API version]) if test "$req_libgcrypt_api" -eq "$tmp" ; then AC_MSG_RESULT([okay]) else ok=no AC_MSG_RESULT([does not match. want=$req_libgcrypt_api got=$tmp]) fi fi fi fi if test $ok = yes; then LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags` LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs` ifelse([$2], , :, [$2]) if test -z "$use_gpgrt_config"; then libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none` else libgcrypt_config_host=`$LIBGCRYPT_CONFIG --variable=host 2>/dev/null || echo none` fi if test x"$libgcrypt_config_host" != xnone ; then if test x"$libgcrypt_config_host" != x"$host" ; then AC_MSG_WARN([[ *** *** The config script "$LIBGCRYPT_CONFIG" was *** built for $libgcrypt_config_host and thus may not match the *** used host $host. *** You may want to use the configure option --with-libgcrypt-prefix *** to specify a matching config script or use \$SYSROOT. ***]]) gpg_config_script_warn="$gpg_config_script_warn libgcrypt" fi fi else LIBGCRYPT_CFLAGS="" LIBGCRYPT_LIBS="" ifelse([$3], , :, [$3]) fi AC_SUBST(LIBGCRYPT_CFLAGS) AC_SUBST(LIBGCRYPT_LIBS) ]) audiowmark-0.6.5/misc/000077500000000000000000000000001501136502400146155ustar00rootroot00000000000000audiowmark-0.6.5/misc/Dockerfile000066400000000000000000000004331501136502400166070ustar00rootroot00000000000000FROM gcc:latest RUN apt-get update && apt-get install -y \ libgcrypt20-dev libsndfile1-dev libmpg123-dev libzita-resampler-dev \ libfftw3-dev libavcodec-dev libavformat-dev autoconf-archive clang \ ffmpeg gettext zstd ADD . /audiowmark WORKDIR /audiowmark RUN misc/build.sh audiowmark-0.6.5/misc/Dockerfile-arch000066400000000000000000000004041501136502400175200ustar00rootroot00000000000000FROM archlinux RUN pacman -Syu --noconfirm RUN pacman -S --noconfirm \ gcc clang make automake autoconf libtool pkg-config \ libsndfile mpg123 zita-resampler fftw autoconf-archive \ ffmpeg ADD . /audiowmark WORKDIR /audiowmark RUN misc/build.sh audiowmark-0.6.5/misc/build.sh000077500000000000000000000013641501136502400162570ustar00rootroot00000000000000#!/bin/bash set -Eeuo pipefail build() { if [ -f "./configure" ]; then make uninstall make distclean fi echo "###############################################################################" echo "# BUILD TESTS :" echo "# CC=$CC CXX=$CXX " echo "# ./autogen.sh $@" echo "###############################################################################" $CXX --version | sed '/^[[:space:]]*$/d;s/^/# /' echo "###############################################################################" ./autogen.sh "$@" make -j `nproc` V=1 make -j `nproc` check make install } # Tests using gcc export CC=gcc CXX=g++ build --with-ffmpeg make -j `nproc` distcheck # Tests clang export CC=clang CXX=clang++ build --with-ffmpeg audiowmark-0.6.5/misc/dbuild.sh000077500000000000000000000002341501136502400164160ustar00rootroot00000000000000#!/bin/bash set -Eeuo pipefail docker build -f "misc/Dockerfile" -t audiowmark-dbuild . docker build -f "misc/Dockerfile-arch" -t audiowmark-dbuild-arch . audiowmark-0.6.5/misc/macbuild.sh000077500000000000000000000014261501136502400167370ustar00rootroot00000000000000#!/bin/bash set -Eeo pipefail -x # install dependencies brew install autoconf-archive automake libsndfile fftw mpg123 libgcrypt libtool ffmpeg@7 export PKG_CONFIG_PATH="$(brew --prefix ffmpeg@7)/lib/pkgconfig:${PKG_CONFIG_PATH:-}" export PATH="$(brew --prefix ffmpeg@7)/bin:$PATH" # build zita-resampler git clone https://github.com/swesterfeld/zita-resampler cd zita-resampler cmake . sudo make install cd .. export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH # build audiowmark ./autogen.sh NPROC=`sysctl -n hw.ncpu` make -j $NPROC make -j $NPROC check # test build audiowmark with ffmpeg support make clean ./autogen.sh --with-ffmpeg make -j $NPROC ### unfortunately HLS is currently broken on macOS, so although it builds, make check will fail ### ###make -j $NPROC check audiowmark-0.6.5/src/000077500000000000000000000000001501136502400144515ustar00rootroot00000000000000audiowmark-0.6.5/src/.gitignore000066400000000000000000000002511501136502400164370ustar00rootroot00000000000000*.o .deps/ .libs/ test/ audiowmark testconvcode testmp3 testrandom teststream testlimiter testhls testmpegts testshortcode testthreadpool testrawconverter testwavformat audiowmark-0.6.5/src/Makefile.am000066400000000000000000000045131501136502400165100ustar00rootroot00000000000000bin_PROGRAMS = audiowmark dist_bin_SCRIPTS = videowmark COMMON_SRC = utils.hh utils.cc convcode.hh convcode.cc random.hh random.cc wavdata.cc wavdata.hh \ audiostream.cc audiostream.hh sfinputstream.cc sfinputstream.hh stdoutwavoutputstream.cc stdoutwavoutputstream.hh \ sfoutputstream.cc sfoutputstream.hh rawinputstream.cc rawinputstream.hh rawoutputstream.cc rawoutputstream.hh \ rawconverter.cc rawconverter.hh mp3inputstream.cc mp3inputstream.hh wmcommon.cc wmcommon.hh fft.cc fft.hh \ limiter.cc limiter.hh shortcode.cc shortcode.hh mpegts.cc mpegts.hh hls.cc hls.hh audiobuffer.hh \ wmget.cc wmadd.cc syncfinder.cc syncfinder.hh wmspeed.cc wmspeed.hh threadpool.cc threadpool.hh \ resample.cc resample.hh wavpipeinputstream.cc wavpipeinputstream.hh wavchunkloader.cc wavchunkloader.hh COMMON_LIBS = $(SNDFILE_LIBS) $(FFTW_LIBS) $(LIBGCRYPT_LIBS) $(LIBMPG123_LIBS) $(FFMPEG_LIBS) $(LTLIBZITA_RESAMPLER) AM_CXXFLAGS = $(SNDFILE_CFLAGS) $(FFTW_CFLAGS) $(LIBGCRYPT_CFLAGS) $(LIBMPG123_CFLAGS) $(FFMPEG_CFLAGS) audiowmark_SOURCES = audiowmark.cc $(COMMON_SRC) audiowmark_LDFLAGS = $(COMMON_LIBS) noinst_PROGRAMS = testconvcode testrandom testmp3 teststream testlimiter testshortcode testmpegts testthreadpool \ testrawconverter testwavformat testconvcode_SOURCES = testconvcode.cc $(COMMON_SRC) testconvcode_LDFLAGS = $(COMMON_LIBS) testrandom_SOURCES = testrandom.cc $(COMMON_SRC) testrandom_LDFLAGS = $(COMMON_LIBS) testmp3_SOURCES = testmp3.cc $(COMMON_SRC) testmp3_LDFLAGS = $(COMMON_LIBS) teststream_SOURCES = teststream.cc $(COMMON_SRC) teststream_LDFLAGS = $(COMMON_LIBS) testlimiter_SOURCES = testlimiter.cc $(COMMON_SRC) testlimiter_LDFLAGS = $(COMMON_LIBS) testshortcode_SOURCES = testshortcode.cc $(COMMON_SRC) testshortcode_LDFLAGS = $(COMMON_LIBS) testmpegts_SOURCES = testmpegts.cc $(COMMON_SRC) testmpegts_LDFLAGS = $(COMMON_LIBS) testthreadpool_SOURCES = testthreadpool.cc $(COMMON_SRC) testthreadpool_LDFLAGS = $(COMMON_LIBS) testrawconverter_SOURCES = testrawconverter.cc $(COMMON_SRC) testrawconverter_LDFLAGS = $(COMMON_LIBS) testwavformat_SOURCES = testwavformat.cc $(COMMON_SRC) testwavformat_LDFLAGS = $(COMMON_LIBS) if COND_WITH_FFMPEG COMMON_SRC += hlsoutputstream.cc hlsoutputstream.hh noinst_PROGRAMS += testhls testhls_SOURCES = testhls.cc $(COMMON_SRC) testhls_LDFLAGS = $(COMMON_LIBS) endif audiowmark-0.6.5/src/TODO000066400000000000000000000004631501136502400151440ustar00rootroot00000000000000speed detection possible improvements: - detect silence instead of total volume? - split big region into two smaller ones? - connected search on even smaller regions? possible improvements: - dynamic bit strength - merge infinite wav header generator videowmark: opus length ffprobe -show_format phil.mkv audiowmark-0.6.5/src/audiobuffer.hh000066400000000000000000000027511501136502400172720ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_AUDIO_BUFFER_HH #define AUDIOWMARK_AUDIO_BUFFER_HH #include class AudioBuffer { const int n_channels = 0; std::vector buffer; public: AudioBuffer (int n_channels) : n_channels (n_channels) { } void write_frames (const std::vector& samples) { buffer.insert (buffer.end(), samples.begin(), samples.end()); } std::vector read_frames (size_t frames) { assert (frames * n_channels <= buffer.size()); const auto begin = buffer.begin(); const auto end = begin + frames * n_channels; std::vector result (begin, end); buffer.erase (begin, end); return result; } size_t can_read_frames() const { return buffer.size() / n_channels; } }; #endif /* AUDIOWMARK_AUDIO_BUFFER_HH */ audiowmark-0.6.5/src/audiostream.cc000066400000000000000000000070131501136502400172760ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "audiostream.hh" #include "wmcommon.hh" #include "sfinputstream.hh" #include "sfoutputstream.hh" #include "mp3inputstream.hh" #include "rawconverter.hh" #include "rawoutputstream.hh" #include "stdoutwavoutputstream.hh" #include "wavpipeinputstream.hh" using std::string; AudioStream::~AudioStream() { } std::unique_ptr AudioInputStream::create (const string& filename, Error& err) { std::unique_ptr in_stream; if (Params::input_format == Format::AUTO) { SFInputStream *sistream = new SFInputStream(); in_stream.reset (sistream); err = sistream->open (filename); if (err && MP3InputStream::detect (filename)) { MP3InputStream *mistream = new MP3InputStream(); in_stream.reset (mistream); err = mistream->open (filename); if (err) return nullptr; } else if (err) return nullptr; } else if (Params::input_format == Format::RAW) { RawInputStream *ristream = new RawInputStream(); in_stream.reset (ristream); err = ristream->open (filename, Params::raw_input_format); if (err) return nullptr; } else if (Params::input_format == Format::WAV_PIPE) { WavPipeInputStream *wistream = new WavPipeInputStream(); in_stream.reset (wistream); err = wistream->open (filename); if (err) return nullptr; } else { err = Error ("selected format is not supported as input format"); return nullptr; } return in_stream; } std::unique_ptr AudioOutputStream::create (const string& filename, int n_channels, int sample_rate, int bit_depth, Encoding encoding, size_t n_frames, Error& err) { std::unique_ptr out_stream; if (Params::output_format == Format::RAW) { RawOutputStream *rostream = new RawOutputStream(); out_stream.reset (rostream); err = rostream->open (filename, Params::raw_output_format); if (err) return nullptr; } else if (filename == "-") { bool wav_pipe = Params::output_format == Format::WAV_PIPE; StdoutWavOutputStream *swstream = new StdoutWavOutputStream(); out_stream.reset (swstream); err = swstream->open (n_channels, sample_rate, bit_depth, encoding, n_frames, wav_pipe); if (err) return nullptr; } else { SFOutputStream::OutFormat out_format; if (Params::output_format == Format::RF64) out_format = SFOutputStream::OutFormat::RF64; else out_format = SFOutputStream::OutFormat::WAV; SFOutputStream *sfostream = new SFOutputStream(); out_stream.reset (sfostream); err = sfostream->open (filename, n_channels, sample_rate, bit_depth, encoding, out_format); if (err) return nullptr; } return out_stream; } audiowmark-0.6.5/src/audiostream.hh000066400000000000000000000035701501136502400173140ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_AUDIO_STREAM_HH #define AUDIOWMARK_AUDIO_STREAM_HH #include #include #include "utils.hh" enum class Encoding { SIGNED, UNSIGNED, FLOAT }; class AudioStream { public: virtual int bit_depth() const = 0; virtual int sample_rate() const = 0; virtual int n_channels() const = 0; virtual ~AudioStream(); }; class AudioInputStream : public AudioStream { public: static std::unique_ptr create (const std::string& filename, Error& err); // for streams that do not know the number of frames in advance (i.e. raw input stream) static constexpr size_t N_FRAMES_UNKNOWN = ~size_t (0); virtual size_t n_frames() const = 0; virtual Encoding encoding() const = 0; virtual Error read_frames (std::vector& samples, size_t count) = 0; }; class AudioOutputStream : public AudioStream { public: static std::unique_ptr create (const std::string& filename, int n_channels, int sample_rate, int bit_depth, Encoding encoding, size_t n_frames, Error& err); virtual Error write_frames (const std::vector& frames) = 0; virtual Error close() = 0; }; #endif /* AUDIOWMARK_AUDIO_STREAM_HH */ audiowmark-0.6.5/src/audiowmark.cc000066400000000000000000000717121501136502400171330ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 #include #include "wavdata.hh" #include "utils.hh" #include "random.hh" #include "wmcommon.hh" #include "shortcode.hh" #include "hls.hh" #include "resample.hh" #include #include "config.h" using std::string; using std::vector; using std::min; using std::max; void print_usage() { // 01234567891123456789212345678931234567894123456789512345678961234567897123456789 printf ("usage: audiowmark [ ... ]\n"); printf ("\n"); printf ("Commands:\n"); printf (" * create a watermarked wav file with a message\n"); printf (" audiowmark add \n"); printf ("\n"); printf (" * retrieve message\n"); printf (" audiowmark get \n"); printf ("\n"); printf (" * compare watermark message with expected message\n"); printf (" audiowmark cmp \n"); printf ("\n"); printf (" * generate 128-bit watermarking key, to be used with --key option\n"); printf (" audiowmark gen-key [ --name ]\n"); printf ("\n"); printf ("Global options:\n"); printf (" -q, --quiet disable information messages\n"); printf (" --strict treat (minor) problems as errors\n"); printf ("\n"); printf ("Options for get / cmp:\n"); printf (" --detect-speed detect and correct replay speed difference\n"); printf (" --detect-speed-patient slower, more accurate speed detection\n"); printf (" --json write JSON results into file\n"); printf ("\n"); printf ("Options for add / get / cmp:\n"); printf (" --key load watermarking key from file\n"); printf (" --short enable short payload mode\n"); printf (" --strength set watermark strength [%.6g]\n", Params::water_delta * 1000); printf ("\n"); printf (" --input-format raw use raw stream as input\n"); printf (" --output-format raw use raw stream as output\n"); printf (" --format raw use raw stream as input and output\n"); printf ("\n"); printf ("The options to set the raw stream parameters (such as --raw-rate\n"); printf ("or --raw-channels) are documented in the README file.\n"); printf ("\n"); printf ("HLS command help can be displayed using --help-hls\n"); } void print_usage_hls() { printf ("usage: audiowmark [ ... ]\n"); printf ("\n"); printf ("Commands:\n"); printf (" * prepare HLS segments for streaming:\n"); printf (" audiowmark hls-prepare \n"); printf ("\n"); printf (" * watermark one HLS segment:\n"); printf (" audiowmark hls-add \n"); printf ("\n"); printf ("Global options:\n"); printf (" -q, --quiet disable information messages\n"); printf (" --strict treat (minor) problems as errors\n"); printf ("\n"); printf ("Watermarking options:\n"); printf (" --strength set watermark strength [%.6g]\n", Params::water_delta * 1000); printf (" --short enable short payload mode\n"); printf (" --key load watermarking key from file\n"); printf (" --bit-rate set AAC bitrate\n"); } Format parse_format (const string& str) { if (str == "raw") return Format::RAW; if (str == "auto") return Format::AUTO; if (str == "rf64") return Format::RF64; if (str == "wav-pipe") return Format::WAV_PIPE; error ("audiowmark: unsupported format '%s'\n", str.c_str()); exit (1); } RawFormat::Endian parse_endian (const string& str) { if (str == "little") return RawFormat::Endian::LITTLE; if (str == "big") return RawFormat::Endian::BIG; error ("audiowmark: unsupported endianness '%s'\n", str.c_str()); exit (1); } void parse_encoding (const string& str, RawFormat& fmt) { if (str == "signed") fmt.set_encoding (Encoding::SIGNED); else if (str == "unsigned") fmt.set_encoding (Encoding::UNSIGNED); else if (str == "float") { fmt.set_encoding (Encoding::FLOAT); fmt.set_bit_depth (32); } else if (str == "double") { fmt.set_encoding (Encoding::FLOAT); fmt.set_bit_depth (64); } else { error ("audiowmark: unsupported encoding '%s'\n", str.c_str()); exit (1); } } void update_raw_bits (RawFormat& fmt, int bits) { if (fmt.encoding() == Encoding::FLOAT) { error ("audiowmark: bit depth can not be changed for float / double encoding\n"); exit (1); } fmt.set_bit_depth (bits); } int atoi_or_die (const string& s) { char *e = nullptr; int i = strtol (s.c_str(), &e, 0); if (e && e[0]) { error ("audiowmark: error during string->int conversion: %s\n", s.c_str()); exit (1); } return i; } float atof_or_die (const string& s) { char *e = nullptr; float f = strtof (s.c_str(), &e); if (e && e[0]) { error ("audiowmark: error during string->float conversion: %s\n", s.c_str()); exit (1); } return f; } int gentest (const string& infile, const string& outfile) { printf ("generating test sample from '%s' to '%s'\n", infile.c_str(), outfile.c_str()); WavData wav_data; Error err = wav_data.load (infile); if (err) { error ("audiowmark: error loading %s: %s\n", infile.c_str(), err.message()); return 1; } const vector& in_signal = wav_data.samples(); vector out_signal; /* 2:45 of audio - this is approximately the minimal amount of audio data required * for storing three separate watermarks with a 128-bit encoded message */ const size_t offset = 0 * wav_data.n_channels() * wav_data.sample_rate(); const size_t n_samples = 165 * wav_data.n_channels() * wav_data.sample_rate(); if (in_signal.size() < (offset + n_samples)) { error ("audiowmark: input file %s too short\n", infile.c_str()); return 1; } for (size_t i = 0; i < n_samples; i++) { out_signal.push_back (in_signal[i + offset]); } WavData out_wav_data (out_signal, wav_data.n_channels(), wav_data.sample_rate(), wav_data.bit_depth()); err = out_wav_data.save (outfile); if (err) { error ("audiowmark: error saving %s: %s\n", outfile.c_str(), err.message()); return 1; } return 0; } int cut_start (const string& infile, const string& outfile, const string& start_str) { WavData wav_data; Error err = wav_data.load (infile); if (err) { error ("audiowmark: error loading %s: %s\n", infile.c_str(), err.message()); return 1; } size_t start = atoi_or_die (start_str.c_str()); const vector& in_signal = wav_data.samples(); vector out_signal; for (size_t i = start * wav_data.n_channels(); i < in_signal.size(); i++) out_signal.push_back (in_signal[i]); WavData out_wav_data (out_signal, wav_data.n_channels(), wav_data.sample_rate(), wav_data.bit_depth()); err = out_wav_data.save (outfile); if (err) { error ("audiowmark: error saving %s: %s\n", outfile.c_str(), err.message()); return 1; } return 0; } int test_subtract (const string& infile1, const string& infile2, const string& outfile) { WavData in1_data; Error err = in1_data.load (infile1); if (err) { error ("audiowmark: error loading %s: %s\n", infile1.c_str(), err.message()); return 1; } WavData in2_data; err = in2_data.load (infile2); if (err) { error ("audiowmark: error loading %s: %s\n", infile2.c_str(), err.message()); return 1; } if (in1_data.n_values() != in2_data.n_values()) { int64_t l1 = in1_data.n_values(); int64_t l2 = in2_data.n_values(); size_t delta = std::abs (l1 - l2); warning ("audiowmark: size mismatch: %zd frames\n", delta / in1_data.n_channels()); warning (" - %s frames: %zd\n", infile1.c_str(), in1_data.n_values() / in1_data.n_channels()); warning (" - %s frames: %zd\n", infile2.c_str(), in2_data.n_values() / in2_data.n_channels()); } assert (in1_data.n_channels() == in2_data.n_channels()); const auto& in1_signal = in1_data.samples(); const auto& in2_signal = in2_data.samples(); size_t len = std::min (in1_data.n_values(), in2_data.n_values()); vector out_signal; for (size_t i = 0; i < len; i++) out_signal.push_back (in1_signal[i] - in2_signal[i]); WavData out_wav_data (out_signal, in1_data.n_channels(), in1_data.sample_rate(), in1_data.bit_depth()); err = out_wav_data.save (outfile); if (err) { error ("audiowmark: error saving %s: %s\n", outfile.c_str(), err.message()); return 1; } return 0; } int test_snr (const string& orig_file, const string& wm_file) { WavData orig_data; Error err = orig_data.load (orig_file); if (err) { error ("audiowmark: error loading %s: %s\n", orig_file.c_str(), err.message()); return 1; } WavData wm_data; err = wm_data.load (wm_file); if (err) { error ("audiowmark: error loading %s: %s\n", wm_file.c_str(), err.message()); return 1; } assert (orig_data.n_values() == wm_data.n_values()); assert (orig_data.n_channels() == orig_data.n_channels()); const auto& orig_signal = orig_data.samples(); const auto& wm_signal = wm_data.samples(); double snr_delta_power = 0; double snr_signal_power = 0; for (size_t i = 0; i < orig_signal.size(); i++) { const double orig = orig_signal[i]; // original sample const double delta = orig_signal[i] - wm_signal[i]; // watermark snr_delta_power += delta * delta; snr_signal_power += orig * orig; } printf ("%f\n", 10 * log10 (snr_signal_power / snr_delta_power)); return 0; } int test_clip (const Key& key, const string& in_file, const string& out_file, int seed, int time_seconds) { WavData in_data; Error err = in_data.load (in_file); if (err) { error ("audiowmark: error loading %s: %s\n", in_file.c_str(), err.message()); return 1; } bool done = false; Random rng (key, seed, /* there is no stream for this test */ Random::Stream::data_up_down); size_t start_point, end_point; do { // this is unbiased only if 2 * block_size + time_seconds is smaller than overall file length const size_t values_per_block = (mark_sync_frame_count() + mark_data_frame_count()) * Params::frame_size * in_data.n_channels(); start_point = 2 * values_per_block * rng.random_double(); start_point /= in_data.n_channels(); end_point = start_point + time_seconds * in_data.sample_rate(); if (end_point < in_data.n_values() / in_data.n_channels()) done = true; } while (!done); //printf ("%.3f %.3f\n", start_point / double (in_data.sample_rate()), end_point / double (in_data.sample_rate())); vector out_signal (in_data.samples().begin() + start_point * in_data.n_channels(), in_data.samples().begin() + end_point * in_data.n_channels()); WavData out_wav_data (out_signal, in_data.n_channels(), in_data.sample_rate(), in_data.bit_depth()); err = out_wav_data.save (out_file); if (err) { error ("audiowmark: error saving %s: %s\n", out_file.c_str(), err.message()); return 1; } return 0; } int test_speed (const Key& key, int seed) { Random rng (key, seed, /* there is no stream for this test */ Random::Stream::data_up_down); double low = 0.85; double high = 1.15; printf ("%.6f\n", low + (rng() / double (UINT64_MAX)) * (high - low)); return 0; } int test_gen_noise (const Key& key, const string& out_file, double seconds, int rate, int bits) { const int channels = 2; vector noise; Random rng (key, 0, /* there is no stream for this test */ Random::Stream::data_up_down); for (size_t i = 0; i < size_t (rate * seconds) * channels; i++) noise.push_back (rng.random_double() * 2 - 1); WavData out_wav_data (noise, channels, rate, bits); Error err = out_wav_data.save (out_file); if (err) { error ("audiowmark: error saving %s: %s\n", out_file.c_str(), err.message()); return 1; } return 0; } int test_change_speed (const string& in_file, const string& out_file, double speed) { WavData in_data; Error err = in_data.load (in_file); if (err) { error ("audiowmark: error loading %s: %s\n", in_file.c_str(), err.message()); return 1; } WavData out_data = resample_ratio (in_data, 1 / speed, in_data.sample_rate()); err = out_data.save (out_file); if (err) { error ("audiowmark: error saving %s: %s\n", out_file.c_str(), err.message()); return 1; } return 0; } int test_resample (const string& in_file, const string& out_file, int new_rate) { WavData in_data; Error err = in_data.load (in_file); if (err) { error ("audiowmark: error loading %s: %s\n", in_file.c_str(), err.message()); return 1; } WavData out_data = resample (in_data, new_rate); err = out_data.save (out_file); if (err) { error ("audiowmark: error saving %s: %s\n", out_file.c_str(), err.message()); return 1; } return 0; } int test_info (const string& in_file, const string& property) { WavData in_data; Error err = in_data.load (in_file); if (err) { error ("audiowmark: error loading %s: %s\n", in_file.c_str(), err.message()); return 1; } if (property == "bit_depth") { printf ("%d\n", in_data.bit_depth()); return 0; } if (property == "frames") { printf ("%zd\n", in_data.n_frames()); return 0; } error ("audiowmark: unsupported property for test_info: %s\n", property.c_str()); return 1; } static string escape_key_name (const string& name) { string result; for (unsigned int ch : name) { if (ch == '"' || ch == '\\') { result += '\\'; result += ch; } else if (ch >= 32) // ASCII, UTF-8 { result += ch; } else { error ("audiowmark: bad key name: %d is not allowed as character in key names\n", ch); exit (1); } } return result; } int gen_key (const string& outfile, const string& key_name) { string ename = escape_key_name (key_name); int fd = open (outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fd < 0) { error ("audiowmark: error opening file %s: %s\n", outfile.c_str(), strerror (errno)); return 1; } FILE *f = fdopen (fd, "w"); if (!f) { close (fd); error ("audiowmark: error writing to file %s\n", outfile.c_str()); return 1; } fprintf (f, "# watermarking key for audiowmark\n\nkey %s\n", Random::gen_key().c_str()); if (key_name != "") fprintf (f, "name \"%s\"\n", ename.c_str()); fclose (f); // closes fd return 0; } static bool is_option (const string& arg) { /* single - is not treated as option (stdin / stdout) * --foo or -f is treated as option */ return (arg.size() > 1) && arg[0] == '-'; } class ArgParser { vector m_args; string m_command; bool starts_with (const string& s, const string& start) { return s.substr (0, start.size()) == start; } public: ArgParser (int argc, char **argv) { for (int i = 1; i < argc; i++) m_args.push_back (argv[i]); } bool parse_cmd (const string& cmd) { if (!m_args.empty() && m_args[0] == cmd) { m_args.erase (m_args.begin()); m_command = cmd; return true; } return false; } vector parse_multi_opt (const string& option) { vector values; auto it = m_args.begin(); while (it != m_args.end()) { auto next_it = it + 1; if (*it == option && next_it != m_args.end()) /* --option foo */ { values.push_back (*next_it); next_it = m_args.erase (it, it + 2); } else if (starts_with (*it, (option + "="))) /* --option=foo */ { values.push_back (it->substr (option.size() + 1)); next_it = m_args.erase (it); } it = next_it; } return values; } bool parse_opt (const string& option, string& out_s) { vector values = parse_multi_opt (option); if (values.size()) { out_s = values.back(); return true; } return false; } bool parse_opt (const string& option, int& out_i) { string out_s; if (parse_opt (option, out_s)) { out_i = atoi_or_die (out_s.c_str()); return true; } return false; } bool parse_opt (const string& option, float& out_f) { string out_s; if (parse_opt (option, out_s)) { out_f = atof_or_die (out_s.c_str()); return true; } return false; } bool parse_opt (const string& option) { for (auto it = m_args.begin(); it != m_args.end(); it++) { if (*it == option) /* --option */ { m_args.erase (it); return true; } } return false; } bool parse_args (size_t expected_count, vector& out_args) { if (m_args.size() == expected_count) { for (auto a : m_args) { if (is_option (a)) return false; } out_args = m_args; return true; } return false; } vector remaining_args() const { return m_args; } string command() const { return m_command; } }; void parse_shared_options (ArgParser& ap) { int i; if (ap.parse_opt ("--short", i)) { Params::payload_size = i; if (!short_code_init (Params::payload_size)) { error ("audiowmark: unsupported short payload size %zd\n", Params::payload_size); exit (1); } Params::payload_short = true; } ap.parse_opt ("--frames-per-bit", Params::frames_per_bit); if (ap.parse_opt ("--linear")) { Params::mix = false; } } vector parse_key_list (ArgParser& ap) { vector key_list; vector key_files = ap.parse_multi_opt ("--key"); for (auto f : key_files) { Key key; key.load_key (f); key_list.push_back (key); } vector test_keys = ap.parse_multi_opt ("--test-key"); for (auto t : test_keys) { Key key; key.set_test_key (atoi_or_die (t.c_str())); key_list.push_back (key); } if (key_list.empty()) { Key key; // default initialized with zero key key_list.push_back (key); } return key_list; } Key parse_key (ArgParser& ap) { auto key_list = parse_key_list (ap); if (key_list.size() > 1) { error ("audiowmark %s: watermark key can at most be set once (--key / --test-key option)\n", ap.command().c_str()); exit (1); } return key_list[0]; } void parse_add_options (ArgParser& ap) { string s; int i; float f; ap.parse_opt ("--set-input-label", Params::input_label); ap.parse_opt ("--set-output-label", Params::output_label); if (ap.parse_opt ("--snr")) { Params::snr = true; } if (ap.parse_opt ("--input-format", s)) { Params::input_format = parse_format (s); } if (ap.parse_opt ("--output-format", s)) { Params::output_format = parse_format (s); } if (ap.parse_opt ("--format", s)) { Params::input_format = Params::output_format = parse_format (s); } if (ap.parse_opt ( "--raw-input-endian", s)) { auto e = parse_endian (s); Params::raw_input_format.set_endian (e); } if (ap.parse_opt ("--raw-output-endian", s)) { auto e = parse_endian (s); Params::raw_output_format.set_endian (e); } if (ap.parse_opt ("--raw-endian", s)) { auto e = parse_endian (s); Params::raw_input_format.set_endian (e); Params::raw_output_format.set_endian (e); } if (ap.parse_opt ("--raw-input-encoding", s)) { parse_encoding (s, Params::raw_input_format); } if (ap.parse_opt ("--raw-output-encoding", s)) { parse_encoding (s, Params::raw_output_format); } if (ap.parse_opt ("--raw-encoding", s)) { parse_encoding (s, Params::raw_input_format); parse_encoding (s, Params::raw_output_format); } if (ap.parse_opt ("--raw-input-bits", i)) { update_raw_bits (Params::raw_input_format, i); } if (ap.parse_opt ("--raw-output-bits", i)) { update_raw_bits (Params::raw_output_format, i); } if (ap.parse_opt ("--raw-bits", i)) { update_raw_bits (Params::raw_input_format, i); update_raw_bits (Params::raw_output_format, i); } if (ap.parse_opt ("--raw-channels", i)) { Params::raw_input_format.set_channels (i); Params::raw_output_format.set_channels (i); } if (ap.parse_opt ("--raw-rate", i)) { Params::raw_input_format.set_sample_rate (i); Params::raw_output_format.set_sample_rate (i); } if (ap.parse_opt ("--test-no-limiter")) { Params::test_no_limiter = true; } if (Params::input_format == Format::RF64) { error ("audiowmark: using rf64 as input format has no effect\n"); exit (1); } if (ap.parse_opt ("--strength", f)) { Params::water_delta = f / 1000; } } void parse_get_options (ArgParser& ap) { string s; float f; int i; ap.parse_opt ("--test-cut", Params::test_cut); ap.parse_opt ("--test-truncate", Params::test_truncate); if (ap.parse_opt ("--hard")) { Params::hard = true; } if (ap.parse_opt ("--test-no-sync")) { Params::test_no_sync = true; } int speed_options = 0; if (ap.parse_opt ("--detect-speed")) { Params::detect_speed = true; speed_options++; } if (ap.parse_opt ("--detect-speed-patient")) { Params::detect_speed_patient = true; speed_options++; } if (ap.parse_opt ("--try-speed", f)) { speed_options++; Params::try_speed = f; } if (speed_options > 1) { error ("audiowmark: can only use one option: --detect-speed or --detect-speed-patient or --try-speed\n"); exit (1); } if (ap.parse_opt ("--test-speed", f)) { Params::test_speed = f; } if (ap.parse_opt ("--json", s)) { Params::json_output = s; } if (ap.parse_opt ("--chunk-size", f)) { if (f < 10) { error ("audiowmark: --chunk-size needs to be at least 10 minutes\n"); exit (1); } Params::get_chunk_size = f; } if (ap.parse_opt ("--sync-threshold", f)) { Params::sync_threshold2 = f; } if (ap.parse_opt ("--n-best", i)) { if (i < 0) { error ("audiowmark: --n-best should not be a negative number\n"); exit (1); } Params::get_n_best = i; } } template vector parse_positional (ArgParser& ap, Args ... arg_names) { vector vs {arg_names...}; vector args; if (ap.parse_args (vs.size(), args)) return args; string command = ap.command(); for (auto arg : ap.remaining_args()) { if (is_option (arg)) { error ("audiowmark: unsupported option '%s' for command '%s' (use audiowmark -h)\n", arg.c_str(), command.c_str()); exit (1); } } error ("audiowmark: error parsing arguments for command '%s' (use audiowmark -h)\n\n", command.c_str()); string msg = "usage: audiowmark " + command + " [options...]"; for (auto s : vs) msg += " <" + s + ">"; error ("%s\n", msg.c_str()); exit (1); } int main (int argc, char **argv) { ArgParser ap (argc, argv); vector args; if (ap.parse_opt ("--help") || ap.parse_opt ("-h")) { print_usage(); return 0; } if (ap.parse_opt ("--help-hls")) { print_usage_hls(); return 0; } if (ap.parse_opt ("--version") || ap.parse_opt ("-v")) { printf ("audiowmark %s\n", VERSION); return 0; } if (ap.parse_opt ("--quiet") || ap.parse_opt ("-q")) { set_log_level (Log::WARNING); } if (ap.parse_opt ("--strict")) { Params::strict = true; } if (ap.parse_cmd ("hls-add")) { parse_shared_options (ap); ap.parse_opt ("--bit-rate", Params::hls_bit_rate); Key key = parse_key (ap); args = parse_positional (ap, "input_ts", "output_ts", "message_hex"); return hls_add (key, args[0], args[1], args[2]); } else if (ap.parse_cmd ("hls-prepare")) { ap.parse_opt ("--bit-rate", Params::hls_bit_rate); args = parse_positional (ap, "input_dir", "output_dir", "playlist_name", "audio_master"); return hls_prepare (args[0], args[1], args[2], args[3]); } else if (ap.parse_cmd ("add")) { parse_shared_options (ap); parse_add_options (ap); Key key = parse_key (ap); args = parse_positional (ap, "input_wav", "watermarked_wav", "message_hex"); return add_watermark (key, args[0], args[1], args[2]); } else if (ap.parse_cmd ("get")) { parse_shared_options (ap); parse_get_options (ap); vector key_list = parse_key_list (ap); args = parse_positional (ap, "watermarked_wav"); return get_watermark (key_list, args[0], /* no ber */ ""); } else if (ap.parse_cmd ("cmp")) { parse_shared_options (ap); parse_get_options (ap); ap.parse_opt ("--expect-matches", Params::expect_matches); vector key_list = parse_key_list (ap); args = parse_positional (ap, "watermarked_wav", "message_hex"); return get_watermark (key_list, args[0], args[1]); } else if (ap.parse_cmd ("gen-key")) { string key_name; ap.parse_opt ("--name", key_name); args = parse_positional (ap, "key_file"); return gen_key (args[0], key_name); } else if (ap.parse_cmd ("gentest")) { args = parse_positional (ap, "input_wav", "output_wav"); return gentest (args[0], args[1]); } else if (ap.parse_cmd ("cut-start")) { args = parse_positional (ap, "input_wav", "output_wav", "cut_samples"); return cut_start (args[0], args[1], args[2]); } else if (ap.parse_cmd ("test-subtract")) { args = parse_positional (ap, "input1_wav", "input2_wav", "output_wav"); return test_subtract (args[0], args[1], args[2]); } else if (ap.parse_cmd ("test-snr")) { args = parse_positional (ap, "orig_wav", "watermarked_wav"); return test_snr (args[0], args[1]); } else if (ap.parse_cmd ("test-clip")) { parse_shared_options (ap); Key key = parse_key (ap); args = parse_positional (ap, "input_wav", "output_wav", "seed", "seconds"); return test_clip (key, args[0], args[1], atoi_or_die (args[2].c_str()), atoi_or_die (args[3].c_str())); } else if (ap.parse_cmd ("test-speed")) { parse_shared_options (ap); Key key = parse_key (ap); args = parse_positional (ap, "seed"); return test_speed (key, atoi_or_die (args[0].c_str())); } else if (ap.parse_cmd ("test-gen-noise")) { parse_shared_options (ap); int bits = 16; ap.parse_opt ("--bits", bits); Key key = parse_key (ap); args = parse_positional (ap, "output_wav", "seconds", "sample_rate"); return test_gen_noise (key, args[0], atof_or_die (args[1].c_str()), atoi_or_die (args[2].c_str()), bits); } else if (ap.parse_cmd ("test-change-speed")) { parse_shared_options (ap); args = parse_positional (ap, "input_wav", "output_wav", "speed"); return test_change_speed (args[0], args[1], atof_or_die (args[2].c_str())); } else if (ap.parse_cmd ("test-resample")) { parse_shared_options (ap); args = parse_positional (ap, "input_wav", "output_wav", "new_rate"); return test_resample (args[0], args[1], atoi_or_die (args[2].c_str())); } else if (ap.parse_cmd ("test-info")) { parse_shared_options (ap); args = parse_positional (ap, "input_wav", "property"); return test_info (args[0], args[1]); } else if (ap.remaining_args().size()) { string s = ap.remaining_args().front(); if (is_option (s)) { error ("audiowmark: unsupported global option '%s' (use audiowmark -h)\n", s.c_str()); } else { error ("audiowmark: unsupported command '%s' (use audiowmark -h)\n", s.c_str()); } return 1; } error ("audiowmark: error parsing commandline args (use audiowmark -h)\n"); return 1; } audiowmark-0.6.5/src/ber-double-mp3.sh000077500000000000000000000002001501136502400175150ustar00rootroot00000000000000#/bin/bash echo 512 $(ber-test.sh) for quality in 256 196 128 96 64 do echo $quality $(ber-test.sh double-mp3 $quality) done audiowmark-0.6.5/src/ber-mp3.sh000077500000000000000000000001711501136502400162540ustar00rootroot00000000000000#/bin/bash echo 512 $(ber-test.sh) for quality in 256 196 128 96 64 do echo $quality $(ber-test.sh mp3 $quality) done audiowmark-0.6.5/src/ber-ogg.sh000077500000000000000000000001711501136502400163310ustar00rootroot00000000000000#/bin/bash echo 512 $(ber-test.sh) for quality in 256 196 128 96 64 do echo $quality $(ber-test.sh ogg $quality) done audiowmark-0.6.5/src/ber-test.sh000077500000000000000000000145611501136502400165440ustar00rootroot00000000000000#!/bin/bash # set -Eeuo pipefail # -x set -Eeo pipefail TRANSFORM=$1 if [ "x$AWM_TRUNCATE" != "x" ]; then AWM_REPORT=truncv fi if [ "x$AWM_SET" == "x" ]; then AWM_SET=small fi if [ "x$AWM_SEEDS" == "x" ]; then AWM_SEEDS=0 fi if [ "x$AWM_REPORT" == "x" ]; then AWM_REPORT=fer fi if [ "x$AWM_FILE" == "x" ]; then AWM_FILE=t fi if [ "x$AWM_MULTI_CLIP" == "x" ]; then AWM_MULTI_CLIP=1 fi if [ "x$AWM_PATTERN_BITS" == "x" ]; then AWM_PATTERN_BITS=128 fi audiowmark_cmp() { audiowmark cmp "$@" || { if [ "x$AWM_FAIL_DIR" != "x" ]; then mkdir -p $AWM_FAIL_DIR SUM=$(sha1sum $1 | awk '{print $1;}') cp -av $1 $AWM_FAIL_DIR/${AWM_FILE}.${SUM}.wav fi } } { if [ "x$AWM_SET" == "xsmall" ]; then ls test/T* elif [ "x$AWM_SET" == "xbig" ]; then cat test_list elif [ "x$AWM_SET" != "x" ] && [ -d "$AWM_SET" ] && [ -f "$AWM_SET/T001"*wav ]; then ls $AWM_SET/T* else echo "bad AWM_SET $AWM_SET" >&2 exit 1 fi } | while read i do for SEED in $AWM_SEEDS do echo in_file $i if [ "x$AWM_RAND_PATTERN" != "x" ]; then # random pattern, 128 bit PATTERN=$( for i in $(seq 16) do printf "%02x" $((RANDOM % 256)) done ) elif [ "x$AWM_DRAND_PATTERN" != "x" ]; then # different "random" pattern per file, but reproducable for fair comparisions PATTERN=$(echo "$i:$SEED" | md5sum | awk '{print $1;}') else # pseudo random pattern, 128 bit PATTERN=4e1243bd22c66e76c2ba9eddc1f91394 fi PATTERN=${PATTERN:0:$((AWM_PATTERN_BITS / 4))} echo in_pattern $PATTERN echo in_flags $AWM_PARAMS $AWM_PARAMS_ADD --test-key $SEED audiowmark add "$i" ${AWM_FILE}.wav $PATTERN $AWM_PARAMS $AWM_PARAMS_ADD --test-key $SEED --quiet CUT=0 if [ "x$AWM_ALWAYS_CUT" != x ]; then CUT="$AWM_ALWAYS_CUT" fi if [ "x$AWM_RAND_CUT" != x ]; then CUT=$((CUT + RANDOM)) fi if [ "x$CUT" != x0 ]; then audiowmark cut-start "${AWM_FILE}.wav" "${AWM_FILE}.wav" $CUT TEST_CUT_ARGS="--test-cut $CUT" echo in_cut $CUT else TEST_CUT_ARGS="" fi if [ "x$AWM_SPEED" != x ]; then if [ "x$AWM_SPEED_PRE_MP3" != x ]; then # first (optional) mp3 step: simulate quality loss before speed change lame -b "$AWM_SPEED_PRE_MP3" ${AWM_FILE}.wav ${AWM_FILE}.mp3 --quiet rm ${AWM_FILE}.wav ffmpeg -f mp3 -i ${AWM_FILE}.mp3 ${AWM_FILE}.wav -v quiet -nostdin fi [ -z $SPEED_SEED ] && SPEED_SEED=0 SPEED=$(audiowmark test-speed $SPEED_SEED --test-key $SEED) SPEED_SEED=$((SPEED_SEED + 1)) echo in_speed $SPEED sox -D -V1 ${AWM_FILE}.wav ${AWM_FILE}.speed.wav speed $SPEED mv ${AWM_FILE}.speed.wav ${AWM_FILE}.wav if [ "x$AWM_SPEED_PATIENT" != x ]; then TEST_SPEED_ARGS="--detect-speed-patient --test-speed $SPEED" elif [ "x$AWM_TRY_SPEED" != x ]; then TEST_SPEED_ARGS="--try-speed $SPEED" else TEST_SPEED_ARGS="--detect-speed --test-speed $SPEED" fi else TEST_SPEED_ARGS="" fi if [ "x$TRANSFORM" == "xmp3" ]; then if [ "x$2" == "x" ]; then echo "need mp3 bitrate" >&2 exit 1 fi lame -b $2 ${AWM_FILE}.wav ${AWM_FILE}.mp3 --quiet OUT_FILE=${AWM_FILE}.mp3 elif [ "x$TRANSFORM" == "xdouble-mp3" ]; then if [ "x$2" == "x" ]; then echo "need mp3 bitrate" >&2 exit 1 fi # first mp3 step (fixed bitrate) lame -b 128 ${AWM_FILE}.wav ${AWM_FILE}.mp3 --quiet rm ${AWM_FILE}.wav ffmpeg -f mp3 -i ${AWM_FILE}.mp3 ${AWM_FILE}.wav -v quiet -nostdin # second mp3 step lame -b $2 ${AWM_FILE}.wav ${AWM_FILE}.mp3 --quiet OUT_FILE=${AWM_FILE}.mp3 elif [ "x$TRANSFORM" == "xogg" ]; then if [ "x$2" == "x" ]; then echo "need ogg bitrate" >&2 exit 1 fi if [ "x$AWM_OGG_RATE" != "x" ]; then OGG_ARGS="--resample $AWM_OGG_RATE" fi oggenc $OGG_ARGS -b $2 ${AWM_FILE}.wav -o ${AWM_FILE}.ogg --quiet OUT_FILE=${AWM_FILE}.ogg elif [ "x$TRANSFORM" == "x" ]; then OUT_FILE=${AWM_FILE}.wav else echo "unknown transform $TRANSFORM" >&2 exit 1 fi echo if [ "x${AWM_CLIP}" != "x" ]; then for CLIP in $(seq $AWM_MULTI_CLIP) do audiowmark test-clip $OUT_FILE ${OUT_FILE}.clip.wav $((CLIP_SEED++)) $AWM_CLIP --test-key $SEED audiowmark_cmp ${OUT_FILE}.clip.wav $PATTERN $AWM_PARAMS $AWM_PARAMS_GET --test-key $SEED $TEST_CUT_ARGS $TEST_SPEED_ARGS rm ${OUT_FILE}.clip.wav echo done elif [ "x$AWM_REPORT" == "xtruncv" ]; then for TRUNC in $AWM_TRUNCATE do audiowmark_cmp $OUT_FILE $PATTERN $AWM_PARAMS $AWM_PARAMS_GET --test-key $SEED $TEST_CUT_ARGS $TEST_SPEED_ARGS --test-truncate $TRUNC | sed "s/^/$TRUNC /g" echo done else audiowmark_cmp $OUT_FILE $PATTERN $AWM_PARAMS $AWM_PARAMS_GET --test-key $SEED $TEST_CUT_ARGS $TEST_SPEED_ARGS echo fi rm -f ${AWM_FILE}.wav $OUT_FILE # cleanup temp files done done | { if [ "x$AWM_REPORT" == "xfer" ]; then stdbuf -oL awk 'BEGIN { bad = n = 0 } $1 == "match_count" { if ($2 == 0) bad++; n++; } END { print bad, n, bad * 100.0 / (n > 0 ? n : 1); }' elif [ "x$AWM_REPORT" == "xferv" ]; then stdbuf -oL awk 'BEGIN { bad = n = 0 } { print "###", $0; } $1 == "match_count" { if ($2 == 0) bad++; n++; } END { print bad, n, bad * 100.0 / (n > 0 ? n : 1); }' elif [ "x$AWM_REPORT" == "xsync" ]; then stdbuf -oL awk 'BEGIN { bad = n = 0 } $1 == "sync_match" { bad += (3 - $2) / 3.0; n++; } END { print bad, n, bad * 100.0 / (n > 0 ? n : 1); }' elif [ "x$AWM_REPORT" == "xsyncv" ]; then stdbuf -oL awk '{ print "###", $0; } $1 == "sync_match" { correct += $2; missing += 3 - $2; incorrect += $3-$2; print "correct:", correct, "missing:", missing, "incorrect:", incorrect; }' elif [ "x$AWM_REPORT" == "xtruncv" ]; then stdbuf -oL awk ' { print "###", $0; } $2 == "match_count" { if (!n[$1]) { n[$1] = 0; bad[$1] = 0; } if ($3 == 0) bad[$1]++; n[$1]++; } END { for (trunc in n) { print trunc, bad[trunc], n[trunc], bad[trunc] * 100.0 / n[trunc]; } }' else echo "unknown report $AWM_REPORT" >&2 exit 1 fi } audiowmark-0.6.5/src/convcode.cc000066400000000000000000000156231501136502400165670ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "convcode.hh" #include #include #include using std::vector; using std::string; using std::min; int parity (unsigned int v) { int p = 0; while (v) { p ^= (v & 1); v >>= 1; } return p; } constexpr auto ab_generators = std::array { 066561, 075211, 071545, 054435, 063635, 052475, 063543, 075307, 052547, 045627, 067657, 051757 }; constexpr unsigned int ab_rate = ab_generators.size(); constexpr unsigned int order = 15; /* constexpr unsigned int order = 9; constexpr auto generators = std::array { 0557, 0663, 0711 }; constexpr unsigned int order = 14; constexpr auto generators = std::array { 021645, 035661, 037133 }; constexpr unsigned int order = 18; constexpr auto generators = std::array { 0552137, 0614671, 0772233 }; */ constexpr unsigned int state_count = (1 << order); constexpr unsigned int state_mask = (1 << order) - 1; size_t conv_code_size (ConvBlockType block_type, size_t msg_size) { switch (block_type) { case ConvBlockType::a: case ConvBlockType::b: return (msg_size + order) * ab_rate / 2; case ConvBlockType::ab: return (msg_size + order) * ab_rate; default: assert (false); } } vector get_block_type_generators (ConvBlockType block_type) { vector generators; if (block_type == ConvBlockType::a) { for (unsigned int i = 0; i < ab_rate / 2; i++) generators.push_back (ab_generators[i * 2]); } else if (block_type == ConvBlockType::b) { for (unsigned int i = 0; i < ab_rate / 2; i++) generators.push_back (ab_generators[i * 2 + 1]); } else { assert (block_type == ConvBlockType::ab); generators.assign (ab_generators.begin(), ab_generators.end()); } return generators; } vector conv_encode (ConvBlockType block_type, const vector& in_bits) { auto generators = get_block_type_generators (block_type); vector out_vec; vector vec = in_bits; /* termination: bring encoder into all-zero state */ for (unsigned int i = 0; i < order; i++) vec.push_back (0); unsigned int reg = 0; for (auto b : vec) { reg = (reg << 1) | b; for (auto poly : generators) { int out_bit = parity (reg & poly); out_vec.push_back (out_bit); } } return out_vec; } /* decode using viterbi algorithm */ vector conv_decode_soft (ConvBlockType block_type, const vector& coded_bits, float *error_out) { auto generators = get_block_type_generators (block_type); unsigned int rate = generators.size(); vector decoded_bits; assert (coded_bits.size() % rate == 0); struct StateEntry { int last_state; float delta; int bit; }; vector> error_count; for (size_t i = 0; i < coded_bits.size() + rate; i += rate) /* 1 extra element */ error_count.emplace_back (state_count, StateEntry {0, -1, 0}); error_count[0][0].delta = 0; /* start state */ /* precompute state -> output bits table */ vector state2bits; for (unsigned int state = 0; state < state_count; state++) { for (size_t p = 0; p < generators.size(); p++) { int out_bit = parity (state & generators[p]); state2bits.push_back (out_bit); } } for (size_t i = 0; i < coded_bits.size(); i += rate) { vector& old_table = error_count[i / rate]; vector& new_table = error_count[i / rate + 1]; for (unsigned int state = 0; state < state_count; state++) { /* this check enforces that we only consider states reachable from state=0 at time=0*/ if (old_table[state].delta >= 0) { for (int bit = 0; bit < 2; bit++) { int new_state = ((state << 1) | bit) & state_mask; float delta = old_table[state].delta; int sbit_pos = new_state * rate; for (size_t p = 0; p < rate; p++) { const float cbit = coded_bits[i + p]; const float sbit = state2bits[sbit_pos + p]; /* decoding error weight for this bit; if input is only 0.0 and 1.0, this is the hamming distance */ delta += (cbit - sbit) * (cbit - sbit); } if (delta < new_table[new_state].delta || new_table[new_state].delta < 0) /* better match with this link? replace entry */ { new_table[new_state].delta = delta; new_table[new_state].last_state = state; new_table[new_state].bit = bit; } } } } } unsigned int state = 0; if (error_out) *error_out = error_count.back()[state].delta / coded_bits.size(); for (size_t idx = error_count.size() - 1; idx > 0; idx--) { decoded_bits.push_back (error_count[idx][state].bit); state = error_count[idx][state].last_state; } std::reverse (decoded_bits.begin(), decoded_bits.end()); /* remove termination */ assert (decoded_bits.size() >= order); decoded_bits.resize (decoded_bits.size() - order); return decoded_bits; } vector conv_decode_hard (ConvBlockType block_type, const vector& coded_bits) { /* for the final application, we always want soft decoding, so we don't * special case hard decoding here, so this will be a little slower than * necessary */ vector soft_bits; for (auto b : coded_bits) soft_bits.push_back (b ? 1.0f : 0.0f); return conv_decode_soft (block_type, soft_bits); } void conv_print_table (ConvBlockType block_type) { vector bits (100); bits[0] = 1; vector out_bits = conv_encode (block_type, bits); auto generators = get_block_type_generators (block_type); unsigned int rate = generators.size(); for (unsigned int r = 0; r < rate; r++) { for (unsigned int i = 0; i < order; i++) printf ("%s%d", i == 0 ? "" : " ", out_bits[i * rate + r]); printf ("\n"); } } audiowmark-0.6.5/src/convcode.hh000066400000000000000000000024721501136502400165770ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_CONV_CODE_HH #define AUDIOWMARK_CONV_CODE_HH #include #include enum class ConvBlockType { a, b, ab }; size_t conv_code_size (ConvBlockType block_type, size_t msg_size); std::vector conv_encode (ConvBlockType block_type, const std::vector& in_bits); std::vector conv_decode_hard (ConvBlockType block_type, const std::vector& coded_bits); std::vector conv_decode_soft (ConvBlockType block_type, const std::vector& coded_bits, float *error_out = nullptr); void conv_print_table (ConvBlockType block_type); #endif /* AUDIOWMARK_CONV_CODE_HH */ audiowmark-0.6.5/src/delta2wav.sh000077500000000000000000000003041501136502400166760ustar00rootroot00000000000000for delta in 0.080 0.050 0.030 0.020 0.015 0.010 0.005 do audiowmark add "$1" --water-delta=$delta /tmp/w$delta.wav a9fcd54b25e7e863d72cd47c08af46e61 done audiowmark scale "$1" /tmp/w.orig.wav audiowmark-0.6.5/src/fer-test.sh000077500000000000000000000005141501136502400165410ustar00rootroot00000000000000#!/bin/bash SEEDS="$1" MAX_SEED=$(($SEEDS - 1)) P="$2" shift 2 echo "n seeds : $SEEDS" echo "ber-test args : $@" echo "params : $P" for seed in $(seq 0 $MAX_SEED) do echo $(AWM_SEEDS=$seed AWM_PARAMS="$P" AWM_REPORT="fer" ber-test.sh "$@") done | awk '{bad += $1; files += $2; print bad, files, bad * 100. / files }' audiowmark-0.6.5/src/fft.cc000066400000000000000000000060171501136502400155430ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "fft.hh" #include #include #include using std::vector; using std::complex; using std::map; static struct FFTPlanMap { std::map fft_plan; std::map ifft_plan; ~FFTPlanMap() { auto free_plans = [] (auto& pmap) { for (auto it = pmap.begin(); it != pmap.end(); it++) { fftwf_destroy_plan (it->second); } pmap.clear(); }; free_plans (fft_plan); free_plans (ifft_plan); } } fft_plan_map; static std::mutex fft_planner_mutex; FFTProcessor::FFTProcessor (size_t N) { std::lock_guard lg (fft_planner_mutex); const size_t N_2 = N + 2; /* extra space for r2c extra complex output */ m_in = static_cast (fftwf_malloc (sizeof (float) * N_2)); m_out = static_cast (fftwf_malloc (sizeof (float) * N_2)); /* plan if not done already */ fftwf_plan& pfft = fft_plan_map.fft_plan[N]; if (!pfft) pfft = fftwf_plan_dft_r2c_1d (N, m_in, (fftwf_complex *) m_out, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); fftwf_plan& pifft = fft_plan_map.ifft_plan[N]; if (!pifft) pifft = fftwf_plan_dft_c2r_1d (N, (fftwf_complex *) m_in, m_out, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); /* store plan for size N as member variables */ plan_fft = pfft; plan_ifft = pifft; // we could add code for saving plans here, and use patient planning } FFTProcessor::~FFTProcessor() { fftwf_free (m_in); fftwf_free (m_out); } void FFTProcessor::fft() { fftwf_execute_dft_r2c (plan_fft, m_in, (fftwf_complex *) m_out); } void FFTProcessor::ifft() { fftwf_execute_dft_c2r (plan_ifft, (fftwf_complex *) m_in, m_out); } vector FFTProcessor::ifft (const vector>& in) { vector out ((in.size() - 1) * 2); /* complex vector and m_out have the same layout in memory */ std::copy (in.begin(), in.end(), reinterpret_cast *> (m_in)); ifft(); std::copy (m_out, m_out + out.size(), &out[0]); return out; } vector> FFTProcessor::fft (const vector& in) { vector> out (in.size() / 2 + 1); /* complex vector and m_out have the same layout in memory */ std::copy (in.begin(), in.end(), m_in); fft(); std::copy (m_out, m_out + out.size() * 2, reinterpret_cast (&out[0])); return out; } audiowmark-0.6.5/src/fft.hh000066400000000000000000000024671501136502400155620ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_FFT_HH #define AUDIOWMARK_FFT_HH #include #include #include class FFTProcessor { fftwf_plan plan_fft; fftwf_plan plan_ifft; float *m_in = nullptr; float *m_out = nullptr; public: FFTProcessor (size_t N); ~FFTProcessor(); /* low level (fast) */ void fft(); void ifft(); float *in() { return m_in; } float *out() { return m_out; }; /* high level (convenient) */ std::vector> fft (const std::vector& in); std::vector ifft (const std::vector>& in); }; #endif /* AUDIOWMARK_FFT_HH */ audiowmark-0.6.5/src/gen-fer-adoc.sh000077500000000000000000000012241501136502400172360ustar00rootroot00000000000000#!/bin/bash for TEST in mp3 double-mp3 ogg do echo ".$TEST" echo '[frame="topbot",options="header",cols="12*>"]' echo '|==========================' echo -n "| " for D in $(seq 15 -1 5) do DELTA=$(printf "0.0%02d\n" $D) echo -n "| $DELTA" done echo for BITRATE in 512 256 196 128 96 64 do echo -n "| $BITRATE" | sed 's/512/wav/g' for D in $(seq 15 -1 5) do DELTA=$(printf "0.0%02d\n" $D) cat $DELTA-$TEST-* | grep ^$BITRATE | awk '{bad += $2; n += $3} END {fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done echo '|==========================' echo done audiowmark-0.6.5/src/gen-fer-mk.sh000077500000000000000000000020371501136502400167420ustar00rootroot00000000000000#!/bin/bash DELTA_RANGE="0.005 0.006 0.007 0.008 0.009 0.010 0.011 0.012 0.013 0.014 0.015" SEEDS="$(seq 0 19)" echo -n "all: " for SEED in $SEEDS do for DELTA in $DELTA_RANGE do echo -n "$DELTA-ogg-$SEED $DELTA-mp3-$SEED $DELTA-double-mp3-$SEED " done done echo echo for SEED in $SEEDS do for DELTA in $DELTA_RANGE do FILE="$DELTA-ogg-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_SET=huge AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-ogg.sh ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="$DELTA-mp3-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_SET=huge AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-mp3.sh ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="$DELTA-double-mp3-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_SET=huge AWM_PARAMS='--water-delta $DELTA' AWM_REPORT=fer AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-double-mp3.sh ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done audiowmark-0.6.5/src/gen-mp3q-clip.sh000077500000000000000000000033111501136502400173620ustar00rootroot00000000000000#!/bin/bash SEEDS=$(seq 5) STRENGTHS="10 15" MP3_QUALITIES="128 64 48" MULTI_CLIP=4 if [ "x$1" == "xmk" ]; then echo -n "all:" for STRENGTH in $STRENGTHS do FILE="snr-$STRENGTH" echo -n " $FILE" done for SEED in $SEEDS do for STRENGTH in $STRENGTHS do for Q in $MP3_QUALITIES do FILE="mp3-$Q-$STRENGTH-$SEED" echo -n " $FILE" done done done echo echo for STRENGTH in $STRENGTHS do FILE="snr-$STRENGTH" echo "$FILE:" echo -e "\t( cd ..; AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' snr.sh ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done for SEED in $SEEDS do for STRENGTH in $STRENGTHS do for Q in $MP3_QUALITIES do FILE="mp3-$Q-$STRENGTH-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_SET=huge2 AWM_CLIP='50' AWM_PARAMS_ADD='--strength $STRENGTH' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 $Q ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done done fi if [ "x$1" == "xadoc" ]; then echo '[frame="topbot",options="header",cols="<1,4*<"]' echo '|==========================' echo '| *Strength* | *SNR* | *mp3 128kbit/s* | *mp3 64kbit/s* | *mp3 48kbit/s*' for STRENGTH in $STRENGTHS do echo -n "| *$STRENGTH* " cat snr-$STRENGTH | awk '{printf ("| %.2f ", $1);}' for Q in $MP3_QUALITIES do for FILE in "mp3-$Q-$STRENGTH-*" do cat $FILE done | grep -v '#' | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done echo '|==========================' fi audiowmark-0.6.5/src/gen-short-clip-adoc.sh000077500000000000000000000022561501136502400205540ustar00rootroot00000000000000#!/bin/bash STRENGTHS="10 15 20 30" STR_CLIPS="5 10 15 20 25 30" MAIN_CLIPS="5 10 15 20 30 40 50 60" echo ".performance-by-clip-length" echo '[frame="topbot",options="header",cols="<2,8*>1"]' echo '|==========================' echo -n "| Quality " for CLIP in $MAIN_CLIPS do echo -n "| $CLIP" done echo for TEST in mp3-256 mp3-128 double-mp3-128 ogg-128 do echo -n "| $TEST " for CLIP in $MAIN_CLIPS do cat main-$TEST-*-$CLIP | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done echo echo '|==========================' echo ".effects-of-watermarking-strength" echo '[frame="topbot",options="header",cols="<1,7*>1"]' echo '|==========================' echo -n "| Strength | SNR " for CLIP in $STR_CLIPS do echo -n "| $CLIP" done echo for STRENGTH in $STRENGTHS do echo -n "| $STRENGTH " cat snr-$STRENGTH | awk '{printf ("| %.2f ", $1);}' for CLIP in $STR_CLIPS do cat str-$STRENGTH-mp3-*-$CLIP | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done echo echo '|==========================' audiowmark-0.6.5/src/gen-short-clip-mk.sh000077500000000000000000000044461501136502400202600ustar00rootroot00000000000000#!/bin/bash SEEDS=$(seq 5) STRENGTHS="10 15 20 30" STR_CLIPS="5 10 15 20 25 30" MAIN_CLIPS="5 10 15 20 30 40 50 60" MULTI_CLIP=4 echo -n "all:" for STRENGTH in $STRENGTHS do FILE="snr-$STRENGTH" echo -n " $FILE" done for SEED in $SEEDS do for STRENGTH in $STRENGTHS do for CLIP in $STR_CLIPS do FILE="str-$STRENGTH-mp3-$SEED-$CLIP" echo -n " $FILE" done done for CLIP in $MAIN_CLIPS do echo -n " main-mp3-256-$SEED-$CLIP main-mp3-128-$SEED-$CLIP main-ogg-128-$SEED-$CLIP main-double-mp3-128-$SEED-$CLIP" done done echo echo for SEED in $SEEDS do for STRENGTH in $STRENGTHS do for CLIP in $STR_CLIPS do FILE="str-$STRENGTH-mp3-$SEED-$CLIP" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_SET=huge2 AWM_PARAMS_ADD='--strength $STRENGTH' AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done done for STRENGTH in $STRENGTHS do FILE="snr-$STRENGTH" echo "$FILE:" echo -e "\t( cd ..; AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' snr.sh ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done for SEED in $SEEDS do for CLIP in $MAIN_CLIPS do FILE="main-mp3-256-$SEED-$CLIP" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_SET=huge2 AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 256 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="main-mp3-128-$SEED-$CLIP" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_SET=huge2 AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="main-ogg-128-$SEED-$CLIP" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_SET=huge2 AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="main-double-mp3-128-$SEED-$CLIP" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_SET=huge2 AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done audiowmark-0.6.5/src/gen-short-payload-adoc.sh000077500000000000000000000013421501136502400212510ustar00rootroot00000000000000#!/bin/bash STRENGTHS="10 15" STR_CLIPS="6 10 15 20 25 30" QUALITIES="128 256" for LS in long short do echo ".watermarking-with-$LS-payload" echo '[frame="topbot",options="header",cols="<1,7*>1"]' echo '|==========================' echo -n "| Strength | Quality " for CLIP in $STR_CLIPS do echo -n "| $CLIP" done echo for STRENGTH in $STRENGTHS do for QUALITY in $QUALITIES do echo -n "| $STRENGTH | $QUALITY " for CLIP in $STR_CLIPS do cat $LS-$CLIP-$STRENGTH-$QUALITY-* | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done done echo '|==========================' done audiowmark-0.6.5/src/gen-short-payload-mk.sh000077500000000000000000000024051501136502400207530ustar00rootroot00000000000000#!/bin/bash SEEDS=$(seq 5) STRENGTHS="10 15" QUALITIES="128 256" CLIPS="6 10 15 20 25 30" MULTI_CLIP=4 echo -n "all:" for SEED in $SEEDS do for STRENGTH in $STRENGTHS do for QUALITY in $QUALITIES do for CLIP in $CLIPS do echo -n " long-$CLIP-$STRENGTH-$QUALITY-$SEED short-$CLIP-$STRENGTH-$QUALITY-$SEED" done done done done echo echo for SEED in $SEEDS do for STRENGTH in $STRENGTHS do for QUALITY in $QUALITIES do for CLIP in $CLIPS do FILE="long-$CLIP-$STRENGTH-$QUALITY-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_ALWAYS_CUT=500000 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 $QUALITY ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="short-$CLIP-$STRENGTH-$QUALITY-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_PATTERN_BITS=12 AWM_RAND_PATTERN=1 AWM_ALWAYS_CUT=500000 AWM_SET=huge2 AWM_PARAMS='--short --strength $STRENGTH' AWM_CLIP='$CLIP' AWM_MULTI_CLIP='$MULTI_CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 $QUALITY ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done done done audiowmark-0.6.5/src/gen-speed-adoc.sh000077500000000000000000000016221501136502400175640ustar00rootroot00000000000000#!/bin/bash STRENGTHS="10 15" CLIPS="15 30" MODES="0 1 2" for MODE in $MODES do echo ".watermarking-speed-$MODE" echo '[frame="topbot",options="header",cols="<1,3*<"]' echo '|==========================' echo -n "| Strength " for CLIP in $CLIPS do echo -n "| 0:$CLIP" done echo -n "| 2:45" echo for STRENGTH in $STRENGTHS do echo -n "| $STRENGTH " for CLIP in $CLIPS do cat speed-$CLIP-$STRENGTH-$MODE-* | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done for FULL in speed-full-$STRENGTH-$MODE-* do if [ "$(echo $FULL | tr -d a-z0-9)" == "----" ]; then cat $FULL fi done | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' echo done echo '|==========================' done audiowmark-0.6.5/src/gen-speed-mk.sh000077500000000000000000000027071501136502400172720ustar00rootroot00000000000000#!/bin/bash SEEDS=$(seq 10) STRENGTHS="10 15" CLIPS="15 30" MODES="0 1 2" echo -n "all:" for SEED in $SEEDS do for MODE in $MODES do for STRENGTH in $STRENGTHS do # clips for CLIP in $CLIPS do echo -n " speed-$CLIP-$STRENGTH-$MODE-$SEED" done # full file echo -n " speed-full-$STRENGTH-$MODE-$SEED" done done done echo echo for SEED in $SEEDS do for MODE in $MODES do if [ "x$MODE" == "x0" ]; then MODE_ARGS="" elif [ "x$MODE" == "x1" ]; then MODE_ARGS="AWM_SPEED_PATIENT=1" elif [ "x$MODE" == "x2" ]; then MODE_ARGS="AWM_TRY_SPEED=1" fi for STRENGTH in $STRENGTHS do # clips for CLIP in $CLIPS do FILE="speed-$CLIP-$STRENGTH-$MODE-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_REPORT=ferv AWM_DRAND_PATTERN=1 AWM_SET=huge2 AWM_PARAMS_ADD='--strength $STRENGTH' AWM_SPEED=1 $MODE_ARGS AWM_SPEED_PRE_MP3=128 AWM_CLIP='$CLIP' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done # full file FILE="speed-full-$STRENGTH-$MODE-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_REPORT=ferv AWM_DRAND_PATTERN=1 AWM_SET=huge2 AWM_PARAMS_ADD='--strength $STRENGTH' AWM_SPEED=1 $MODE_ARGS AWM_SPEED_PRE_MP3=128 AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done done audiowmark-0.6.5/src/gen-sync-adoc.sh000077500000000000000000000013731501136502400174430ustar00rootroot00000000000000#!/bin/bash echo ".sync-codec-resistence" echo '[frame="topbot",options="header",cols="<2,6*>1"]' echo '|==========================' echo -n "| " for STRENGTH in $(seq 10 -1 5) do echo -n "| $STRENGTH" done echo for TEST in mp3 double-mp3 ogg do if [ $TEST == mp3 ]; then echo -n "| mp3 128kbit/s" elif [ $TEST == double-mp3 ]; then echo -n "| double mp3 128kbit/s" elif [ $TEST == ogg ]; then echo -n "| ogg 128kbit/s" else echo "error: bad TEST $TEST ???" exit 1 fi for STRENGTH in $(seq 10 -1 5) do cat $STRENGTH-$TEST-* | grep -v '^#' | awk '{bad += $1; n += $2} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done echo echo '|==========================' audiowmark-0.6.5/src/gen-sync-mk.sh000077500000000000000000000022171501136502400171420ustar00rootroot00000000000000#!/bin/bash STRENGTH_RANGE=$(seq 5 10) SEEDS="$(seq 0 19)" echo -n "all: " for SEED in $SEEDS do for STRENGTH in $STRENGTH_RANGE do echo -n "$STRENGTH-ogg-$SEED $STRENGTH-mp3-$SEED $STRENGTH-double-mp3-$SEED " done done echo echo for SEED in $SEEDS do for STRENGTH in $STRENGTH_RANGE do FILE="$STRENGTH-ogg-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_REPORT=ferv AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="$STRENGTH-mp3-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_REPORT=ferv AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="$STRENGTH-double-mp3-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_REPORT=ferv AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done audiowmark-0.6.5/src/gen-test.sh000077500000000000000000000004051501136502400165350ustar00rootroot00000000000000#!/bin/bash mkdir -p test if [ ! -z "$(ls test)" ]; then echo test dir not empty exit 1 fi seq=1 cat test_list | while read f do audiowmark gentest "$f" test/T$(printf "%02d__%s" $seq $(echo $f | sed 's, ,_,g;s,.*/,,g')).wav || exit 1 ((seq++)) done audiowmark-0.6.5/src/gen-trunc-adoc.sh000077500000000000000000000015071501136502400176210ustar00rootroot00000000000000#!/bin/bash TRUNCATE="60 110 245" for TRUNC in 60 110 245 do echo ".sync-codec-resistence$TRUNC" echo '[frame="topbot",options="header",cols="<2,6*>1"]' echo '|==========================' echo -n "| " for STRENGTH in $(seq 10 -1 5) do echo -n "| $STRENGTH" done echo for TEST in mp3 double-mp3 ogg do if [ $TEST == mp3 ]; then echo -n "| mp3 128kbit/s" elif [ $TEST == double-mp3 ]; then echo -n "| double mp3 128kbit/s" elif [ $TEST == ogg ]; then echo -n "| ogg 128kbit/s" else echo "error: bad TEST $TEST ???" exit 1 fi for STRENGTH in $(seq 10 -1 5) do cat $STRENGTH-$TEST-* | grep -v '^#' | grep ^$TRUNC | awk '{bad += $2; n += $3} END {if (n==0) n=1;fer=100.0*bad/n; bold=fer>0?"*":" ";printf ("| %s%.2f%s", bold, fer, bold)}' done echo done echo echo '|==========================' done audiowmark-0.6.5/src/gen-trunc-mk.sh000077500000000000000000000023001501136502400173120ustar00rootroot00000000000000#!/bin/bash STRENGTH_RANGE=$(seq 5 10) SEEDS="$(seq 0 19)" TRUNCATE="60 110 245" echo -n "all: " for SEED in $SEEDS do for STRENGTH in $STRENGTH_RANGE do echo -n "$STRENGTH-ogg-$SEED $STRENGTH-mp3-$SEED $STRENGTH-double-mp3-$SEED " done done echo echo for SEED in $SEEDS do for STRENGTH in $STRENGTH_RANGE do FILE="$STRENGTH-ogg-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_TRUNCATE='$TRUNCATE' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh ogg 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="$STRENGTH-mp3-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_TRUNCATE='$TRUNCATE' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo FILE="$STRENGTH-double-mp3-$SEED" echo "$FILE:" echo -e "\t( cd ..; AWM_RAND_PATTERN=1 AWM_RAND_CUT=1 AWM_SET=huge2 AWM_PARAMS='--strength $STRENGTH' AWM_TRUNCATE='$TRUNCATE' AWM_SEEDS=$SEED AWM_FILE='t-$FILE' ber-test.sh double-mp3 128 ) >x$FILE" echo -e "\tmv x$FILE $FILE" echo done done audiowmark-0.6.5/src/hls.cc000066400000000000000000000425371501136502400155610ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "mpegts.hh" #include "sfinputstream.hh" #include "sfoutputstream.hh" #include "wmcommon.hh" #include "wavdata.hh" #include "config.h" using std::string; using std::vector; using std::regex; using std::map; using std::min; #if !HAVE_FFMPEG int hls_prepare (const string& in_dir, const string& out_dir, const string& filename, const string& audio_master) { error ("audiowmark: hls support is not available in this build of audiowmark\n"); return 1; } int hls_add (const Key& key, const string& infile, const string& outfile, const string& bits) { error ("audiowmark: hls support is not available in this build of audiowmark\n"); return 1; } #else #include "hlsoutputstream.hh" static bool file_exists (const string& filename) { struct stat st; if (stat (filename.c_str(), &st) == 0) { return S_ISREG (st.st_mode); } return false; } static string args2string (const vector& args) { string result; bool first = true; for (auto a : args) { if (!first) result += " "; first = false; result += a; } return result; } static Error run (const vector& args, vector *pipe_out = nullptr) { auto report_error = [=] { error ("audiowmark: failed to execute %s\n", args2string (args).c_str()); }; char *argv[args.size() + 1]; for (size_t i = 0; i < args.size(); i++) argv[i] = (char *) args[i].c_str(); argv[args.size()] = nullptr; int pipe_fds[2]; if (pipe_out) { if (pipe (pipe_fds) == -1) { report_error(); return Error ("pipe() failed"); } } pid_t pid = fork(); if (pid < 0) { if (pipe_out) { close (pipe_fds[0]); close (pipe_fds[1]); } report_error(); return Error ("fork() failed"); } if (pid == 0) /* child process */ { if (pipe_out) { // replace stdout with pipe if (dup2 (pipe_fds[1], STDOUT_FILENO) == -1) { perror ("audiowmark: dup2() failed"); exit (127); } // close remaining pipe fds close (pipe_fds[0]); close (pipe_fds[1]); } execvp (argv[0], argv); perror ("audiowmark: execvp() failed"); // should not be reached in normal operation, so exec failed exit (127); } /* parent process */ if (pipe_out) /* capture child stdout */ { close (pipe_fds[1]); // close pipe write fd FILE *f = fdopen (pipe_fds[0], "r"); if (!f) { close (pipe_fds[0]); report_error(); return Error ("fdopen() pipe failed"); } char buffer[1024]; while (fgets (buffer, 1024, f)) { if (strlen (buffer) && buffer[strlen (buffer) - 1] == '\n') buffer[strlen (buffer) - 1] = 0; if (pipe_out) pipe_out->push_back (buffer); } fclose (f); // close pipe read fd } int status; pid_t exited = waitpid (pid, &status, 0); if (exited < 0) { report_error(); return Error ("waitpid() failed"); } if (WIFEXITED (status)) { int exit_status = WEXITSTATUS (status); if (exit_status != 0) { report_error(); return Error (string_printf ("subprocess failed / exit status %d", exit_status)); } } else { report_error(); return Error ("child didn't exit normally"); } return Error::Code::NONE; } Error ff_decode (const string& filename, WavData& out_wav_data) { FILE *tmp_file = tmpfile(); ScopedFile tmp_file_s (tmp_file); string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file)); if (!tmp_file) return Error ("failed to create temp file"); Error err = run ({"ffmpeg", "-v", "error", "-nostdin", "-y", "-f", "mpegts", "-i", filename, "-f", "wav", tmp_file_name}); if (err) return err; err = out_wav_data.load (tmp_file_name); return err; } int hls_add (const Key& key, const string& infile, const string& outfile, const string& bits) { TSReader reader; Error err = reader.load (infile); if (err) { error ("hls: %s\n", err.message()); return 1; } const TSReader::Entry *full_flac = reader.find ("full.flac"); if (!full_flac) { error ("hls: no embedded context found in %s\n", infile.c_str()); return 1; } SFInputStream in_stream; err = in_stream.open (&full_flac->data); if (err) { error ("hls: %s\n", err.message()); return 1; } map vars = reader.parse_vars ("vars"); bool missing_vars = false; auto get_var = [&] (const std::string& var) { auto it = vars.find (var); if (it == vars.end()) { error ("audiowmark: hls segment is missing value for required variable '%s'\n", var.c_str()); missing_vars = true; return ""; } else return it->second.c_str(); }; size_t start_pos = atoi (get_var ("start_pos")); size_t prev_size = atoi (get_var ("prev_size")); size_t size = atoi (get_var ("size")); double pts_start = atof (get_var ("pts_start")); int bit_rate = atoi (get_var ("bit_rate")); size_t prev_ctx = min (1024 * 3, prev_size); string channel_layout = get_var ("channel_layout"); if (missing_vars) return 1; if (Params::hls_bit_rate) // command line option overrides vars bit-rate bit_rate = Params::hls_bit_rate; HLSOutputStream out_stream (in_stream.n_channels(), in_stream.sample_rate(), in_stream.bit_depth()); out_stream.set_bit_rate (bit_rate); out_stream.set_channel_layout (channel_layout); /* ffmpeg aac encode adds one frame of latency - it would be possible to compensate for this * by setting shift = 1024, but it can also be done by adjusting the presentation timestamp */ const size_t shift = 0; const size_t cut_aac_frames = (prev_ctx + shift) / 1024; const size_t delete_input_start = prev_size - prev_ctx; const size_t keep_aac_frames = size / 1024; err = out_stream.open (outfile, cut_aac_frames, keep_aac_frames, pts_start, delete_input_start); if (err) { error ("audiowmark: error opening HLS output stream %s: %s\n", outfile.c_str(), err.message()); return 1; } int wm_rc = add_stream_watermark (key, &in_stream, &out_stream, bits, start_pos - prev_size); if (wm_rc != 0) return wm_rc; info ("AAC Bitrate: %d\n", bit_rate); return 0; } Error bit_rate_from_m3u8 (const string& m3u8, const WavData& wav_data, int& bit_rate) { FILE *tmp_file = tmpfile(); ScopedFile tmp_file_s (tmp_file); string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file)); if (!tmp_file) return Error ("failed to create temp file"); Error err = run ({"ffmpeg", "-v", "error", "-nostdin", "-y", "-i", m3u8, "-c:a", "copy", "-f", "adts", tmp_file_name}); if (err) return err; struct stat stat_buf; if (stat (tmp_file_name.c_str(), &stat_buf) != 0) { return Error (string_printf ("failed to stat temporary aac file: %s", strerror (errno))); } double seconds = double (wav_data.n_frames()) / wav_data.sample_rate(); bit_rate = stat_buf.st_size / seconds * 8; return Error::Code::NONE; } Error load_audio_master (const string& filename, WavData& audio_master_data) { FILE *tmp_file = tmpfile(); ScopedFile tmp_file_s (tmp_file); string tmp_file_name = string_printf ("/dev/fd/%d", fileno (tmp_file)); if (!tmp_file) return Error ("failed to create temp file"); /* extract wav */ Error err = run ({"ffmpeg", "-v", "error", "-nostdin", "-y", "-i", filename, "-f", "wav", tmp_file_name}); if (err) return err; err = audio_master_data.load (tmp_file_name); if (err) return err; return Error::Code::NONE; } Error probe_input_segment (const string& filename, map& params) { TSReader reader; Error err = reader.load (filename); if (err) { error ("audiowmark: hls: failed to read mpegts input file: %s\n", filename.c_str()); return err; } if (reader.entries().size()) { error ("audiowmark: hls: file appears to be already prepared: %s\n", filename.c_str()); return Error ("input for hls-prepare must not contain context"); } vector format_out; err = run ({"ffprobe", "-v", "error", "-print_format", "compact", "-show_streams", filename}, &format_out); if (err) { error ("audiowmark: hls: failed to validate input file: %s\n", filename.c_str()); return err; } for (auto o : format_out) { /* parse assignments stream|index=0|codec_name=aac|... */ string key, value; bool in_key = true; for (char c : '|' + o + '|') { if (c == '=') { in_key = false; } else if (c == '|') { params[key] = value; in_key = true; key = ""; value = ""; } else { if (in_key) key += c; else value += c; } } } return Error::Code::NONE; } int hls_prepare (const string& in_dir, const string& out_dir, const string& filename, const string& audio_master) { string in_name = in_dir + "/" + filename; FILE *in_file = fopen (in_name.c_str(), "r"); ScopedFile in_file_s (in_file); if (!in_file) { error ("audiowmark: error opening input playlist %s\n", in_name.c_str()); return 1; } int mkret = mkdir (out_dir.c_str(), 0755); if (mkret == -1 && errno != EEXIST) { error ("audiowmark: unable to create directory %s: %s\n", out_dir.c_str(), strerror (errno)); return 1; } string out_name = out_dir + "/" + filename; if (file_exists (out_name)) { error ("audiowmark: output file already exists: %s\n", out_name.c_str()); return 1; } FILE *out_file = fopen (out_name.c_str(), "w"); ScopedFile out_file_s (out_file); if (!out_file) { error ("audiowmark: error opening output playlist %s\n", out_name.c_str()); return 1; } WavData audio_master_data; Error err = load_audio_master (audio_master, audio_master_data); if (err) { error ("audiowmark: failed to load audio master: %s\n", audio_master.c_str()); return 1; } struct Segment { string name; size_t size; map vars; }; vector segments; char buffer[1024]; const regex blank_re (R"(\s*(#.*)?)"); while (fgets (buffer, 1024, in_file)) { /* kill newline chars at end */ int last = strlen (buffer) - 1; while (last > 0 && (buffer[last] == '\n' || buffer[last] == '\r')) buffer[last--] = 0; string s = buffer; std::smatch match; if (regex_match (s, blank_re)) { /* blank line or comment */ fprintf (out_file, "%s\n", s.c_str()); } else { fprintf (out_file, "%s\n", s.c_str()); Segment segment; segment.name = s; segments.push_back (segment); } } for (auto& segment : segments) { map params; string segname = in_dir + "/" + segment.name; Error err = probe_input_segment (segname, params); if (err) { error ("audiowmark: hls: %s\n", err.message()); return 1; } /* validate input segment */ if (atoi (params["index"].c_str()) != 0) { error ("audiowmark: hls segment '%s' contains more than one stream\n", segname.c_str()); return 1; } if (params["codec_name"] != "aac") { error ("audiowmark: hls segment '%s' is not encoded using AAC\n", segname.c_str()); return 1; } int segment_channels = atoi (params["channels"].c_str()); if (segment_channels != audio_master_data.n_channels()) { error ("audiowmark: number of channels mismatch:\n - hls segment '%s' has %d channels\n - audio master '%s' has %d channels\n", segname.c_str(), segment_channels, audio_master.c_str(), audio_master_data.n_channels()); return 1; } /* get segment parameters */ if (params["channel_layout"].empty()) { error ("audiowmark: hls segment '%s' has no channel_layout entry\n", segname.c_str()); return 1; } segment.vars["channel_layout"] = params["channel_layout"]; /* get start pts */ if (params["start_time"].empty()) { error ("audiowmark: hls segment '%s' has no start_time entry\n", segname.c_str()); return 1; } segment.vars["pts_start"] = params["start_time"]; } /* find bitrate for AAC encoder */ int bit_rate = 0; if (!Params::hls_bit_rate) { err = bit_rate_from_m3u8 (in_dir + "/" + filename, audio_master_data, bit_rate); if (err) { error ("audiowmark: bit-rate detection failed: %s\n", err.message()); return 1; } info ("AAC Bitrate: %d (detected)\n", bit_rate); } else { bit_rate = Params::hls_bit_rate; info ("AAC Bitrate: %d\n", bit_rate); } info ("Segments: %zd\n", segments.size()); size_t start_pos = 0; for (auto& segment : segments) { WavData out; Error err = ff_decode (in_dir + "/" + segment.name, out); if (err) { error ("audiowmark: hls: ff_decode failed: %s\n", err.message()); return 1; } segment.size = out.n_values() / out.n_channels(); if ((segment.size % 1024) != 0) { error ("audiowmark: hls input segments need 1024-sample alignment (due to AAC)\n"); return 1; } /* store 3 seconds of the context before this segment and after this segment (if available) */ const size_t ctx_3sec = 3 * out.sample_rate(); const size_t prev_size = min (start_pos, ctx_3sec); const size_t segment_size_with_ctx = prev_size + segment.size + ctx_3sec; segment.vars["start_pos"] = string_printf ("%zd", start_pos); segment.vars["size"] = string_printf ("%zd", segment.size); segment.vars["prev_size"] = string_printf ("%zd", prev_size); segment.vars["bit_rate"] = string_printf ("%d", bit_rate); /* write audio segment with context */ const size_t start_point = min (start_pos - prev_size, audio_master_data.n_frames()); const size_t end_point = min (start_point + segment_size_with_ctx, audio_master_data.n_frames()); vector out_signal (audio_master_data.samples().begin() + start_point * audio_master_data.n_channels(), audio_master_data.samples().begin() + end_point * audio_master_data.n_channels()); // append zeros if audio master is too short to provide segment with context out_signal.resize (segment_size_with_ctx * audio_master_data.n_channels()); vector full_flac_mem; SFOutputStream out_stream; err = out_stream.open (&full_flac_mem, audio_master_data.n_channels(), audio_master_data.sample_rate(), audio_master_data.bit_depth(), Encoding::SIGNED, /* flac does not support floating point audio */ SFOutputStream::OutFormat::FLAC); if (err) { error ("audiowmark: hls: open context flac failed: %s\n", err.message()); return 1; } err = out_stream.write_frames (out_signal); if (err) { error ("audiowmark: hls: write context flac failed: %s\n", err.message()); return 1; } err = out_stream.close(); if (err) { error ("audiowmark: hls: close context flac failed: %s\n", err.message()); return 1; } /* store everything we need in a mpegts file */ TSWriter writer; writer.append_data ("full.flac", full_flac_mem); writer.append_vars ("vars", segment.vars); string out_segment = out_dir + "/" + segment.name; if (file_exists (out_segment)) { error ("audiowmark: output file already exists: %s\n", out_segment.c_str()); return 1; } err = writer.process (in_dir + "/" + segment.name, out_segment); if (err) { error ("audiowmark: processing hls segment %s failed: %s\n", segment.name.c_str(), err.message()); return 1; } /* start position for the next segment */ start_pos += segment.size; } int orig_seconds = start_pos / audio_master_data.sample_rate(); info ("Time: %d:%02d\n", orig_seconds / 60, orig_seconds % 60); return 0; } #endif audiowmark-0.6.5/src/hls.hh000066400000000000000000000021451501136502400155620ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_HLS_HH #define AUDIOWMARK_HLS_HH #include int hls_add (const Key& key, const std::string& infile, const std::string& outfile, const std::string& bits); int hls_prepare (const std::string& in_dir, const std::string& out_dir, const std::string& filename, const std::string& audio_master); Error ff_decode (const std::string& filename, WavData& out_wav_data); #endif /* AUDIOWMARK_MPEGTS_HH */ audiowmark-0.6.5/src/hlsoutputstream.cc000066400000000000000000000344751501136502400202600ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "hlsoutputstream.hh" #undef av_err2str #define av_err2str(errnum) av_make_error_string((char*)__builtin_alloca(AV_ERROR_MAX_STRING_SIZE), AV_ERROR_MAX_STRING_SIZE, errnum) /* HLSOutputStream is based on code from ffmpeg: doc/examples/muxing.c */ /* * Copyright (c) 2003 Fabrice Bellard * * 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. */ using std::vector; using std::string; using std::min; HLSOutputStream::HLSOutputStream (int n_channels, int sample_rate, int bit_depth) : m_bit_depth (bit_depth), m_sample_rate (sample_rate), m_n_channels (n_channels), m_audio_buffer (n_channels) { av_log_set_level (AV_LOG_ERROR); } void HLSOutputStream::set_bit_rate (int bit_rate) { m_bit_rate = bit_rate; } void HLSOutputStream::set_channel_layout (const string& channel_layout) { m_channel_layout = channel_layout; } HLSOutputStream::~HLSOutputStream() { close(); } /* Add an output stream. */ Error HLSOutputStream::add_stream (const AVCodec **codec, enum AVCodecID codec_id) { /* find the encoder */ *codec = avcodec_find_encoder (codec_id); if (!(*codec)) return Error (string_printf ("could not find encoder for '%s'", avcodec_get_name (codec_id))); m_st = avformat_new_stream (m_fmt_ctx, NULL); if (!m_st) return Error ("could not allocate stream"); m_st->id = m_fmt_ctx->nb_streams - 1; m_enc = avcodec_alloc_context3 (*codec); if (!m_enc) return Error ("could not alloc an encoding context"); if ((*codec)->type != AVMEDIA_TYPE_AUDIO) return Error ("codec type must be audio"); m_enc->sample_fmt = (*codec)->sample_fmts ? (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; m_enc->bit_rate = m_bit_rate; m_enc->sample_rate = m_sample_rate; if ((*codec)->supported_samplerates) { bool match = false; for (int i = 0; (*codec)->supported_samplerates[i]; i++) { if ((*codec)->supported_samplerates[i] == m_sample_rate) { m_enc->sample_rate = m_sample_rate; match = true; } } if (!match) return Error (string_printf ("no codec support for sample rate %d", m_sample_rate)); } AVChannelLayout channel_layout = { AVChannelOrder (0), }; if (av_channel_layout_from_string (&channel_layout, m_channel_layout.c_str()) != 0) return Error (string_printf ("bad channel layout '%s'", m_channel_layout.c_str())); av_channel_layout_uninit (&m_enc->ch_layout); av_channel_layout_copy (&m_enc->ch_layout, &channel_layout); if ((*codec)->ch_layouts) { av_channel_layout_uninit (&m_enc->ch_layout); av_channel_layout_copy (&m_enc->ch_layout, &(*codec)->ch_layouts[0]); for (int i = 0; (*codec)->ch_layouts[i].nb_channels; i++) { if (av_channel_layout_compare (&(*codec)->ch_layouts[i], &channel_layout) == 0) { av_channel_layout_uninit (&m_enc->ch_layout); av_channel_layout_copy (&m_enc->ch_layout, &(*codec)->ch_layouts[i]); } } } if (av_channel_layout_compare (&channel_layout, &m_enc->ch_layout) != 0) return Error (string_printf ("codec: unsupported channel layout '%s'", m_channel_layout.c_str())); av_channel_layout_uninit (&channel_layout); m_st->time_base = (AVRational){ 1, m_enc->sample_rate }; /* Some formats want stream headers to be separate. */ if (m_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) m_enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; return Error::Code::NONE; } AVFrame * HLSOutputStream::alloc_audio_frame (AVSampleFormat sample_fmt, const AVChannelLayout *channel_layout, int sample_rate, int nb_samples, Error& err) { AVFrame *frame = av_frame_alloc(); if (!frame) { err = Error ("error allocating an audio frame"); return nullptr; } frame->format = sample_fmt; av_channel_layout_copy (&frame->ch_layout, channel_layout); frame->sample_rate = sample_rate; frame->nb_samples = nb_samples; if (nb_samples) { int ret = av_frame_get_buffer (frame, 0); if (ret < 0) { err = Error ("Error allocating an audio buffer"); return nullptr; } } return frame; } Error HLSOutputStream::open_audio (const AVCodec *codec, AVDictionary *opt_arg) { int nb_samples; int ret; AVDictionary *opt = NULL; /* open it */ av_dict_copy (&opt, opt_arg, 0); ret = avcodec_open2 (m_enc, codec, &opt); av_dict_free (&opt); if (ret < 0) return Error (string_printf ("could not open audio codec: %s", av_err2str (ret))); if (m_enc->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE) nb_samples = 10000; else nb_samples = m_enc->frame_size; Error err; m_frame = alloc_audio_frame (m_enc->sample_fmt, &m_enc->ch_layout, m_enc->sample_rate, nb_samples, err); if (err) return err; m_tmp_frame = alloc_audio_frame (AV_SAMPLE_FMT_FLT, &m_enc->ch_layout, m_enc->sample_rate, nb_samples, err); if (err) return err; m_tmp_pkt = av_packet_alloc(); if (!m_tmp_pkt) return Error ("could not allocate AVPacket"); /* copy the stream parameters to the muxer */ ret = avcodec_parameters_from_context (m_st->codecpar, m_enc); if (ret < 0) return Error ("could not copy the stream parameters"); /* create resampler context */ m_swr_ctx = swr_alloc(); if (!m_swr_ctx) return Error ("could not allocate resampler context"); /* set options */ av_opt_set_chlayout (m_swr_ctx, "in_chlayout", &m_enc->ch_layout, 0); av_opt_set_int (m_swr_ctx, "in_sample_rate", m_enc->sample_rate, 0); av_opt_set_sample_fmt (m_swr_ctx, "in_sample_fmt", AV_SAMPLE_FMT_FLT, 0); av_opt_set_chlayout (m_swr_ctx, "out_chlayout", &m_enc->ch_layout, 0); av_opt_set_int (m_swr_ctx, "out_sample_rate", m_enc->sample_rate, 0); av_opt_set_sample_fmt (m_swr_ctx, "out_sample_fmt", m_enc->sample_fmt, 0); /* initialize the resampling context */ if ((ret = swr_init(m_swr_ctx)) < 0) return Error ("failed to initialize the resampling context"); return Error::Code::NONE; } /* fill audio frame with samples from AudioBuffer */ AVFrame * HLSOutputStream::get_audio_frame() { AVFrame *frame = m_tmp_frame; if (m_audio_buffer.can_read_frames() < size_t (frame->nb_samples)) return nullptr; vector samples = m_audio_buffer.read_frames (frame->nb_samples); std::copy (samples.begin(), samples.end(), (float *)frame->data[0]); frame->pts = m_next_pts; m_next_pts += frame->nb_samples; return frame; } int HLSOutputStream::write_frame (const AVRational *time_base, AVStream *st, AVPacket *pkt) { /* rescale output packet timestamp values from codec to stream timebase */ av_packet_rescale_ts (pkt, *time_base, st->time_base); pkt->stream_index = st->index; /* Write the compressed frame to the media file. */ return av_interleaved_write_frame (m_fmt_ctx, pkt); } /* * encode one audio frame and send it to the muxer * returns EncResult: OK, ERROR, DONE */ HLSOutputStream::EncResult HLSOutputStream::write_audio_frame (Error& err) { AVFrame *frame; int ret; frame = get_audio_frame(); if (frame) { /* convert samples from native format to destination codec format, using the resampler */ /* compute destination number of samples */ int dst_nb_samples = av_rescale_rnd (swr_get_delay (m_swr_ctx, m_enc->sample_rate) + frame->nb_samples, m_enc->sample_rate, m_enc->sample_rate, AV_ROUND_UP); av_assert0 (dst_nb_samples == frame->nb_samples); /* when we pass a frame to the encoder, it may keep a reference to it * internally; * make sure we do not overwrite it here */ ret = av_frame_make_writable (m_frame); if (ret < 0) { err = Error ("error making frame writable"); return EncResult::ERROR; } /* convert to destination format */ ret = swr_convert (m_swr_ctx, m_frame->data, dst_nb_samples, (const uint8_t **)frame->data, frame->nb_samples); if (ret < 0) { err = Error ("error while converting"); return EncResult::ERROR; } frame = m_frame; frame->pts = av_rescale_q (m_samples_count + m_start_pos, (AVRational){1, m_enc->sample_rate}, m_enc->time_base); m_samples_count += dst_nb_samples; } ret = avcodec_send_frame (m_enc, frame); if (ret == AVERROR_EOF) { return EncResult::DONE; // encoder has nothing more to do } else if (ret < 0) { err = Error (string_printf ("error encoding audio frame: %s", av_err2str (ret))); return EncResult::ERROR; } for (;;) { ret = avcodec_receive_packet (m_enc, m_tmp_pkt); if (ret == AVERROR (EAGAIN)) { return EncResult::OK; // encoder needs more data to produce something } else if (ret == AVERROR_EOF) { return EncResult::DONE; } else if (ret < 0) { err = Error (string_printf ("error while encoding audio frame: %s", av_err2str (ret))); return EncResult::ERROR; } /* one packet available */ if (m_cut_aac_frames) { m_cut_aac_frames--; } else if (m_keep_aac_frames) { ret = write_frame (&m_enc->time_base, m_st, m_tmp_pkt); if (ret < 0) { err = Error (string_printf ("error while writing audio frame: %s", av_err2str (ret))); return EncResult::ERROR; } m_keep_aac_frames--; } } } void HLSOutputStream::close_stream() { avcodec_free_context (&m_enc); av_frame_free (&m_frame); av_frame_free (&m_tmp_frame); av_packet_free (&m_tmp_pkt); swr_free (&m_swr_ctx); } Error HLSOutputStream::open (const string& out_filename, size_t cut_aac_frames, size_t keep_aac_frames, double pts_start, size_t delete_input_start) { assert (m_state == State::NEW); avformat_alloc_output_context2 (&m_fmt_ctx, NULL, "mpegts", NULL); if (!m_fmt_ctx) return Error ("failed to alloc avformat output context"); /* * Since each segment is generated individually, the continuity counter fields of each * mpegts segment start at 0, so we expect discontinuities whenever a new segment starts. * * Players are requested to ignore this by setting this flag. */ int ret = av_opt_set (m_fmt_ctx->priv_data, "mpegts_flags", "+initial_discontinuity", 0); if (ret < 0) return Error (av_err2str (ret)); string filename = out_filename; if (filename == "-") filename = "pipe:1"; ret = avio_open (&m_fmt_ctx->pb, filename.c_str(), AVIO_FLAG_WRITE); if (ret < 0) return Error (av_err2str (ret)); AVDictionary *opt = nullptr; const AVCodec *audio_codec; Error err = add_stream (&audio_codec, AV_CODEC_ID_AAC); if (err) return err; err = open_audio (audio_codec, opt); if (err) return err; /* Write the stream header, if any. */ ret = avformat_write_header (m_fmt_ctx, &opt); if (ret < 0) { error ("Error occurred when writing output file: %s\n", av_err2str(ret)); return Error ("avformat_write_header failed\n"); } m_delete_input_start = delete_input_start; m_cut_aac_frames = cut_aac_frames; m_keep_aac_frames = keep_aac_frames; // FIXME: correct? m_start_pos = pts_start * m_sample_rate - cut_aac_frames * 1024; m_start_pos += 1024; m_state = State::OPEN; return Error::Code::NONE; } Error HLSOutputStream::close() { if (m_state != State::OPEN) return Error::Code::NONE; // never close twice m_state = State::CLOSED; Error err; while (write_audio_frame (err) == EncResult::OK); if (err) return err; av_write_trailer (m_fmt_ctx); close_stream(); /* Close the output file. */ if (!(m_fmt_ctx->oformat->flags & AVFMT_NOFILE)) avio_closep (&m_fmt_ctx->pb); /* free the stream */ avformat_free_context (m_fmt_ctx); return Error::Code::NONE; } Error HLSOutputStream::write_frames (const std::vector& frames) { // if we don't need any more aac frames, just throw away samples (save cpu cycles) if (m_keep_aac_frames == 0) return Error::Code::NONE; m_audio_buffer.write_frames (frames); size_t delete_input = min (m_delete_input_start, m_audio_buffer.can_read_frames()); if (delete_input) { m_audio_buffer.read_frames (delete_input); m_delete_input_start -= delete_input; } Error err; while (m_audio_buffer.can_read_frames() >= 1024) { write_audio_frame (err); if (err) return err; } return Error::Code::NONE; } int HLSOutputStream::bit_depth() const { return m_bit_depth; } int HLSOutputStream::sample_rate() const { return m_sample_rate; } int HLSOutputStream::n_channels() const { return m_n_channels; } audiowmark-0.6.5/src/hlsoutputstream.hh000066400000000000000000000060651501136502400202640ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_HLS_OUTPUT_STREAM_HH #define AUDIOWMARK_HLS_OUTPUT_STREAM_HH #include "audiostream.hh" #include "audiobuffer.hh" #include extern "C" { #include #include #include #include #include #include } class HLSOutputStream : public AudioOutputStream { AVStream *m_st = nullptr; AVCodecContext *m_enc = nullptr; AVFormatContext *m_fmt_ctx = nullptr; /* pts of the next frame that will be generated */ int64_t m_next_pts = 0; int m_samples_count = 0; int m_start_pos = 0; AVFrame *m_frame = nullptr; AVFrame *m_tmp_frame = nullptr; AVPacket *m_tmp_pkt = nullptr; size_t m_cut_aac_frames = 0; size_t m_keep_aac_frames = 0; SwrContext *m_swr_ctx = nullptr; int m_bit_depth = 0; int m_sample_rate = 0; int m_n_channels = 0; AudioBuffer m_audio_buffer; size_t m_delete_input_start = 0; int m_bit_rate = 0; std::string m_channel_layout; enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; Error add_stream (const AVCodec **codec, enum AVCodecID codec_id); Error open_audio (const AVCodec *codec, AVDictionary *opt_arg); AVFrame *get_audio_frame(); enum class EncResult { OK, ERROR, DONE }; EncResult write_audio_frame (Error& err); void close_stream(); AVFrame *alloc_audio_frame (AVSampleFormat sample_fmt, const AVChannelLayout *channel_layout, int sample_rate, int nb_samples, Error& err); int write_frame (const AVRational *time_base, AVStream *st, AVPacket *pkt); public: HLSOutputStream (int n_channels, int sample_rate, int bit_depth); ~HLSOutputStream(); void set_bit_rate (int bit_rate); void set_channel_layout (const std::string& channel_layout); Error open (const std::string& output_filename, size_t cut_aac_frames, size_t keep_aac_frames, double pts_start, size_t delete_input_start); int bit_depth() const override; int sample_rate() const override; int n_channels() const override; Error write_frames (const std::vector& frames) override; Error close() override; }; #endif /* AUDIOWMARK_HLS_OUTPUT_STREAM_HH */ audiowmark-0.6.5/src/limiter.cc000066400000000000000000000077651501136502400164440ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "limiter.hh" #include #include #include using std::vector; using std::max; Limiter::Limiter (int n_channels, int sample_rate) : n_channels (n_channels), sample_rate (sample_rate) { } void Limiter::set_block_size_ms (int ms) { block_size = sample_rate * ms / 1000; } void Limiter::set_ceiling (float new_ceiling) { ceiling = new_ceiling; } vector Limiter::process (const vector& samples) { assert (block_size >= 1); assert (samples.size() % n_channels == 0); // process should be called with whole frames buffer.insert (buffer.end(), samples.begin(), samples.end()); /* need at least two complete blocks in buffer to produce output */ const uint buffered_blocks = buffer.size() / n_channels / block_size; if (buffered_blocks < 2) return {}; const uint blocks_todo = buffered_blocks - 1; vector out (blocks_todo * block_size * n_channels); for (uint b = 0; b < blocks_todo; b++) process_block (&buffer[b * block_size * n_channels], &out[b * block_size * n_channels]); buffer.erase (buffer.begin(), buffer.begin() + blocks_todo * block_size * n_channels); return out; } size_t Limiter::skip (size_t zeros) { assert (block_size >= 1); size_t buffer_size = buffer.size(); buffer_size += zeros * n_channels; /* need at least two complete blocks in buffer to produce output */ const size_t buffered_blocks = buffer_size / n_channels / block_size; if (buffered_blocks < 2) { buffer.resize (buffer_size); return 0; } const size_t blocks_todo = buffered_blocks - 1; buffer.resize (buffer_size - blocks_todo * block_size * n_channels); return blocks_todo * block_size; } float Limiter::block_max (const float *in) { float maximum = ceiling; for (uint x = 0; x < block_size * n_channels; x++) maximum = max (maximum, fabs (in[x])); return maximum; } void Limiter::process_block (const float *in, float *out) { if (block_max_last < ceiling) block_max_last = ceiling; if (block_max_current < ceiling) block_max_current = block_max (in); if (block_max_next < ceiling) block_max_next = block_max (in + block_size * n_channels); const float scale_start = ceiling / max (block_max_last, block_max_current); const float scale_end = ceiling / max (block_max_current, block_max_next); const float scale_step = (scale_end - scale_start) / block_size; for (size_t i = 0; i < block_size; i++) { const float scale = scale_start + i * scale_step; // debug_scale (scale); for (uint c = 0; c < n_channels; c++) out[i * n_channels + c] = in[i * n_channels + c] * scale; } block_max_last = block_max_current; block_max_current = block_max_next; block_max_next = 0; } void Limiter::debug_scale (float scale) { static int debug_scale_samples = 0; if (debug_scale_samples % (sample_rate / 1000) == 0) printf ("%f %f\n", double (debug_scale_samples) / sample_rate, scale); debug_scale_samples++; } vector Limiter::flush() { vector out; vector zblock (1024 * n_channels); size_t todo = buffer.size(); while (todo > 0) { vector block = process (zblock); if (block.size() > todo) block.resize (todo); out.insert (out.end(), block.begin(), block.end()); todo -= block.size(); } return out; } audiowmark-0.6.5/src/limiter.hh000066400000000000000000000027341501136502400164450ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_LIMITER_HH #define AUDIOWMARK_LIMITER_HH #include #include class Limiter { float ceiling = 1; float block_max_last = 0; float block_max_current = 0; float block_max_next = 0; uint block_size = 0; uint n_channels = 0; uint sample_rate = 0; std::vector buffer; void process_block (const float *in, float *out); float block_max (const float *in); void debug_scale (float scale); public: Limiter (int n_channels, int sample_rate); void set_block_size_ms (int value_ms); void set_ceiling (float ceiling); std::vector process (const std::vector& samples); size_t skip (size_t zeros); std::vector flush(); }; #endif /* AUDIOWMARK_LIMITER_HH */ audiowmark-0.6.5/src/mp3inputstream.cc000066400000000000000000000146301501136502400177570ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "mp3inputstream.hh" #include #include using std::min; using std::string; static void mp3_init() { static bool mpg123_init_ok = false; if (!mpg123_init_ok) { int err = mpg123_init(); if (err != MPG123_OK) { error ("audiowmark: init mpg123 lib failed\n"); exit (1); } mpg123_init_ok = true; } } MP3InputStream::~MP3InputStream() { close(); } Error MP3InputStream::open (const string& filename) { int err = 0; mp3_init(); m_handle = mpg123_new (nullptr, &err); if (err != MPG123_OK) return Error ("mpg123_new failed"); err = mpg123_param (m_handle, MPG123_ADD_FLAGS, MPG123_QUIET, 0); if (err != MPG123_OK) return Error ("setting quiet mode failed"); // allow arbitary amount of data for resync */ err = mpg123_param (m_handle, MPG123_RESYNC_LIMIT, -1, 0); if (err != MPG123_OK) return Error ("setting resync limit parameter failed"); // force floating point output { const long *rates; size_t rate_count; mpg123_format_none (m_handle); mpg123_rates (&rates, &rate_count); for (size_t i = 0; i < rate_count; i++) { err = mpg123_format (m_handle, rates[i], MPG123_MONO|MPG123_STEREO, MPG123_ENC_FLOAT_32); if (err != MPG123_OK) return Error (mpg123_strerror (m_handle)); } } err = mpg123_open (m_handle, filename.c_str()); if (err != MPG123_OK) return Error (mpg123_strerror (m_handle)); m_need_close = true; /* scan headers to get best possible length estimate */ err = mpg123_scan (m_handle); if (err != MPG123_OK) return Error (mpg123_strerror (m_handle)); long rate; int channels; int encoding; err = mpg123_getformat (m_handle, &rate, &channels, &encoding); if (err != MPG123_OK) return Error (mpg123_strerror (m_handle)); /* ensure that the format will not change */ mpg123_format_none (m_handle); mpg123_format (m_handle, rate, channels, encoding); m_n_values = mpg123_length (m_handle) * channels; m_n_channels = channels; m_sample_rate = rate; m_frames_left = m_n_values / m_n_channels; return Error::Code::NONE; } Error MP3InputStream::read_frames (std::vector& samples, size_t count) { while (!m_eof && m_read_buffer.size() < count * m_n_channels) { size_t buffer_bytes = mpg123_outblock (m_handle); assert (buffer_bytes % sizeof (float) == 0); std::vector buffer (buffer_bytes / sizeof (float)); size_t done; int err = mpg123_read (m_handle, reinterpret_cast (&buffer[0]), buffer_bytes, &done); if (err == MPG123_OK) { const size_t n_values = done / sizeof (float); m_read_buffer.insert (m_read_buffer.end(), buffer.begin(), buffer.begin() + n_values); } else if (err == MPG123_DONE) { m_eof = true; } else if (err == MPG123_NEED_MORE) { // some mp3s have this error before reaching eof -> harmless m_eof = true; } else { return Error (mpg123_strerror (m_handle)); } } /* pad zero samples at end if necessary to match the number of frames we promised to deliver */ if (m_eof && m_read_buffer.size() < m_frames_left * m_n_channels) m_read_buffer.resize (m_frames_left * m_n_channels); /* never read past the promised number of frames */ if (count > m_frames_left) count = m_frames_left; const auto begin = m_read_buffer.begin(); const auto end = begin + min (count * m_n_channels, m_read_buffer.size()); samples.assign (begin, end); m_read_buffer.erase (begin, end); m_frames_left -= count; return Error::Code::NONE; } void MP3InputStream::close() { if (m_state == State::OPEN) { if (m_handle && m_need_close) mpg123_close (m_handle); if (m_handle) { mpg123_delete (m_handle); m_handle = nullptr; } m_state = State::CLOSED; } } int MP3InputStream::bit_depth() const { return 24; /* mp3 decoder is running on floats */ } Encoding MP3InputStream::encoding() const { return Encoding::SIGNED; /* should use 24-bit pcm output */ } int MP3InputStream::sample_rate() const { return m_sample_rate; } int MP3InputStream::n_channels() const { return m_n_channels; } size_t MP3InputStream::n_frames() const { return m_n_values / m_n_channels; } /* there is no really simple way of detecting if something is an mp3 * * so we try to decode a few frames; if that works without error the * file is probably a valid mp3 */ bool MP3InputStream::detect (const string& filename) { struct ScopedMHandle { mpg123_handle *mh = nullptr; bool need_close = false; ~ScopedMHandle() { if (mh && need_close) mpg123_close (mh); if (mh) mpg123_delete (mh); } }; int err = 0; mp3_init(); mpg123_handle *mh = mpg123_new (NULL, &err); if (err != MPG123_OK) return false; auto smh = ScopedMHandle { mh }; // cleanup on return err = mpg123_param (mh, MPG123_ADD_FLAGS, MPG123_QUIET, 0); if (err != MPG123_OK) return false; err = mpg123_open (mh, filename.c_str()); if (err != MPG123_OK) return false; smh.need_close = true; long rate; int channels; int encoding; err = mpg123_getformat (mh, &rate, &channels, &encoding); if (err != MPG123_OK) return false; size_t buffer_bytes = mpg123_outblock (mh); unsigned char buffer[buffer_bytes]; for (size_t i = 0; i < 30; i++) { size_t done; err = mpg123_read (mh, buffer, buffer_bytes, &done); if (err == MPG123_DONE) { return true; } else if (err != MPG123_OK) { return false; } } return true; } audiowmark-0.6.5/src/mp3inputstream.hh000066400000000000000000000032751501136502400177740ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_MP3_INPUT_STREAM_HH #define AUDIOWMARK_MP3_INPUT_STREAM_HH #include #include #include "audiostream.hh" class MP3InputStream : public AudioInputStream { enum class State { NEW, OPEN, CLOSED }; int m_n_values = 0; int m_n_channels = 0; int m_sample_rate = 0; size_t m_frames_left = 0; bool m_need_close = false; bool m_eof = false; State m_state = State::NEW; mpg123_handle *m_handle = nullptr; std::vector m_read_buffer; public: ~MP3InputStream(); Error open (const std::string& filename); Error read_frames (std::vector& samples, size_t count) override; void close(); int bit_depth() const override; int sample_rate() const override; int n_channels() const override; size_t n_frames() const override; Encoding encoding() const override; static bool detect (const std::string& filename); }; #endif /* AUDIOWMARK_MP3_INPUT_STREAM_HH */ audiowmark-0.6.5/src/mpegts.cc000066400000000000000000000214421501136502400162620ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "mpegts.hh" using std::string; using std::vector; using std::map; using std::regex; class TSPacket { public: enum class ID { awmk_file, awmk_data, unknown }; private: std::array m_data; std::array get_id_bytes (ID type) { if (type == ID::awmk_file) return { 'G', 0x1F, 0xFF, 0x10, 'A', 'W', 'M', 'K', 'f', 'i', 'l', 'e' }; if (type == ID::awmk_data) return { 'G', 0x1F, 0xFF, 0x10, 'A', 'W', 'M', 'K', 'd', 'a', 't', 'a' }; return {0,}; } public: bool read (FILE *file, Error& err) { size_t bytes_read = fread (m_data.data(), 1, m_data.size(), file); if (bytes_read == 0) /* probably eof */ return false; if (bytes_read == m_data.size()) /* successful read */ { if (m_data[0] == 'G') return true; err = Error ("bad packet sync while reading transport (.ts) packet"); return false; } err = Error ("short read while reading transport stream (.ts) packet"); return false; } Error write (FILE *file) { size_t bytes_written = fwrite (m_data.data(), 1, m_data.size(), file); if (bytes_written != m_data.size()) return Error ("short write while writing transport stream (.ts) packet"); return Error::Code::NONE; } void clear (ID type) { std::fill (m_data.begin(), m_data.end(), 0); auto id = get_id_bytes (type); std::copy (id.begin(), id.end(), m_data.begin()); } unsigned char& operator[] (size_t n) { return m_data[n]; } bool id_eq (size_t offset, unsigned char a, unsigned char b, unsigned char c, unsigned char d) { return m_data[offset] == a && m_data[offset + 1] == b && m_data[offset + 2] == c && m_data[offset + 3] == d; } ID get_id() { if (id_eq (0, 'G', 0x1F, 0xFF, 0x10) && id_eq (4, 'A', 'W', 'M', 'K')) { if (id_eq (8, 'f', 'i', 'l', 'e')) return ID::awmk_file; if (id_eq (8, 'd', 'a', 't', 'a')) return ID::awmk_data; } return ID::unknown; } constexpr size_t size() { return m_data.size(); // is constant } const std::array& data() { return m_data; } }; Error TSWriter::append_file (const string& name, const string& filename) { vector data; FILE *datafile = fopen (filename.c_str(), "r"); ScopedFile datafile_s (datafile); if (!datafile) return Error ("unable to open data file"); int c; while ((c = fgetc (datafile)) >= 0) data.push_back (c); entries.push_back ({name, data}); return Error::Code::NONE; } void TSWriter::append_vars (const string& name, const map& vars) { vector data; for (auto kv : vars) { for (auto k : kv.first) data.push_back (k); data.push_back ('='); for (auto v : kv.second) data.push_back (v); data.push_back (0); } entries.push_back ({name, data}); } void TSWriter::append_data (const string& name, const vector& data) { entries.push_back ({name, data}); } Error TSWriter::process (const string& inname, const string& outname) { FILE *infile = fopen (inname.c_str(), "r"); FILE *outfile = fopen (outname.c_str(), "w"); ScopedFile infile_s (infile); ScopedFile outfile_s (outfile); if (!infile) { error ("audiowmark: unable to open %s for reading\n", inname.c_str()); return Error (strerror (errno)); } if (!outfile) { error ("audiowmark: unable to open %s for writing\n", outname.c_str()); return Error (strerror (errno)); } while (!feof (infile)) { TSPacket p; Error err; bool read_ok = p.read (infile, err); if (!read_ok) { if (err) return err; } else { err = p.write (outfile); if (err) return err; } } for (auto entry : entries) { string header = string_printf ("%zd:%s", entry.data.size(), entry.name.c_str()) + '\0'; vector data = entry.data; for (size_t i = 0; i < header.size(); i++) data.insert (data.begin() + i, header[i]); TSPacket p_file; p_file.clear (TSPacket::ID::awmk_file); size_t data_pos = 0; int pos = 12; while (data_pos < data.size()) { p_file[pos++] = data[data_pos]; if (pos == 188) { Error err = p_file.write (outfile); if (err) return err; p_file.clear (TSPacket::ID::awmk_data); pos = 12; } data_pos++; } if (pos != 12) { Error err = p_file.write (outfile); if (err) return err; } } return Error::Code::NONE; } bool TSReader::parse_header (Header& header, vector& data) { for (size_t i = 0; i < data.size(); i++) { if (data[i] == 0) // header is terminated with one single 0 byte { string s = (const char *) (&data[0]); static const regex header_re ("([0-9]*):(.*)"); std::smatch sm; if (regex_match (s, sm, header_re)) { header.data_size = atoi (sm[1].str().c_str()); header.filename = sm[2]; // erase header including null termination data.erase (data.begin(), data.begin() + i + 1); return true; } } } return false; } Error TSReader::load (const string& inname) { if (inname == "-") { return load (stdin); } else { FILE *infile = fopen (inname.c_str(), "r"); ScopedFile infile_s (infile); if (!infile) return Error (string_printf ("error opening input .ts '%s'", inname.c_str())); return load (infile); } } Error TSReader::load (FILE *infile) { vector awmk_stream; Header header; bool header_valid = false; Error err; while (!feof (infile)) { TSPacket p; bool read_ok = p.read (infile, err); if (!read_ok) { if (err) return err; } else { TSPacket::ID id = p.get_id(); if (id == TSPacket::ID::awmk_file) { /* new stream start, clear old contents */ header_valid = false; awmk_stream.clear(); } if (id == TSPacket::ID::awmk_file || id == TSPacket::ID::awmk_data) { awmk_stream.insert (awmk_stream.end(), p.data().begin() + 12, p.data().end()); if (!header_valid) { if (parse_header (header, awmk_stream)) { awmk_stream.reserve (header.data_size + p.size()); header_valid = true; } } // done? do we have enough bytes for the complete entry? if (header_valid && awmk_stream.size() >= header.data_size) { awmk_stream.resize (header.data_size); m_entries.push_back ({ header.filename, std::move (awmk_stream)}); header_valid = false; awmk_stream.clear(); } } } } return Error::Code::NONE; } const vector& TSReader::entries() { return m_entries; } const TSReader::Entry * TSReader::find (const string& name) const { for (const auto& entry : m_entries) if (entry.filename == name) return &entry; return nullptr; } map TSReader::parse_vars (const string& name) { map vars; auto entry = find (name); if (!entry) return vars; enum { KEY, VALUE } mode = KEY; string s; string key; for (auto c : entry->data) { if (c == '=' && mode == KEY) { key = s; s.clear(); mode = VALUE; } else if (c == '\0' && mode == VALUE) { vars[key] = s; s.clear(); mode = KEY; } else { s += c; } } return vars; } audiowmark-0.6.5/src/mpegts.hh000066400000000000000000000036601501136502400162760ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_MPEGTS_HH #define AUDIOWMARK_MPEGTS_HH #include class TSReader { public: struct Entry { std::string filename; std::vector data; }; private: struct Header { std::string filename; size_t data_size = 0; }; std::vector m_entries; bool parse_header (Header& header, std::vector& data); Error load (FILE *infile); public: Error load (const std::string& inname); const std::vector& entries(); const Entry *find (const std::string& filename) const; std::map parse_vars (const std::string& name); }; class TSWriter { struct Entry { std::string name; std::vector data; }; std::vector entries; public: Error append_file (const std::string& name, const std::string& filename); void append_vars (const std::string& name, const std::map& vars); void append_data (const std::string& name, const std::vector& data); Error process (const std::string& in_name, const std::string& out_name); }; int pcr (const std::string& filename, const std::string& outname, double time_offset_ms); #endif /* AUDIOWMARK_MPEGTS_HH */ audiowmark-0.6.5/src/peaq.sh000077500000000000000000000013611501136502400157370ustar00rootroot00000000000000# pseudo random pattern PATTERN=4e1243bd22c66e76c2ba9eddc1f91394e57f9f83 TRANSFORM=$1 peaq_print_scores() { awk ' BEGIN { odg = "*ERROR*"; di = "*ERROR*"; } /Objective Difference Grade:/ { odg = $NF; } /Distortion Index:/ { di = $NF; } END { print odg, di }' } for i in test/T* do audiowmark add "$i" t.wav $PATTERN $AWM_PARAMS >/dev/null audiowmark scale "$i" t_in.wav echo $i $(peaq t_in.wav t.wav | peaq_print_scores) done | awk 'BEGIN { max=-10; min=10; avg=0; count=0; } { if ($2 > max) max = $2; if ($2 < min) min = $2; avg += $2; count++; } END { avg /= count printf ("%7.3f %7.3f %7.3f\n", avg, min, max); }' audiowmark-0.6.5/src/random.cc000066400000000000000000000217051501136502400162450ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "random.hh" #include "utils.hh" #include #include using std::string; using std::vector; using std::regex; using std::regex_match; static void gcrypt_init() { static bool init_ok = false; if (!init_ok) { /* version check: start libgcrypt initialization */ if (!gcry_check_version (GCRYPT_VERSION)) { error ("audiowmark: libgcrypt version mismatch\n"); exit (1); } /* disable secure memory (assume we run in a controlled environment) */ gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* tell libgcrypt that initialization has completed */ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); init_ok = true; } } static constexpr auto GCRY_CIPHER = GCRY_CIPHER_AES128; static void uint64_to_buffer (uint64_t u, unsigned char *buffer) { /* this has to be endian independent: use big endian order */ buffer[0] = u >> 56; buffer[1] = u >> 48; buffer[2] = u >> 40; buffer[3] = u >> 32; buffer[4] = u >> 24; buffer[5] = u >> 16; buffer[6] = u >> 8; buffer[7] = u; } static uint64_t uint64_from_buffer (unsigned char *buffer) { /* this has to be endian independent: use big endian order */ return (uint64_t (buffer[0]) << 56) + (uint64_t (buffer[1]) << 48) + (uint64_t (buffer[2]) << 40) + (uint64_t (buffer[3]) << 32) + (uint64_t (buffer[4]) << 24) + (uint64_t (buffer[5]) << 16) + (uint64_t (buffer[6]) << 8) + buffer[7]; } #if 0 /* debugging only */ static void print (const string& label, const vector& data) { printf ("%s: ", label.c_str()); for (auto ch : data) printf ("%02x ", ch); printf ("\n"); } #endif Random::Random (const Key& key, uint64_t start_seed, Stream stream) { gcrypt_init(); gcry_error_t gcry_ret = gcry_cipher_open (&aes_ctr_cipher, GCRY_CIPHER, GCRY_CIPHER_MODE_CTR, 0); die_on_error ("gcry_cipher_open", gcry_ret); gcry_ret = gcry_cipher_setkey (aes_ctr_cipher, key.aes_key(), Key::SIZE); die_on_error ("gcry_cipher_setkey", gcry_ret); gcry_ret = gcry_cipher_open (&seed_cipher, GCRY_CIPHER, GCRY_CIPHER_MODE_ECB, 0); die_on_error ("gcry_cipher_open", gcry_ret); gcry_ret = gcry_cipher_setkey (seed_cipher, key.aes_key(), Key::SIZE); die_on_error ("gcry_cipher_setkey", gcry_ret); seed (start_seed, stream); } void Random::seed (uint64_t seed, Stream stream) { buffer_pos = 0; buffer.clear(); unsigned char plain_text[Key::SIZE]; unsigned char cipher_text[Key::SIZE]; memset (plain_text, 0, sizeof (plain_text)); uint64_to_buffer (seed, &plain_text[0]); plain_text[8] = uint8_t (stream); gcry_error_t gcry_ret = gcry_cipher_encrypt (seed_cipher, &cipher_text[0], Key::SIZE, &plain_text[0], Key::SIZE); die_on_error ("gcry_cipher_encrypt", gcry_ret); gcry_ret = gcry_cipher_setctr (aes_ctr_cipher, &cipher_text[0], Key::SIZE); die_on_error ("gcry_cipher_setctr", gcry_ret); } Random::~Random() { gcry_cipher_close (aes_ctr_cipher); gcry_cipher_close (seed_cipher); } void Random::refill_buffer() { const size_t block_size = 256; static unsigned char zeros[block_size] = { 0, }; unsigned char cipher_text[block_size]; gcry_error_t gcry_ret = gcry_cipher_encrypt (aes_ctr_cipher, cipher_text, block_size, zeros, block_size); die_on_error ("gcry_cipher_encrypt", gcry_ret); // print ("AES OUT", {cipher_text, cipher_text + block_size}); buffer.clear(); for (size_t i = 0; i < block_size; i += 8) buffer.push_back (uint64_from_buffer (cipher_text + i)); buffer_pos = 0; } void Random::die_on_error (const char *func, gcry_error_t err) { if (err) { error ("%s failed: %s/%s\n", func, gcry_strsource (err), gcry_strerror (err)); exit (1); /* can't recover here */ } } string Random::gen_key() { gcrypt_init(); vector key (16); gcry_randomize (&key[0], 16, /* long term key material strength */ GCRY_VERY_STRONG_RANDOM); return vec_to_hex_str (key); } uint64_t Random::seed_from_hash (const vector& floats) { unsigned char hash[20]; gcry_md_hash_buffer (GCRY_MD_SHA1, hash, &floats[0], floats.size() * sizeof (float)); return uint64_from_buffer (hash); } Key::Key() : m_aes_key (SIZE) { } Key::~Key() { std::fill (m_aes_key.begin(), m_aes_key.end(), 0); } void Key::set_test_key (uint64_t key) { uint64_to_buffer (key, m_aes_key.data()); m_name = string_printf ("test-key-%" PRId64, key); } static bool string_chars (char ch) { if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch == '.') || (ch == ':') || (ch == '=') || (ch == '/') || (ch == '-') || (ch == '_')) return true; return false; } static bool white_space (char ch) { return (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r'); } static bool tokenize (const string& line, vector& tokens) { enum { BLANK, STRING, QUOTED_STRING, QUOTED_STRING_ESCAPED, COMMENT } state = BLANK; string s; string xline = line + '\n'; tokens.clear(); for (string::const_iterator i = xline.begin(); i != xline.end(); i++) { if (state == BLANK && string_chars (*i)) { state = STRING; s += *i; } else if (state == BLANK && *i == '"') { state = QUOTED_STRING; } else if (state == BLANK && white_space (*i)) { // ignore more whitespaces if we've already seen one } else if (state == STRING && string_chars (*i)) { s += *i; } else if ((state == STRING && white_space (*i)) || (state == QUOTED_STRING && *i == '"')) { tokens.push_back (s); s = ""; state = BLANK; } else if (state == QUOTED_STRING && *i == '\\') { state = QUOTED_STRING_ESCAPED; } else if (state == QUOTED_STRING) { s += *i; } else if (state == QUOTED_STRING_ESCAPED) { s += *i; state = QUOTED_STRING; } else if (*i == '#') { state = COMMENT; } else if (state == COMMENT) { // ignore comments } else { return false; } } return state == BLANK || state == COMMENT; } void Key::load_key (const string& key_file) { FILE *f = fopen (key_file.c_str(), "r"); if (!f) { error ("audiowmark: error opening key file: '%s'\n", key_file.c_str()); exit (1); } m_name = key_file; // basename size_t sep = m_name.find_last_of ("\\/"); if (sep != string::npos) m_name = m_name.substr (sep + 1); char buffer[1024]; int line = 1; int keys = 0; while (fgets (buffer, 1024, f)) { vector tokens; bool parse_ok = false; if (tokenize (buffer, tokens)) { if (tokens.size() == 2 && tokens[0] == "key") /* line containing aes key */ { vector key = hex_str_to_vec (tokens[1]); if (key.size() != Key::SIZE) { error ("audiowmark: wrong key length in key file '%s', line %d\n => required key length is %zd bits\n", key_file.c_str(), line, Key::SIZE * 8); exit (1); } m_aes_key = key; keys++; parse_ok = true; } if (tokens.size() == 2 && tokens[0] == "name") /* key name */ { m_name = tokens[1]; parse_ok = true; } if (tokens.empty()) /* blank line or comment */ { parse_ok = true; } } if (!parse_ok) { error ("audiowmark: parse error in key file '%s', line %d\n", key_file.c_str(), line); exit (1); } line++; } fclose (f); if (keys > 1) { error ("audiowmark: key file '%s' contains more than one key\n", key_file.c_str()); exit (1); } if (keys == 0) { error ("audiowmark: key file '%s' contains no key\n", key_file.c_str()); exit (1); } } const unsigned char * Key::aes_key() const { assert (m_aes_key.size() == SIZE); return m_aes_key.data(); } const string& Key::name() const { return m_name; } audiowmark-0.6.5/src/random.hh000066400000000000000000000053171501136502400162600ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_RANDOM_HH #define AUDIOWMARK_RANDOM_HH #include #include #include #include #include class Key { std::vector m_aes_key; std::string m_name; public: static constexpr size_t SIZE = 16; /* 128 bits */ Key(); ~Key(); bool operator== (const Key& other) const { return (m_aes_key == other.m_aes_key) && (m_name == other.m_name); } void set_test_key (uint64_t key); void load_key (const std::string& filename); const unsigned char *aes_key() const; const std::string& name() const; }; class Random { public: enum class Stream { data_up_down = 1, sync_up_down = 2, speed_clip = 3, mix = 4, bit_order = 5, frame_position = 6 }; private: gcry_cipher_hd_t aes_ctr_cipher = nullptr; gcry_cipher_hd_t seed_cipher = nullptr; std::vector buffer; size_t buffer_pos = 0; std::uniform_real_distribution double_dist; void die_on_error (const char *func, gcry_error_t error); public: Random (const Key& key, uint64_t seed, Stream stream); ~Random(); typedef uint64_t result_type; result_type operator()() { if (buffer_pos == buffer.size()) refill_buffer(); return buffer[buffer_pos++]; } static constexpr result_type min() { return 0; } static constexpr result_type max() { return UINT64_MAX; } double random_double() /* range [0,1) */ { return double_dist (*this); } void refill_buffer(); void seed (uint64_t seed, Stream stream); template void shuffle (std::vector& result) { // Fisher–Yates shuffle for (size_t i = 0; i < result.size(); i++) { const uint64_t random_number = (*this)(); size_t j = i + random_number % (result.size() - i); std::swap (result[i], result[j]); } } static std::string gen_key(); static uint64_t seed_from_hash (const std::vector& floats); }; #endif /* AUDIOWMARK_RANDOM_HH */ audiowmark-0.6.5/src/rawconverter.cc000066400000000000000000000215241501136502400175050ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "rawconverter.hh" #include "config.h" #include #include #include #include using std::vector; RawConverter::~RawConverter() { } template class RawConverterImpl final : public RawConverter { public: void to_raw (const float *samples, unsigned char *bytes, size_t n_samples) override AUDIOWMARK_EXTRA_OPT; void from_raw (const unsigned char *bytes, float *samples, size_t n_samples) override AUDIOWMARK_EXTRA_OPT; }; template static RawConverter * create_with_bits_endian (const RawFormat& raw_format, Error& error) { switch (raw_format.encoding()) { case Encoding::SIGNED: return new RawConverterImpl(); case Encoding::UNSIGNED: return new RawConverterImpl(); case Encoding::FLOAT: return new RawConverterImpl(); } error = Error ("unsupported encoding"); return nullptr; } template static RawConverter * create_with_bits (const RawFormat& raw_format, Error& error) { switch (raw_format.endian()) { case RawFormat::LITTLE: return create_with_bits_endian (raw_format, error); case RawFormat::BIG: return create_with_bits_endian (raw_format, error); } error = Error ("unsupported endianness"); return nullptr; } RawConverter * RawConverter::create (const RawFormat& raw_format, Error& error) { error = Error::Code::NONE; if (raw_format.encoding() == Encoding::FLOAT) { switch (raw_format.bit_depth()) { case 32: return create_with_bits<32> (raw_format, error); case 64: return create_with_bits<64> (raw_format, error); default: error = Error (string_printf ("unsupported bit depth %d for float encoding", raw_format.bit_depth())); } } switch (raw_format.bit_depth()) { case 8: return create_with_bits<8> (raw_format, error); case 16: return create_with_bits<16> (raw_format, error); case 24: return create_with_bits<24> (raw_format, error); case 32: return create_with_bits<32> (raw_format, error); default: error = Error (string_printf ("unsupported bit depth %d for signed/unsigned encoding", raw_format.bit_depth())); } return nullptr; } template constexpr std::array make_endian_shift () { if (BIT_DEPTH == 8) { return { 24, -1, -1, -1 }; // same shift for big/little endian } if (BIT_DEPTH == 16) { if (ENDIAN == RawFormat::Endian::LITTLE) return { 16, 24, -1, -1 }; else return { 24, 16, -1, -1 }; } if (BIT_DEPTH == 24) { if (ENDIAN == RawFormat::Endian::LITTLE) return { 8, 16, 24, -1 }; else return { 24, 16, 8, -1 }; } if (BIT_DEPTH == 32) { if (ENDIAN == RawFormat::Endian::LITTLE) return { 0, 8, 16, 24 }; else return { 24, 16, 8, 0 }; } if (BIT_DEPTH == 64) // double encoding: code doesn't use endian shift array anyway return { 0, 0, 0, 0 }; } template static uint32_t to_endian (uint32_t i) { #ifdef WORDS_BIGENDDIAN constexpr bool native_endian = ENDIAN == RawFormat::BIG; #else constexpr bool native_endian = ENDIAN == RawFormat::LITTLE; #endif if (!native_endian) return bswap32 (i); else return i; } template static uint64_t to_endian (uint64_t i) { #ifdef WORDS_BIGENDDIAN constexpr bool native_endian = ENDIAN == RawFormat::BIG; #else constexpr bool native_endian = ENDIAN == RawFormat::LITTLE; #endif if (!native_endian) return bswap64 (i); else return i; } template void RawConverterImpl::to_raw (const float *samples, unsigned char *output_bytes, size_t n_samples) { constexpr int sample_width = BIT_DEPTH / 8; assert ((uintptr_t (output_bytes) & (sample_width - 1)) == 0); // ensure alignment for native access (16-bit, 32-bit and 64-bit version) if (ENCODING == Encoding::FLOAT) { for (size_t i = 0; i < n_samples; i++) { if (BIT_DEPTH == 32) { float f = float_clip (samples[i]); uint32_t out; std::memcpy (&out, &f, sample_width); ((uint32_t *) output_bytes)[i] = to_endian (out); } if (BIT_DEPTH == 64) { double d = float_clip (samples[i]); uint64_t out; std::memcpy (&out, &d, sample_width); ((uint64_t *) output_bytes)[i] = to_endian (out); } } } else { #ifdef WORDS_BIGENDDIAN constexpr bool native_endian = ENDIAN == RawFormat::BIG; #else constexpr bool native_endian = ENDIAN == RawFormat::LITTLE; #endif constexpr auto eshift = make_endian_shift(); unsigned char *ptr = output_bytes; for (size_t i = 0; i < n_samples; i++) { if (native_endian && ENCODING == Encoding::SIGNED && BIT_DEPTH == 32) ((int32_t *)ptr)[i] = float_to_int_clip<32> (samples[i]); else if (native_endian && ENCODING == Encoding::SIGNED && BIT_DEPTH == 16) ((int16_t *)ptr)[i] = float_to_int_clip<16> (samples[i]); else { int sample = float_to_int_clip<32> (samples[i]); if (ENCODING == Encoding::UNSIGNED) sample ^= 0x80000000; if (eshift[0] >= 0) ptr[0] = sample >> eshift[0]; if (eshift[1] >= 0) ptr[1] = sample >> eshift[1]; if (eshift[2] >= 0) ptr[2] = sample >> eshift[2]; if (eshift[3] >= 0) ptr[3] = sample >> eshift[3]; ptr += sample_width; } } } } template void RawConverterImpl::from_raw (const unsigned char *input_bytes, float *samples, size_t n_samples) { constexpr int sample_width = BIT_DEPTH / 8; assert ((uintptr_t (input_bytes) & (sample_width - 1)) == 0); // ensure alignment for native access (16-bit, 32-bit and 64-bit version) if (ENCODING == Encoding::FLOAT) { for (size_t i = 0; i < n_samples; i++) { if (BIT_DEPTH == 32) { uint32_t in = ((uint32_t *)input_bytes)[i]; in = to_endian (in); std::memcpy (samples + i, &in, BIT_DEPTH / 8); } if (BIT_DEPTH == 64) { uint64_t in = ((uint64_t *)input_bytes)[i]; in = to_endian (in); double d; std::memcpy (&d, &in, BIT_DEPTH / 8); samples[i] = d; } } } else { const unsigned char *ptr = input_bytes; constexpr auto eshift = make_endian_shift(); #ifdef WORDS_BIGENDDIAN constexpr bool native_endian = ENDIAN == RawFormat::BIG; #else constexpr bool native_endian = ENDIAN == RawFormat::LITTLE; #endif const float norm = 1.0 / 0x80000000LL; for (size_t i = 0; i < n_samples; i++) { if (native_endian && ENCODING == Encoding::SIGNED && BIT_DEPTH == 32) samples[i] = ((int32_t *)ptr)[i] * norm; else if (native_endian && ENCODING == Encoding::SIGNED && BIT_DEPTH == 16) samples[i] = ((int16_t *)ptr)[i] * float (1.0 / 0x8000); else { int s32 = 0; if (eshift[0] >= 0) s32 += ptr[0] << eshift[0]; if (eshift[1] >= 0) s32 += ptr[1] << eshift[1]; if (eshift[2] >= 0) s32 += ptr[2] << eshift[2]; if (eshift[3] >= 0) s32 += ptr[3] << eshift[3]; if (ENCODING == Encoding::UNSIGNED) s32 ^= 0x80000000; samples[i] = s32 * norm; ptr += sample_width; } } } } audiowmark-0.6.5/src/rawconverter.hh000066400000000000000000000033551501136502400175210ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_RAW_CONVERTER_HH #define AUDIOWMARK_RAW_CONVERTER_HH #include "rawinputstream.hh" class RawConverter { public: static RawConverter *create (const RawFormat& raw_format, Error& error); virtual ~RawConverter() = 0; virtual void to_raw (const float *samples, unsigned char *bytes, size_t n_samples) = 0; virtual void from_raw (const unsigned char *bytes, float *samples, size_t n_samples) = 0; }; template static inline int float_to_int_clip (float f) { const int64_t inorm = (1LL << (BITS - 1)); const float min_value = -inorm; const float max_value = inorm - 1; const float norm = inorm; const float snorm = f * norm; if (snorm >= max_value) return inorm - 1; else if (snorm <= min_value) return -inorm; else return snorm; } static inline float float_clip (float f) { const float min_value = -1; const float max_value = 1; if (f >= max_value) return max_value; else if (f <= min_value) return min_value; else return f; } #endif /* AUDIOWMARK_RAW_CONVERTER_HH */ audiowmark-0.6.5/src/rawinputstream.cc000066400000000000000000000070271501136502400200530ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "rawinputstream.hh" #include "rawconverter.hh" #include #include #include using std::string; using std::vector; RawFormat::RawFormat() { } RawFormat::RawFormat (int n_channels, int sample_rate, int bit_depth) : m_n_channels (n_channels), m_sample_rate (sample_rate), m_bit_depth (bit_depth) { } void RawFormat::set_channels (int channels) { m_n_channels = channels; } void RawFormat::set_sample_rate (int rate) { m_sample_rate = rate; } void RawFormat::set_bit_depth (int bits) { m_bit_depth = bits; } void RawFormat::set_endian (Endian endian) { m_endian = endian; } void RawFormat::set_encoding (Encoding encoding) { m_encoding = encoding; } RawInputStream::~RawInputStream() { close(); } Error RawInputStream::open (const string& filename, const RawFormat& format) { assert (m_state == State::NEW); if (!format.n_channels()) return Error ("RawInputStream: input format: missing number of channels"); if (!format.bit_depth()) return Error ("RawInputStream: input format: missing bit depth"); if (!format.sample_rate()) return Error ("RawInputStream: input format: missing sample rate"); Error err = Error::Code::NONE; m_raw_converter.reset (RawConverter::create (format, err)); if (err) return err; if (filename == "-") { m_input_file = stdin; m_close_file = false; } else { m_input_file = fopen (filename.c_str(), "r"); if (!m_input_file) return Error (strerror (errno)); m_close_file = true; } m_format = format; m_state = State::OPEN; return Error::Code::NONE; } int RawInputStream::sample_rate() const { return m_format.sample_rate(); } int RawInputStream::bit_depth() const { return m_format.bit_depth(); } size_t RawInputStream::n_frames() const { return N_FRAMES_UNKNOWN; } int RawInputStream::n_channels() const { return m_format.n_channels(); } Encoding RawInputStream::encoding() const { return m_format.encoding(); } Error RawInputStream::read_frames (vector& samples, size_t count) { assert (m_state == State::OPEN); const int n_channels = m_format.n_channels(); const int sample_width = m_format.bit_depth() / 8; vector input_bytes (count * n_channels * sample_width); size_t r_count = fread (input_bytes.data(), n_channels * sample_width, count, m_input_file); if (ferror (m_input_file)) return Error ("error reading sample data"); samples.resize (r_count * n_channels); m_raw_converter->from_raw (input_bytes.data(), samples.data(), r_count * n_channels); return Error::Code::NONE; } void RawInputStream::close() { if (m_state == State::OPEN) { if (m_close_file && m_input_file) { fclose (m_input_file); m_input_file = nullptr; m_close_file = false; } m_state = State::CLOSED; } } audiowmark-0.6.5/src/rawinputstream.hh000066400000000000000000000045201501136502400200600ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_RAW_INPUT_STREAM_HH #define AUDIOWMARK_RAW_INPUT_STREAM_HH #include #include #include #include "audiostream.hh" class RawFormat { public: enum Endian { LITTLE, BIG }; private: int m_n_channels = 2; int m_sample_rate = 0; int m_bit_depth = 16; Endian m_endian = LITTLE; Encoding m_encoding = Encoding::SIGNED; public: RawFormat(); RawFormat (int n_channels, int sample_rate, int bit_depth); int n_channels() const { return m_n_channels; } int sample_rate() const { return m_sample_rate; } int bit_depth() const { return m_bit_depth; } Endian endian() const { return m_endian; } Encoding encoding() const { return m_encoding; } void set_channels (int channels); void set_sample_rate (int rate); void set_bit_depth (int bits); void set_endian (Endian endian); void set_encoding (Encoding encoding); }; class RawConverter; class RawInputStream : public AudioInputStream { enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; RawFormat m_format; FILE *m_input_file = nullptr; bool m_close_file = false; std::unique_ptr m_raw_converter; public: ~RawInputStream(); Error open (const std::string& filename, const RawFormat& format); Error read_frames (std::vector& samples, size_t count) override; void close(); int bit_depth() const override; int sample_rate() const override; size_t n_frames() const override; int n_channels() const override; Encoding encoding() const override; }; #endif /* AUDIOWMARK_RAW_INPUT_STREAM_HH */ audiowmark-0.6.5/src/rawoutputstream.cc000066400000000000000000000056711501136502400202570ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "rawoutputstream.hh" #include #include #include using std::string; using std::vector; RawOutputStream::~RawOutputStream() { close(); } Error RawOutputStream::open (const string& filename, const RawFormat& format) { assert (m_state == State::NEW); if (!format.n_channels()) return Error ("RawOutputStream: output format: missing number of channels"); if (!format.bit_depth()) return Error ("RawOutputStream: output format: missing bit depth"); if (!format.sample_rate()) return Error ("RawOutputStream: output format: missing sample rate"); Error err = Error::Code::NONE; m_raw_converter.reset (RawConverter::create (format, err)); if (err) return err; if (filename == "-") { m_output_file = stdout; m_close_file = false; } else { m_output_file = fopen (filename.c_str(), "w"); if (!m_output_file) return Error (strerror (errno)); m_close_file = true; } m_format = format; m_state = State::OPEN; return Error::Code::NONE; } int RawOutputStream::sample_rate() const { return m_format.sample_rate(); } int RawOutputStream::bit_depth() const { return m_format.bit_depth(); } int RawOutputStream::n_channels() const { return m_format.n_channels(); } Error RawOutputStream::write_frames (const vector& samples) { assert (m_state == State::OPEN); if (samples.empty()) return Error::Code::NONE; vector bytes (samples.size() * m_format.bit_depth() / 8); m_raw_converter->to_raw (samples.data(), bytes.data(), samples.size()); fwrite (&bytes[0], 1, bytes.size(), m_output_file); if (ferror (m_output_file)) return Error ("write sample data failed"); return Error::Code::NONE; } Error RawOutputStream::close() { if (m_state == State::OPEN) { if (m_output_file) { fflush (m_output_file); if (ferror (m_output_file)) return Error ("error during flush"); } if (m_close_file && m_output_file) { if (fclose (m_output_file) != 0) return Error ("error during close"); m_output_file = nullptr; m_close_file = false; } m_state = State::CLOSED; } return Error::Code::NONE; } audiowmark-0.6.5/src/rawoutputstream.hh000066400000000000000000000027411501136502400202640ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_RAW_OUTPUT_STREAM_HH #define AUDIOWMARK_RAW_OUTPUT_STREAM_HH #include "rawinputstream.hh" #include "rawconverter.hh" #include class RawOutputStream : public AudioOutputStream { enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; RawFormat m_format; FILE *m_output_file = nullptr; bool m_close_file = false; std::unique_ptr m_raw_converter; public: ~RawOutputStream(); int bit_depth() const override; int sample_rate() const override; int n_channels() const override; Error open (const std::string& filename, const RawFormat& format); Error write_frames (const std::vector& frames) override; Error close() override; }; #endif /* AUDIOWMARK_RAW_OUTPUT_STREAM_HH */ audiowmark-0.6.5/src/resample.cc000066400000000000000000000205461501136502400165770ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "resample.hh" #include "wmcommon.hh" #include #include #include #include using std::vector; using std::min; template static void process_resampler (R& resampler, const float *in, size_t in_size, float *out, size_t out_size) { resampler.out_count = out_size / resampler.nchan(); resampler.out_data = out; /* avoid timeshift: zita needs k/2 - 1 samples before the actual input */ resampler.inp_count = resampler.inpsize () / 2 - 1; resampler.inp_data = nullptr; resampler.process(); resampler.inp_count = in_size / resampler.nchan(); resampler.inp_data = (float *) in; resampler.process(); /* zita needs k/2 samples after the actual input */ resampler.inp_count = resampler.inpsize() / 2; resampler.inp_data = nullptr; resampler.process(); } WavData resample (const WavData& wav_data, int rate) { /* in our application, resampling should only be called if it is necessary * since using the resampler with input rate == output rate would be slow */ assert (rate != wav_data.sample_rate()); const int hlen = 16; const double ratio = double (rate) / wav_data.sample_rate(); const vector& in = wav_data.samples(); WavData wav_data_out ({}, wav_data.n_channels(), rate, wav_data.bit_depth()); vector& out_ref = wav_data_out.mutable_samples(); out_ref.resize (lrint (in.size() / wav_data.n_channels() * ratio) * wav_data.n_channels()); /* zita-resampler provides two resampling algorithms * * a fast optimized version: Resampler * this is an optimized version, which works for many common cases, * like resampling between 22050, 32000, 44100, 48000, 96000 Hz * * a slower version: VResampler * this works for arbitary rates (like 33333 -> 44100 resampling) * * so we try using Resampler, and if that fails fall back to VResampler */ Resampler resampler; if (resampler.setup (wav_data.sample_rate(), rate, wav_data.n_channels(), hlen) == 0) { process_resampler (resampler, in.data(), in.size(), out_ref.data(), out_ref.size()); return wav_data_out; } VResampler vresampler; if (vresampler.setup (ratio, wav_data.n_channels(), hlen) == 0) { process_resampler (vresampler, in.data(), in.size(), out_ref.data(), out_ref.size()); return wav_data_out; } error ("audiowmark: resampling from rate %d to rate %d not supported.\n", wav_data.sample_rate(), rate); exit (1); } WavData resample_ratio_truncate (const WavData& wav_data, double ratio, int new_rate, double max_in_seconds) { const int hlen = 16; const vector& in = wav_data.samples(); size_t in_size_truncate = in.size(); if (max_in_seconds > 0) in_size_truncate = min (in_size_truncate, wav_data.n_channels() * lrint (wav_data.sample_rate() * max_in_seconds)); WavData wav_data_out ({}, wav_data.n_channels(), new_rate, wav_data.bit_depth()); vector& out_ref = wav_data_out.mutable_samples(); out_ref.resize (lrint (in_size_truncate / wav_data.n_channels() * ratio) * wav_data.n_channels()); VResampler vresampler; if (vresampler.setup (ratio, wav_data.n_channels(), hlen) != 0) { error ("audiowmark: failed to setup vresampler with ratio=%f\n", ratio); exit (1); } process_resampler (vresampler, in.data(), in_size_truncate, out_ref.data(), out_ref.size()); return wav_data_out; } WavData resample_ratio (const WavData& wav_data, double ratio, int new_rate) { return resample_ratio_truncate (wav_data, ratio, new_rate, /* no truncation */ -1); } template class BufferedResamplerImpl : public ResamplerImpl { const int n_channels = 0; const int old_rate = 0; const int new_rate = 0; bool first_write = true; Resampler m_resampler; vector buffer; public: BufferedResamplerImpl (int n_channels, int old_rate, int new_rate) : n_channels (n_channels), old_rate (old_rate), new_rate (new_rate) { } Resampler& resampler() { return m_resampler; } size_t skip (size_t zeros) { /* skipping a whole 1 second block should end in the same resampler state we had at the beginning */ size_t seconds = 0; if (zeros >= Params::frame_size) seconds = (zeros - Params::frame_size) / old_rate; const size_t extra = new_rate * seconds; zeros -= old_rate * seconds; write_frames (vector (zeros * n_channels)); size_t out = can_read_frames() + extra; out -= out % Params::frame_size; /* always skip whole frames */ read_frames (out - extra); return out; } void write_frames (const vector& frames) { if (first_write) { /* avoid timeshift: zita needs k/2 - 1 samples before the actual input */ m_resampler.inp_count = m_resampler.inpsize () / 2 - 1; m_resampler.inp_data = nullptr; m_resampler.out_count = 1000000; // <- just needs to be large enough that all input is consumed m_resampler.out_data = nullptr; m_resampler.process(); first_write = false; } uint start = 0; while (start != frames.size() / n_channels) { const int out_count = Params::frame_size; float out[out_count * n_channels]; m_resampler.out_count = out_count; m_resampler.out_data = out; m_resampler.inp_count = frames.size() / n_channels - start; m_resampler.inp_data = const_cast (&frames[start * n_channels]); m_resampler.process(); size_t count = out_count - m_resampler.out_count; buffer.insert (buffer.end(), out, out + count * n_channels); start = frames.size() / n_channels - m_resampler.inp_count; } } void write_trailing_frames() { /* zita resampler needs k/2 samples after actual input */ std::vector samples ((m_resampler.inpsize() / 2) * n_channels); write_frames (samples); } vector read_frames (size_t frames) { assert (frames * n_channels <= buffer.size()); const auto begin = buffer.begin(); const auto end = begin + frames * n_channels; vector result (begin, end); buffer.erase (begin, end); return result; } size_t can_read_frames() const { return buffer.size() / n_channels; } }; ResamplerImpl * ResamplerImpl::create (int n_channels, int old_rate, int new_rate) { if (old_rate == new_rate) { return nullptr; // should not be using create_resampler for that case } else { /* zita-resampler provides two resampling algorithms * * a fast optimized version: Resampler * this is an optimized version, which works for many common cases, * like resampling between 22050, 32000, 44100, 48000, 96000 Hz * * a slower version: VResampler * this works for arbitary rates (like 33333 -> 44100 resampling) * * so we try using Resampler, and if that fails fall back to VResampler */ const int hlen = 16; auto resampler = new BufferedResamplerImpl (n_channels, old_rate, new_rate); if (resampler->resampler().setup (old_rate, new_rate, n_channels, hlen) == 0) { return resampler; } else delete resampler; auto vresampler = new BufferedResamplerImpl (n_channels, old_rate, new_rate); const double ratio = double (new_rate) / old_rate; if (vresampler->resampler().setup (ratio, n_channels, hlen) == 0) { return vresampler; } else { error ("audiowmark: resampling from old_rate=%d to new_rate=%d not implemented\n", old_rate, new_rate); delete vresampler; return nullptr; } } } audiowmark-0.6.5/src/resample.hh000066400000000000000000000027701501136502400166100ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_RESAMPLE_HH #define AUDIOWMARK_RESAMPLE_HH #include "wavdata.hh" WavData resample (const WavData& wav_data, int rate); WavData resample_ratio (const WavData& wav_data, double ratio, int new_rate); WavData resample_ratio_truncate (const WavData& wav_data, double ratio, int new_rate, double max_seconds); class ResamplerImpl { public: virtual ~ResamplerImpl() { } virtual size_t skip (size_t zeros) = 0; virtual void write_frames (const std::vector& frames) = 0; virtual void write_trailing_frames() = 0; virtual std::vector read_frames (size_t frames) = 0; virtual size_t can_read_frames() const = 0; static ResamplerImpl *create (int n_channels, int old_rate, int new_rate); }; #endif /* AUDIOWMARK_RESAMPLE_HH */ audiowmark-0.6.5/src/seed-test.sh000077500000000000000000000007371501136502400167140ustar00rootroot00000000000000SEEDS="$1" MAX_SEED=$(($SEEDS - 1)) P1="$2" P2="$3" shift 3 echo "n seeds : $SEEDS" echo "ber-test args : $@" echo "left : $P1" echo "right : $P2" for seed in $(seq 0 $MAX_SEED) do echo $(AWM_SEEDS=$seed AWM_PARAMS="$P1" ber-test.sh "$@") $(AWM_SEEDS=$seed AWM_PARAMS="$P2" ber-test.sh "$@") done | awk '{a += $1; if ($2 > b) b = $2; c += $3; if ($4 > d) d = $4; n++; } {printf ("%.5f %.5f - %.5f %.5f - (((%s)))\n", a/n, b, c/n, d, $0);}' audiowmark-0.6.5/src/sfinputstream.cc000066400000000000000000000203471501136502400176720ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "sfinputstream.hh" #include #include #include #include using std::string; using std::vector; SFInputStream::~SFInputStream() { close(); } Error SFInputStream::open (const string& filename) { return open ([&] (SF_INFO *sfinfo) { if (filename == "-") { m_is_stdin = true; return sf_open_fd (STDIN_FILENO, SFM_READ, sfinfo, /* close fd */ SF_FALSE); } else { return sf_open (filename.c_str(), SFM_READ, sfinfo); } }); } Error SFInputStream::open (std::function open_func) { assert (m_state == State::NEW); SF_INFO sfinfo = { 0, }; m_sndfile = open_func (&sfinfo); int error = sf_error (m_sndfile); if (error) { Error err (sf_strerror (m_sndfile)); if (m_sndfile) { m_sndfile = nullptr; sf_close (m_sndfile); } return err; } m_n_channels = sfinfo.channels; m_n_frames = (sfinfo.frames == SF_COUNT_MAX) ? N_FRAMES_UNKNOWN : sfinfo.frames; m_sample_rate = sfinfo.samplerate; switch (sfinfo.format & SF_FORMAT_SUBMASK) { case SF_FORMAT_PCM_U8: m_bit_depth = 8; m_encoding = Encoding::UNSIGNED; break; case SF_FORMAT_PCM_S8: m_bit_depth = 8; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_PCM_16: m_bit_depth = 16; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_PCM_24: m_bit_depth = 24; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_PCM_32: m_bit_depth = 32; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_FLOAT: m_bit_depth = 32; m_encoding = Encoding::FLOAT; break; case SF_FORMAT_DOUBLE: m_bit_depth = 64; m_encoding = Encoding::FLOAT; break; case SF_FORMAT_W64: m_bit_depth = 64; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_ALAC_16: m_bit_depth = 16; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_ALAC_20: m_bit_depth = 20; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_ALAC_24: m_bit_depth = 24; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_ALAC_32: m_bit_depth = 32; m_encoding = Encoding::SIGNED; break; case SF_FORMAT_OPUS: case SF_FORMAT_VORBIS: case SF_FORMAT_MPEG_LAYER_I: case SF_FORMAT_MPEG_LAYER_II: case SF_FORMAT_MPEG_LAYER_III: m_bit_depth = 24; m_encoding = Encoding::SIGNED; break; default: warning ("audiowmark: unknown input stream subformat %x, estimated bit_depth is 32\n", sfinfo.format & SF_FORMAT_SUBMASK); m_bit_depth = 32; /* unknown */ } m_state = State::OPEN; return Error::Code::NONE; } int SFInputStream::sample_rate() const { return m_sample_rate; } int SFInputStream::bit_depth() const { return m_bit_depth; } Encoding SFInputStream::encoding() const { return m_encoding; } Error SFInputStream::read_frames (vector& samples, size_t count) { assert (m_state == State::OPEN); if (m_encoding == Encoding::FLOAT) /* float or double input */ { samples.resize (count * m_n_channels); sf_count_t r_count = sf_readf_float (m_sndfile, &samples[0], count); if (sf_error (m_sndfile)) return Error (sf_strerror (m_sndfile)); samples.resize (r_count * m_n_channels); } else /* integer input */ { vector isamples (count * m_n_channels); sf_count_t r_count = sf_readf_int (m_sndfile, &isamples[0], count); if (sf_error (m_sndfile)) return Error (sf_strerror (m_sndfile)); /* reading a wav file and saving it again with the libsndfile float API will * change some values due to normalization issues: * http://www.mega-nerd.com/libsndfile/FAQ.html#Q010 * * to avoid the problem, we use the int API and do the conversion beween int * and float manually - the important part is that the normalization factors * used during read and write are identical */ samples.resize (r_count * m_n_channels); const float norm = 1.0 / 0x80000000LL; for (size_t i = 0; i < samples.size(); i++) samples[i] = isamples[i] * norm; } return Error::Code::NONE; } void SFInputStream::close() { if (m_state == State::OPEN) { assert (m_sndfile); sf_close (m_sndfile); m_sndfile = nullptr; m_state = State::CLOSED; if (m_is_stdin) { /* WAV files can contain additional RIFF chunks after the end of the 'data' chunk (issue #19). * -> skip the rest of stdin to avoid SIGPIPE for the process writing to the pipe */ ssize_t count; do { char junk[16 * 1024]; count = read (STDIN_FILENO, junk, sizeof (junk)) ; } while (count > 0 || (count == -1 && errno == EINTR)); } } } static sf_count_t virtual_get_len (void *data) { SFVirtualData *vdata = static_cast (data); return vdata->mem->size(); } static sf_count_t virtual_seek (sf_count_t offset, int whence, void *data) { SFVirtualData *vdata = static_cast (data); if (whence == SEEK_CUR) { vdata->offset = vdata->offset + offset; } else if (whence == SEEK_SET) { vdata->offset = offset; } else if (whence == SEEK_END) { vdata->offset = vdata->mem->size() + offset; } /* can't seek beyond eof */ vdata->offset = bound (0, vdata->offset, vdata->mem->size()); return vdata->offset; } static sf_count_t virtual_read (void *ptr, sf_count_t count, void *data) { SFVirtualData *vdata = static_cast (data); int rcount = 0; if (size_t (vdata->offset + count) <= vdata->mem->size()) { /* fast case: read can be fully satisfied with the data we have */ memcpy (ptr, &(*vdata->mem)[vdata->offset], count); rcount = count; } else { unsigned char *uptr = static_cast (ptr); for (sf_count_t i = 0; i < count; i++) { size_t rpos = i + vdata->offset; if (rpos < vdata->mem->size()) { uptr[i] = (*vdata->mem)[rpos]; rcount++; } } } vdata->offset += rcount; return rcount; } static sf_count_t virtual_write (const void *ptr, sf_count_t count, void *data) { SFVirtualData *vdata = static_cast (data); const unsigned char *uptr = static_cast (ptr); for (sf_count_t i = 0; i < count; i++) { unsigned char ch = uptr[i]; size_t wpos = i + vdata->offset; if (wpos >= vdata->mem->size()) vdata->mem->resize (wpos + 1); (*vdata->mem)[wpos] = ch; } vdata->offset += count; return count; } static sf_count_t virtual_tell (void *data) { SFVirtualData *vdata = static_cast (data); return vdata->offset; } SFVirtualData::SFVirtualData() : io { virtual_get_len, virtual_seek, virtual_read, virtual_write, virtual_tell } { } Error SFInputStream::open (const vector *data) { m_virtual_data.mem = const_cast *> (data); return open ([&] (SF_INFO *sfinfo) { return sf_open_virtual (&m_virtual_data.io, SFM_READ, sfinfo, &m_virtual_data); }); } audiowmark-0.6.5/src/sfinputstream.hh000066400000000000000000000041711501136502400177010ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_SF_INPUT_STREAM_HH #define AUDIOWMARK_SF_INPUT_STREAM_HH #include #include #include #include "audiostream.hh" /* to support virtual io read/write from/to memory */ struct SFVirtualData { SFVirtualData(); std::vector *mem = nullptr; sf_count_t offset = 0; SF_VIRTUAL_IO io; }; class SFInputStream : public AudioInputStream { private: SFVirtualData m_virtual_data; SNDFILE *m_sndfile = nullptr; int m_n_channels = 0; size_t m_n_frames = 0; int m_bit_depth = 0; int m_sample_rate = 0; Encoding m_encoding = Encoding::SIGNED; bool m_is_stdin = false; enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; Error open (std::function open_func); public: ~SFInputStream(); Error open (const std::string& filename); Error open (const std::vector *data); Error read_frames (std::vector& samples, size_t count) override AUDIOWMARK_EXTRA_OPT; void close(); int n_channels() const override { return m_n_channels; } int sample_rate() const override; int bit_depth() const override; Encoding encoding() const override; size_t n_frames() const override { return m_n_frames; } }; #endif /* AUDIOWMARK_SF_INPUT_STREAM_HH */ audiowmark-0.6.5/src/sfoutputstream.cc000066400000000000000000000113061501136502400200660ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "sfoutputstream.hh" #include "rawconverter.hh" #include "utils.hh" #include #include using std::string; using std::vector; SFOutputStream::~SFOutputStream() { close(); } Error SFOutputStream::open (const string& filename, int n_channels, int sample_rate, int bit_depth, Encoding encoding, OutFormat out_format) { return open ([&] (SF_INFO *sfinfo) { return sf_open (filename.c_str(), SFM_WRITE, sfinfo); }, n_channels, sample_rate, bit_depth, encoding, out_format); } Error SFOutputStream::open (std::function open_func, int n_channels, int sample_rate, int bit_depth, Encoding encoding, OutFormat out_format) { assert (m_state == State::NEW); m_sample_rate = sample_rate; m_n_channels = n_channels; SF_INFO sfinfo = {0,}; sfinfo.samplerate = sample_rate; sfinfo.channels = n_channels; if (out_format == OutFormat::FLAC) { sfinfo.format = SF_FORMAT_FLAC; if (bit_depth > 16) { sfinfo.format |= SF_FORMAT_PCM_24; m_bit_depth = 24; } else { sfinfo.format |= SF_FORMAT_PCM_16; m_bit_depth = 16; } } else { switch (out_format) { case OutFormat::WAV: sfinfo.format = SF_FORMAT_WAV; break; case OutFormat::RF64: sfinfo.format = SF_FORMAT_RF64; break; default: assert (false); } if (encoding == Encoding::FLOAT) { if (bit_depth == 64) sfinfo.format |= SF_FORMAT_DOUBLE; else sfinfo.format |= SF_FORMAT_FLOAT; m_write_float_data = true; } else { if (bit_depth > 24) { sfinfo.format |= SF_FORMAT_PCM_32; m_bit_depth = 32; } else if (bit_depth > 16) { sfinfo.format |= SF_FORMAT_PCM_24; m_bit_depth = 24; } else { sfinfo.format |= SF_FORMAT_PCM_16; m_bit_depth = 16; } } } m_sndfile = open_func (&sfinfo); int error = sf_error (m_sndfile); if (error) { string msg = sf_strerror (m_sndfile); if (m_sndfile) sf_close (m_sndfile); return Error (msg); } m_state = State::OPEN; return Error::Code::NONE; } Error SFOutputStream::close() { if (m_state == State::OPEN) { assert (m_sndfile); if (sf_close (m_sndfile)) return Error ("sf_close returned an error"); m_state = State::CLOSED; } return Error::Code::NONE; } Error SFOutputStream::write_frames (const vector& samples) { sf_count_t frames = samples.size() / m_n_channels; sf_count_t count; if (m_write_float_data) { vector fsamples (samples.size()); for (size_t i = 0; i < samples.size(); i++) fsamples[i] = float_clip (samples[i]); count = sf_writef_float (m_sndfile, fsamples.data(), frames); } else { vector isamples (samples.size()); for (size_t i = 0; i < samples.size(); i++) isamples[i] = float_to_int_clip<32> (samples[i]); count = sf_writef_int (m_sndfile, isamples.data(), frames); } if (sf_error (m_sndfile)) return Error (sf_strerror (m_sndfile)); if (count != frames) return Error ("writing sample data failed: short write"); return Error::Code::NONE; } int SFOutputStream::bit_depth() const { return m_bit_depth; } int SFOutputStream::sample_rate() const { return m_sample_rate; } int SFOutputStream::n_channels() const { return m_n_channels; } Error SFOutputStream::open (vector *data, int n_channels, int sample_rate, int bit_depth, Encoding encoding, OutFormat out_format) { m_virtual_data.mem = data; return open ([&] (SF_INFO *sfinfo) { return sf_open_virtual (&m_virtual_data.io, SFM_WRITE, sfinfo, &m_virtual_data); }, n_channels, sample_rate, bit_depth, encoding, out_format); } audiowmark-0.6.5/src/sfoutputstream.hh000066400000000000000000000040431501136502400201000ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_SF_OUTPUT_STREAM_HH #define AUDIOWMARK_SF_OUTPUT_STREAM_HH #include #include #include "audiostream.hh" #include "sfinputstream.hh" #include "rawinputstream.hh" class SFOutputStream : public AudioOutputStream { public: enum class OutFormat { WAV, RF64, FLAC }; private: SFVirtualData m_virtual_data; SNDFILE *m_sndfile = nullptr; int m_bit_depth = 0; int m_sample_rate = 0; int m_n_channels = 0; bool m_write_float_data = false; enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; Error open (std::function open_func, int n_channels, int sample_rate, int bit_depth, Encoding encoding, OutFormat out_format); public: ~SFOutputStream(); Error open (const std::string& filename, int n_channels, int sample_rate, int bit_depth, Encoding encoding, OutFormat out_format = OutFormat::WAV); Error open (std::vector *data, int n_channels, int sample_rate, int bit_depth, Encoding encoding, OutFormat out_format = OutFormat::WAV); Error write_frames (const std::vector& frames) override AUDIOWMARK_EXTRA_OPT; Error close() override; int bit_depth() const override; int sample_rate() const override; int n_channels() const override; }; #endif /* AUDIOWMARK_SF_OUTPUT_STREAM_HH */ audiowmark-0.6.5/src/short.mk000066400000000000000000000030311501136502400161360ustar00rootroot00000000000000all: short-60 short-60-mp3 \ short-50 short-50-mp3 \ short-40 short-40-mp3 \ short-30 short-30-mp3 \ short-20 short-20-mp3 \ short-10 short-10-mp3 short-60: AWM_FILE=t-short-60 AWM_CLIP=60 fer-test.sh 10 "" > tmp-short-60 mv tmp-short-60 short-60 short-60-mp3: AWM_FILE=t-short-60-mp3 AWM_CLIP=60 fer-test.sh 10 "" mp3 128 > tmp-short-60-mp3 mv tmp-short-60-mp3 short-60-mp3 short-50: AWM_FILE=t-short-50 AWM_CLIP=50 fer-test.sh 10 "" > tmp-short-50 mv tmp-short-50 short-50 short-50-mp3: AWM_FILE=t-short-50-mp3 AWM_CLIP=50 fer-test.sh 10 "" mp3 128 > tmp-short-50-mp3 mv tmp-short-50-mp3 short-50-mp3 short-40: AWM_FILE=t-short-40 AWM_CLIP=40 fer-test.sh 10 "" > tmp-short-40 mv tmp-short-40 short-40 short-40-mp3: AWM_FILE=t-short-40-mp3 AWM_CLIP=40 fer-test.sh 10 "" mp3 128 > tmp-short-40-mp3 mv tmp-short-40-mp3 short-40-mp3 short-30: AWM_FILE=t-short-30 AWM_CLIP=30 fer-test.sh 10 "" > tmp-short-30 mv tmp-short-30 short-30 short-30-mp3: AWM_FILE=t-short-30-mp3 AWM_CLIP=30 fer-test.sh 10 "" mp3 128 > tmp-short-30-mp3 mv tmp-short-30-mp3 short-30-mp3 short-20: AWM_FILE=t-short-20 AWM_CLIP=20 fer-test.sh 10 "" > tmp-short-20 mv tmp-short-20 short-20 short-20-mp3: AWM_FILE=t-short-20-mp3 AWM_CLIP=20 fer-test.sh 10 "" mp3 128 > tmp-short-20-mp3 mv tmp-short-20-mp3 short-20-mp3 short-10: AWM_FILE=t-short-10 AWM_CLIP=10 fer-test.sh 10 "" > tmp-short-10 mv tmp-short-10 short-10 short-10-mp3: AWM_FILE=t-short-10-mp3 AWM_CLIP=10 fer-test.sh 10 "" mp3 128 > tmp-short-10-mp3 mv tmp-short-10-mp3 short-10-mp3 audiowmark-0.6.5/src/shortcode.cc000066400000000000000000000255341501136502400167630ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "shortcode.hh" #include "wmcommon.hh" #include using std::vector; /* Codes from codetables.de / magma online calculator BKLC (GF(2), N, K) */ static vector> block_65_20_20 = { {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,0,1,1,1,0,1,1,1}, {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,1,0,1,0,0,0,1,1,0,1,0,0,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0,1,1,1,0,1,1,1,1,1,1}, {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,1,0,1,1,0,1,0,0,0,0,1,1,1,1,0,0,1,0,1,1,0,0,1,1,1,0,1,0,1,0,1,1,0,1,1}, {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,1,0,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,1,0,0,1}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,1,1,0,1,0,0,1,1,1,1,0,0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,1,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,0,0,0,1,1,0,1,1,0,1,0,0,0,0,1,1,1,0,0,0,1,0,1,1,0,0,1,1,1,0,1,0,1,0,1,1}, {0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1,0,1,0,1,1,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,1}, {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,0,0,0,1,0,0,0,1,0,1,1,0,1,0,0,1,0,0,0,0,0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,1,0}, {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,1,0,1,1,1,1,1,0,0,1,0,1,1,1,0,1,1,1,1,1,1,0,1,0,0,0,1,0,1,1,0,0,1,0,1,1,1,0}, {0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,0,1,1,1,0,1,1,0,0,1,0,1,1,0,1,1,1,0,1,1,0,0,1,0,1,1,1,0,0,1,1,1,1,0,1,0,0}, {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,1,0,1,0,0,1,1,1,1,0,0,1,0,0,0,1,0,1,0,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1}, {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1,0,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,0,1,0,0,1,0,0,0,0,0,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,1,1,1,1,0,0,1,0,1,0,1,1,1,0,1,1,0,0,1,0,0,1,1,0,0,1,0,0,0,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0,0,1,1,1,1,0,1,0,1,0,1,0,0,0,0,0,1,1,1,0,1,1,1,1,1,1,0,0,1,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,1,1,0,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,0,0,0,1,0,0,1,0,0,0,1,0,1,0,0,1,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,0,1,1,0,1,0,1,1,0,0,0,1,0,1,1,1,1,0,0,1,1,1,1,1,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,1,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,1,1,0,1,1,1,0,0,1,0,0,0,0,1,0,0,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1,1,1,1,0,1,1,1,0,0,1,0,0,0,0,1}, }; static vector> block_61_16_21 = { {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,1,0,0,0,0,0,1,1,0,0,1,0,0,1,0,0}, {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,1,0,0,0,0,0,1,1,0,0,1,0,0,1,0}, {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,0,0,1,0,0,0,0,1,0,1,0,0,0,0,0,1,1,0,0,1,0,0,1}, {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,1,1,0,1,0,0,1,0,1,0,0,0,1,0,1,1,1,1,1,0,1,0,0,1,1,1,1,1,1,0,0,1,1,1,0,0,1}, {0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,0,1,1,1,1,1,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,0,1,1,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1}, {0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,0,1,0,1,1,1,1,1,0,0,1,0,1,0,1,1,0,1,1,0,0,0,0,1,0,0,1,0,1,1,1,0,0,1,1,1,1,0,1}, {0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,0,0,0,1,1,0,0,0,1,1,0,1,0,0,0,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,1,1,0,0,0,0,1,1}, {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,0,1,1,1,1,0,0}, {0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,0,1,1,1,1,0}, {0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,1,0,1,0,0,1,1,0,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1,0,0,1,1,1,1}, {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,1,0,0,0,1,0,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1,0,1,0}, {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,0,1,0,0,0,1,0,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,1,1,1,1,1,0,1}, {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,1,0,0,1,1,1,1,0,0,0,0,0,1,0,1,1,0,0,0,1,1,1,1,0,0,0,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,0,1,0,1,1,0,1,1,1,1,0,1,0,1,1,1,0,1,1,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,0,1,0,1,1,0,1,1,1,1,0,1,0,1,1,1,0,1,1,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,0,1,0,1,1,0,1,1,1,1,0,1,0,1,1,1,0,1,1}, }; static vector> block_56_12_22 = { { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0 }, { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1 }, { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0 }, { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1 }, }; static vector> gen_matrix; static size_t gen_in_count = 0; static size_t gen_out_count = 0; size_t short_code_init (size_t k) { if (k == 12) { gen_matrix = block_56_12_22; gen_in_count = 12; gen_out_count = 56; } else if (k == 16) { gen_matrix = block_61_16_21; gen_in_count = 16; gen_out_count = 61; } else if (k == 20) { gen_matrix = block_65_20_20; gen_in_count = 20; gen_out_count = 65; } else /* unsupported k */ { return 0; } return gen_out_count; } vector code_encode (ConvBlockType block_type, const vector& in_bits) { return Params::payload_short ? short_encode (block_type, in_bits) : conv_encode (block_type, in_bits); } size_t code_size (ConvBlockType block_type, size_t msg_size) { return Params::payload_short ? short_code_size (block_type, msg_size) : conv_code_size (block_type, msg_size); } vector code_decode_soft (ConvBlockType block_type, const std::vector& coded_bits, float *error_out) { return Params::payload_short ? short_decode_soft (block_type, coded_bits, error_out) : conv_decode_soft (block_type, coded_bits, error_out); } vector short_encode_blk (const vector& in_bits) { assert (gen_matrix.size() == in_bits.size()); assert (gen_matrix.size() == gen_in_count); assert (gen_matrix[0].size() == gen_out_count); vector out_bits; for (size_t j = 0; j < gen_out_count; j++) { int x = 0; for (size_t bit = 0; bit < gen_in_count; bit++) { if (in_bits[bit]) { x ^= gen_matrix[bit][j]; } } out_bits.push_back (x); } return out_bits; } vector short_encode (ConvBlockType block_type, const vector& in_bits) { return conv_encode (block_type, short_encode_blk (in_bits)); } size_t short_code_size (ConvBlockType block_type, size_t msg_size) { assert (msg_size == gen_matrix.size()); return conv_code_size (block_type, gen_out_count); } vector short_decode_blk (const vector& coded_bits) { vector out_bits; for (size_t c = 0; c < size_t (1 << gen_in_count); c++) { bool match = true; for (size_t j = 0; j < gen_out_count; j++) { int x = 0; for (size_t bit = 0; bit < gen_in_count; bit++) { if (c & (1 << bit)) { x ^= gen_matrix[bit][j]; } } if (coded_bits[j] != x) { match = false; break; } } if (match) { for (size_t bit = 0; bit < gen_in_count; bit++) { if (c & (1 << bit)) { out_bits.push_back (1); } else { out_bits.push_back (0); } } return out_bits; } } return out_bits; } vector short_decode_soft (ConvBlockType block_type, const std::vector& coded_bits, float *error_out) { return short_decode_blk (conv_decode_soft (block_type, coded_bits, error_out)); } audiowmark-0.6.5/src/shortcode.hh000066400000000000000000000031551501136502400167700ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_SHORT_CODE_HH #define AUDIOWMARK_SHORT_CODE_HH #include #include #include "convcode.hh" size_t code_size (ConvBlockType block_type, size_t msg_size); std::vector code_encode (ConvBlockType block_type, const std::vector& in_bits); std::vector code_decode_soft (ConvBlockType block_type, const std::vector& coded_bits, float *error_out = nullptr); size_t short_code_size (ConvBlockType block_type, size_t msg_size); std::vector short_encode (ConvBlockType block_type, const std::vector& in_bits); std::vector short_decode_soft (ConvBlockType block_type, const std::vector& coded_bits, float *error_out = nullptr); std::vector short_encode_blk (const std::vector& in_bits); std::vector short_decode_blk (const std::vector& coded_bits); size_t short_code_init (size_t k); #endif /* AUDIOWMARK_SHORT_CODE_HH */ audiowmark-0.6.5/src/snr.sh000077500000000000000000000003261501136502400156130ustar00rootroot00000000000000# pseudo random pattern PATTERN=4e1243bd22c66e76c2ba9eddc1f91394 for i in test/T* do echo $i $(audiowmark add $i t.wav $PATTERN $AWM_PARAMS --snr 2>&1 | grep SNR) done | awk '{s += $3; n++} END { print s/n; }' audiowmark-0.6.5/src/stdoutwavoutputstream.cc000066400000000000000000000120611501136502400215150ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "stdoutwavoutputstream.hh" #include "utils.hh" #include #include #include using std::string; using std::vector; using std::min; StdoutWavOutputStream::~StdoutWavOutputStream() { close(); } int StdoutWavOutputStream::sample_rate() const { return m_sample_rate; } int StdoutWavOutputStream::bit_depth() const { return m_bit_depth; } int StdoutWavOutputStream::n_channels() const { return m_n_channels; } static void header_append_str (vector& bytes, const string& str) { for (auto ch : str) bytes.push_back (ch); } static void header_append_u32 (vector& bytes, uint32_t u) { bytes.push_back (u); bytes.push_back (u >> 8); bytes.push_back (u >> 16); bytes.push_back (u >> 24); } static void header_append_u16 (vector& bytes, uint16_t u) { bytes.push_back (u); bytes.push_back (u >> 8); } Error StdoutWavOutputStream::open (int n_channels, int sample_rate, int bit_depth, Encoding encoding, size_t n_frames, bool wav_pipe) { assert (m_state == State::NEW); if (encoding == Encoding::FLOAT) { if (bit_depth != 32 && bit_depth != 64) { return Error (string_printf ("StdoutWavOutputStream::open: unsupported floating point bit depth %d", bit_depth)); } } else if (bit_depth != 16 && bit_depth != 24 && bit_depth != 32) { return Error (string_printf ("StdoutWavOutputStream::open: unsupported bit depth %d", bit_depth)); } if (n_frames == AudioInputStream::N_FRAMES_UNKNOWN && !wav_pipe) { return Error ("unable to write wav format to standard out without input length information"); } RawFormat format; format.set_bit_depth (bit_depth); format.set_encoding (encoding); Error err = Error::Code::NONE; m_raw_converter.reset (RawConverter::create (format, err)); if (err) return err; vector header_bytes; size_t data_size = n_frames * n_channels * ((bit_depth + 7) / 8); m_close_padding = data_size & 1; // padding to ensure even data size size_t aligned_data_size = data_size + m_close_padding; header_append_str (header_bytes, "RIFF"); if (wav_pipe) header_append_u32 (header_bytes, -1); else header_append_u32 (header_bytes, 36 + aligned_data_size); header_append_str (header_bytes, "WAVE"); // subchunk 1 header_append_str (header_bytes, "fmt "); header_append_u32 (header_bytes, 16); // subchunk size header_append_u16 (header_bytes, encoding == Encoding::FLOAT ? 3 : 1); // uncompressed audio header_append_u16 (header_bytes, n_channels); header_append_u32 (header_bytes, sample_rate); header_append_u32 (header_bytes, sample_rate * n_channels * bit_depth / 8); // byte rate header_append_u16 (header_bytes, n_channels * bit_depth / 8); // block align header_append_u16 (header_bytes, bit_depth); // bits per sample // subchunk 2 header_append_str (header_bytes, "data"); if (wav_pipe) header_append_u32 (header_bytes, -1); else header_append_u32 (header_bytes, data_size); fwrite (&header_bytes[0], 1, header_bytes.size(), stdout); if (ferror (stdout)) return Error ("write wav header failed"); m_bit_depth = bit_depth; m_sample_rate = sample_rate; m_n_channels = n_channels; m_state = State::OPEN; return Error::Code::NONE; } Error StdoutWavOutputStream::write_frames (const vector& samples) { if (samples.empty()) return Error::Code::NONE; const size_t block_size = 8192 * m_n_channels; const int sample_width = m_bit_depth / 8; m_output_bytes.resize (sample_width * block_size); size_t pos = 0; while (size_t todo = min (block_size, samples.size() - pos)) { m_raw_converter->to_raw (samples.data() + pos, m_output_bytes.data(), todo); fwrite (m_output_bytes.data(), 1, todo * sample_width, stdout); if (ferror (stdout)) return Error (string_printf ("write sample data failed (%s)", strerror (errno))); pos += todo; } return Error::Code::NONE; } Error StdoutWavOutputStream::close() { if (m_state == State::OPEN) { for (size_t i = 0; i < m_close_padding; i++) { fputc (0, stdout); if (ferror (stdout)) return Error ("write wav padding failed"); } fflush (stdout); if (ferror (stdout)) return Error ("error during flush"); m_state = State::CLOSED; } return Error::Code::NONE; } audiowmark-0.6.5/src/stdoutwavoutputstream.hh000066400000000000000000000030761501136502400215350ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_STDOUT_WAV_STREAM_HH #define AUDIOWMARK_STDOUT_WAV_STREAM_HH #include "audiostream.hh" #include "rawconverter.hh" #include class StdoutWavOutputStream : public AudioOutputStream { int m_bit_depth = 0; int m_sample_rate = 0; int m_n_channels = 0; size_t m_close_padding = 0; enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; std::vector m_output_bytes; std::unique_ptr m_raw_converter; public: ~StdoutWavOutputStream(); Error open (int n_channels, int sample_rate, int bit_depth, Encoding encoding, size_t n_frames, bool wav_pipe); Error write_frames (const std::vector& frames) override; Error close() override; int sample_rate() const override; int bit_depth() const override; int n_channels() const override; }; #endif audiowmark-0.6.5/src/strength2snr.sh000077500000000000000000000001511501136502400174500ustar00rootroot00000000000000for strength in 30 20 15 10 5 3 2 1 do echo $strength $(AWM_PARAMS="--strength=$strength" snr.sh) done audiowmark-0.6.5/src/sync-test.sh000077500000000000000000000005151501136502400167420ustar00rootroot00000000000000#!/bin/bash SEEDS="$1" MAX_SEED=$(($SEEDS - 1)) P="$2" shift 2 echo "n seeds : $SEEDS" echo "ber-test args : $@" echo "params : $P" for seed in $(seq 0 $MAX_SEED) do echo $(AWM_SEEDS=$seed AWM_PARAMS="$P" AWM_REPORT="sync" ber-test.sh "$@") done | awk '{bad += $1; files += $2; print bad, files, bad * 100. / files }' audiowmark-0.6.5/src/syncfinder.cc000066400000000000000000000574031501136502400171350ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "syncfinder.hh" #include "threadpool.hh" #include "wmcommon.hh" using std::complex; using std::vector; using std::string; using std::min; vector> SyncFinder::get_sync_bits (const Key& key, Mode mode) { vector> sync_bits; // "long" blocks consist of two "normal" blocks, which means // the sync bits pattern is repeated after the end of the first block const int first_block_end = mark_sync_frame_count() + mark_data_frame_count(); const int block_count = mode == Mode::CLIP ? 2 : 1; UpDownGen up_down_gen (key, Random::Stream::sync_up_down); BitPosGen bit_pos_gen (key); for (int bit = 0; bit < Params::sync_bits; bit++) { vector frame_bits; for (int f = 0; f < Params::sync_frames_per_bit; f++) { UpDownArray frame_up, frame_down; up_down_gen.get (f + bit * Params::sync_frames_per_bit, frame_up, frame_down); for (int block = 0; block < block_count; block++) { FrameBit frame_bit; frame_bit.frame = bit_pos_gen.sync_frame (f + bit * Params::sync_frames_per_bit) + block * first_block_end; if (block == 0) { for (auto u : frame_up) frame_bit.up.push_back (u - Params::min_band); for (auto d : frame_down) frame_bit.down.push_back (d - Params::min_band); } else { for (auto u : frame_up) frame_bit.down.push_back (u - Params::min_band); for (auto d : frame_down) frame_bit.up.push_back (d - Params::min_band); } std::sort (frame_bit.up.begin(), frame_bit.up.end()); std::sort (frame_bit.down.begin(), frame_bit.down.end()); frame_bits.push_back (frame_bit); } } std::sort (frame_bits.begin(), frame_bits.end(), [] (FrameBit& f1, FrameBit& f2) { return f1.frame < f2.frame; }); sync_bits.push_back (frame_bits); } return sync_bits; } /* safe to call from any thread */ double SyncFinder::normalize_sync_quality (double raw_quality) { /* the quality for a good sync block depends on watermark strength * * this is just an approximation, but it should be good enough to be able to * use one single threshold on the normalized value check if we have a sync * block or not - typical output is 1.0 or more for sync blocks and close * to 0.0 for non-sync blocks */ return raw_quality / min (Params::water_delta, 0.080) / 2.9; } /* safe to call from any thread */ double SyncFinder::bit_quality (float umag, float dmag, int bit) { const int expect_data_bit = bit & 1; /* expect 010101 */ /* convert avoiding bias, raw_bit < 0 => 0 bit received; raw_bit > 0 => 1 bit received */ double raw_bit; if (umag == 0 || dmag == 0) { raw_bit = 0; } else if (umag < dmag) { raw_bit = 1 - umag / dmag; } else { raw_bit = dmag / umag - 1; } return expect_data_bit ? raw_bit : -raw_bit; } double SyncFinder::sync_decode (const vector>& sync_bits, const size_t start_frame, const vector& fft_out_db, const vector& have_frames) { double sync_quality = 0; size_t n_bands = Params::max_band - Params::min_band + 1; int bit_count = 0; for (size_t bit = 0; bit < sync_bits.size(); bit++) { const vector& frame_bits = sync_bits[bit]; float umag = 0, dmag = 0; int frame_bit_count = 0; for (const auto& frame_bit : frame_bits) { if (have_frames[start_frame + frame_bit.frame]) { const int index = (start_frame + frame_bit.frame) * n_bands; for (size_t i = 0; i < frame_bit.up.size(); i++) { umag += fft_out_db[index + frame_bit.up[i]]; dmag += fft_out_db[index + frame_bit.down[i]]; } frame_bit_count++; } } sync_quality += bit_quality (umag, dmag, bit) * frame_bit_count; bit_count += frame_bit_count; } if (bit_count) sync_quality /= bit_count; sync_quality = normalize_sync_quality (sync_quality); return sync_quality; } void SyncFinder::scan_silence (const WavData& wav_data) { const vector& samples = wav_data.samples(); // find first non-zero sample wav_data_first = 0; while (wav_data_first < samples.size() && samples[wav_data_first] == 0) wav_data_first++; // search wav_data_last to get [wav_data_first, wav_data_last) range wav_data_last = samples.size(); while (wav_data_last > wav_data_first && samples[wav_data_last - 1] == 0) wav_data_last--; } void SyncFinder::search_approx (vector& key_results, const vector>>& sync_bits, const WavData& wav_data, Mode mode) { ThreadPool thread_pool; vector fft_db; vector have_frames; std::mutex result_mutex; // compute multiple time-shifted fft vectors size_t n_bands = Params::max_band - Params::min_band + 1; int total_frame_count = mark_sync_frame_count() + mark_data_frame_count(); if (mode == Mode::CLIP) total_frame_count *= 2; for (size_t sync_shift = 0; sync_shift < Params::frame_size; sync_shift += Params::sync_search_step) { sync_fft_parallel (thread_pool, wav_data, sync_shift, fft_db, have_frames); vector start_frames; for (int start_frame = 0; start_frame < frame_count (wav_data); start_frame++) { if ((start_frame + total_frame_count) * n_bands < fft_db.size()) start_frames.push_back (start_frame); } for (size_t k = 0; k < key_results.size(); k++) { for (auto split_start_frames : split_vector (start_frames, 256)) { thread_pool.add_job ([this, k, sync_shift, split_start_frames, &sync_bits, &fft_db, &have_frames, &key_results, &result_mutex]() { for (auto start_frame : split_start_frames) { double quality = sync_decode (sync_bits[k], start_frame, fft_db, have_frames); // printf ("%zd %f\n", sync_index, quality); const size_t sync_index = start_frame * Params::frame_size + sync_shift; { std::lock_guard lg (result_mutex); SearchScore search_score; search_score.index = sync_index; search_score.raw_quality = quality; search_score.local_mean = 0; // fill this after all search scores are ready key_results[k].scores.push_back (search_score); } } }); } } thread_pool.wait_all(); } for (auto& key_result : key_results) { sort (key_result.scores.begin(), key_result.scores.end(), [] (const SearchScore& a, const SearchScore &b) { return a.index < b.index; }); /* * Raw sync quality has a key and audio-dependent local bias, meaning * that in some regions, the values tend to be larger than zero, while in * others, they tend to be smaller than zero. * * Estimating and subtracting the local mean improves our ability to find * the most relevant sync peaks. */ /* compute local mean for all scores */ for (int i = 0; i < int (key_result.scores.size()); i++) { double avg = 0; int n = 0; for (int j = -local_mean_distance; j <= local_mean_distance; j++) { if (std::abs (j) >= 4) { int idx = i + j; if (idx >= 0 && idx < int (key_result.scores.size())) { avg += key_result.scores[idx].raw_quality; n++; } } } if (n > 0) avg /= n; key_result.scores[i].local_mean = avg; } } } void SyncFinder::sync_select_local_maxima (vector& sync_scores) { vector selected_scores; for (size_t i = 0; i < sync_scores.size(); i++) { double q = sync_scores[i].abs_quality(); double q_last = 0; double q_next = 0; if (i > 0) q_last = sync_scores[i - 1].abs_quality(); if (i + 1 < sync_scores.size()) q_next = sync_scores[i + 1].abs_quality(); if (q >= q_last && q >= q_next) { selected_scores.emplace_back (sync_scores[i]); i++; // score with quality q_next cannot be a local maximum } } sync_scores = selected_scores; } /* * One downside of subtracting the local mean is that, around each peak, * we subtract the peak from the quality, which creates a bias in the * opposite direction of the peak. * * To avoid false positive blocks around peaks, we ignore peaks with smaller * amplitude and the opposite sign. This works especially well for large peaks * (clean/strong watermark). */ void SyncFinder::sync_mask_avg_false_positives (vector& sync_scores) { static constexpr int mask_distance = local_mean_distance + 3; static constexpr double mask_factor = 3; vector out_scores; auto quality_sign = [] (const SearchScore& score) { if (score.raw_quality - score.local_mean < 0) return -1; else return 1; }; for (int i = 0; i < int (sync_scores.size()); i++) { bool mask = false; // d is larger the effective distance between the two peaks, because sync_scores // only contains the peaks for (int d = -mask_distance; d <= mask_distance; d++) { int j = i + d; if (i != j && j >= 0 && j < int (sync_scores.size())) { // distance between the two peaks int distance = std::abs (int (sync_scores[i].index) - int (sync_scores[j].index)) / Params::sync_search_step; if (distance <= mask_distance) { if (sync_scores[j].abs_quality() > sync_scores[i].abs_quality() * mask_factor && quality_sign (sync_scores[j]) != quality_sign (sync_scores[i])) { mask = true; } } } } if (!mask) out_scores.push_back (sync_scores[i]); } sync_scores = out_scores; } void SyncFinder::sync_select_by_threshold (vector& sync_scores) { const double sync_threshold1 = Params::sync_threshold2 * 0.75; vector selected_scores; for (size_t i = 0; i < sync_scores.size(); i++) { double q = sync_scores[i].abs_quality(); if (q > sync_threshold1) { double q_last = 0; double q_next = 0; if (i > 0) q_last = sync_scores[i - 1].abs_quality(); if (i + 1 < sync_scores.size()) q_next = sync_scores[i + 1].abs_quality(); if (q >= q_last && q >= q_next) { selected_scores.emplace_back (sync_scores[i]); i++; // score with quality q_next cannot be a local maximum } } } sync_scores = selected_scores; } void SyncFinder::sync_select_threshold_and_n_best (vector& scores, double threshold) { std::sort (scores.begin(), scores.end(), [](SearchScore& s1, SearchScore& s2) { return s1.abs_quality() > s2.abs_quality(); }); /* keep all matches with (quality > threshold) */ int i = 0; while (i < int (scores.size()) && scores[i].abs_quality() > threshold) i++; if (i >= Params::get_n_best) { /* have more than n_best matches with (quality > threshold), keep all of them */ scores.resize (i); } else if (int (scores.size()) > Params::get_n_best) { /* if we have less than n_best matches with (quality > threshold), keep n_best matches */ scores.resize (Params::get_n_best); } } void SyncFinder::sync_select_truncate_n (vector& sync_scores, size_t n) { std::sort (sync_scores.begin(), sync_scores.end(), [](SearchScore& s1, SearchScore& s2) { return s1.abs_quality() > s2.abs_quality(); }); if (sync_scores.size() > n) sync_scores.resize (n); } void SyncFinder::search_refine (const WavData& wav_data, Mode mode, SearchKeyResult& key_result, const vector>& sync_bits) { ThreadPool thread_pool; std::mutex result_mutex; vector result_scores; BitPosGen bit_pos_gen (key_result.key); int total_frame_count = mark_sync_frame_count() + mark_data_frame_count(); const int first_block_end = total_frame_count; if (mode == Mode::CLIP) total_frame_count *= 2; vector want_frames (total_frame_count); for (size_t f = 0; f < mark_sync_frame_count(); f++) { want_frames[bit_pos_gen.sync_frame (f)] = 1; if (mode == Mode::CLIP) want_frames[first_block_end + bit_pos_gen.sync_frame (f)] = 1; } for (const auto& score : key_result.scores) { thread_pool.add_job ([this, score, total_frame_count, &wav_data, &want_frames, &sync_bits, &result_scores, &result_mutex] () { vector fft_db; vector have_frames; //printf ("%zd %s %f", score.index, find_closest_sync (score.index).c_str(), score.quality); // refine match double best_quality = score.raw_quality; size_t best_index = score.index; int start = std::max (int (score.index) - Params::sync_search_step, 0); int end = score.index + Params::sync_search_step; for (int fine_index = start; fine_index <= end; fine_index += Params::sync_search_fine) { sync_fft (wav_data, fine_index, total_frame_count, fft_db, have_frames, want_frames); if (fft_db.size()) { double q = sync_decode (sync_bits, 0, fft_db, have_frames); if (fabs (q - score.local_mean) > fabs (best_quality - score.local_mean)) { best_quality = q; best_index = fine_index; } } } //printf (" => refined: %zd %s %f\n", best_index, find_closest_sync (best_index).c_str(), best_quality); { std::lock_guard lg (result_mutex); SearchScore refined_score; refined_score.index = best_index; refined_score.raw_quality = best_quality; refined_score.local_mean = score.local_mean; result_scores.push_back (refined_score); } }); } thread_pool.wait_all(); sort (result_scores.begin(), result_scores.end(), [] (const SearchScore& a, const SearchScore &b) { return a.index < b.index; }); key_result.scores = result_scores; } vector SyncFinder::fake_sync (const vector& key_list, const WavData& wav_data, Mode mode) { vector result_scores; if (mode == Mode::BLOCK) { const size_t expect0 = Params::frames_pad_start * Params::frame_size; const size_t expect_step = (mark_sync_frame_count() + mark_data_frame_count()) * Params::frame_size; const size_t expect_end = frame_count (wav_data) * Params::frame_size; int ab = 0; for (size_t expect_index = expect0; expect_index + expect_step < expect_end; expect_index += expect_step) result_scores.push_back (Score { expect_index, 1.0, (ab++ & 1) ? ConvBlockType::b : ConvBlockType::a }); } vector key_results; for (auto key : key_list) { KeyResult key_result; key_result.key = key; key_result.sync_scores = result_scores; key_results.push_back (key_result); } return key_results; } vector SyncFinder::search (const vector& key_list, const WavData& wav_data, Mode mode) { if (Params::test_no_sync) return fake_sync (key_list, wav_data, mode); if (mode == Mode::CLIP) { /* in clip mode we optimize handling large areas of padding which is silent */ scan_silence (wav_data); } else { /* in block mode we don't do anything special for silence at beginning/end */ wav_data_first = 0; wav_data_last = wav_data.samples().size(); } vector search_key_results; vector>> sync_bits; for (const auto& key : key_list) { SearchKeyResult search_key_result; search_key_result.key = key; search_key_results.push_back (search_key_result); sync_bits.push_back (get_sync_bits (key, mode)); } search_approx (search_key_results, sync_bits, wav_data, mode); vector key_results; for (size_t k = 0; k < search_key_results.size(); k++) { /* find local maxima */ auto& search_scores = search_key_results[k].scores; sync_select_local_maxima (search_scores); sync_mask_avg_false_positives (search_scores); /* select: threshold1 & at least n_best */ sync_select_threshold_and_n_best (search_scores, Params::sync_threshold2 * 0.75); if (mode == Mode::CLIP) { /* ClipDecoder: enforce a maximum number of matches: at most n_best but at least 5 */ size_t n_max = std::max (Params::get_n_best, 5); sync_select_truncate_n (search_scores, n_max); } search_refine (wav_data, mode, search_key_results[k], sync_bits[k]); /* select: threshold2 & at least n_best */ sync_select_threshold_and_n_best (search_scores, Params::sync_threshold2); sort (search_scores.begin(), search_scores.end(), [] (const SearchScore& a, const SearchScore &b) { return a.index < b.index; }); KeyResult key_result; key_result.key = search_key_results[k].key; for (auto search_score : search_scores) { double q = search_score.raw_quality - search_score.local_mean; Score score; score.index = search_score.index; score.quality = fabs (q); score.block_type = q > 0 ? ConvBlockType::a : ConvBlockType::b; key_result.sync_scores.push_back (score); } key_results.push_back (key_result); } return key_results; } void SyncFinder::sync_fft (const WavData& wav_data, size_t index, size_t frame_count, vector& fft_out_db, vector& have_frames, const vector& want_frames) { fft_out_db.clear(); have_frames.clear(); /* read past end? -> fail */ if (wav_data.n_values() < (index + frame_count * Params::frame_size) * wav_data.n_channels()) return; FFTAnalyzer fft_analyzer (wav_data.n_channels()); const vector& samples = wav_data.samples(); const size_t n_bands = Params::max_band - Params::min_band + 1; int out_pos = 0; fft_out_db.resize (n_bands * frame_count); have_frames.resize (frame_count); for (size_t f = 0; f < frame_count; f++) { const size_t f_first = (index + f * Params::frame_size) * wav_data.n_channels(); const size_t f_last = (index + (f + 1) * Params::frame_size) * wav_data.n_channels(); if ((want_frames.size() && !want_frames[f]) // frame not wanted? || (f_last < wav_data_first) // frame in silence before input? || (f_first > wav_data_last)) // frame in silence after input? { out_pos += n_bands; } else { constexpr double min_db = -96; vector>> frame_result = fft_analyzer.run_fft (samples, index + f * Params::frame_size); /* computing db-magnitude is expensive, so we better do it here */ for (int ch = 0; ch < wav_data.n_channels(); ch++) for (int i = Params::min_band; i <= Params::max_band; i++) fft_out_db[out_pos + i - Params::min_band] += db_from_complex (frame_result[ch][i], min_db); out_pos += n_bands; have_frames[f] = 1; } } } void SyncFinder::sync_fft_parallel (ThreadPool& thread_pool, const WavData& wav_data, size_t index, std::vector& fft_out_db, std::vector& have_frames) { std::mutex result_mutex; fft_out_db.clear(); have_frames.clear(); struct PartialFFTResult { int start_frame = 0; vector have_frames; vector fft_db; }; vector partial_fft_results; const int frames_per_job = 256; for (int start_frame = 0; start_frame < frame_count (wav_data); start_frame += frames_per_job) { thread_pool.add_job ([this, start_frame, index, frames_per_job, &wav_data, &partial_fft_results, &result_mutex] { const int remaining_frames = frame_count (wav_data) - 1 - start_frame; const int frames = std::min (remaining_frames, frames_per_job); if (frames > 0) { PartialFFTResult result; result.start_frame = start_frame; sync_fft (wav_data, index + start_frame * Params::frame_size, frames, result.fft_db, result.have_frames, /* want all frames */ {}); if (!result.fft_db.size()) warning ("SyncFinder: sync_fft_parallel expected %d fft frames, but result was empty\n", frames); { std::lock_guard lg (result_mutex); partial_fft_results.push_back (result); } } }); } thread_pool.wait_all(); std::sort (partial_fft_results.begin(), partial_fft_results.end(), [] (const auto& r1, const auto& r2) { return r1.start_frame < r2.start_frame; }); for (const auto& partial_fft_result : partial_fft_results) { fft_out_db.insert (fft_out_db.end(), partial_fft_result.fft_db.begin(), partial_fft_result.fft_db.end()); have_frames.insert (have_frames.end(), partial_fft_result.have_frames.begin(), partial_fft_result.have_frames.end()); } } string SyncFinder::find_closest_sync (size_t index) { int wm_length = (mark_data_frame_count() + mark_sync_frame_count()) * Params::frame_size; int wm_offset = Params::frames_pad_start * Params::frame_size; int best_error = wm_length * 2; int best = 0; for (int i = 0; i < 100; i++) { int error = abs (int (index) - (wm_offset + i * wm_length)); if (error < best_error) { best = i; best_error = error; } } return string_printf ("n:%d offset:%d", best, int (index) - (wm_offset + best * wm_length)); } vector> SyncFinder::split_vector (vector& in_vector, size_t max_size) { /* split input vector into smaller vectors of at most max_size elements */ vector> result; size_t size = in_vector.size(); for (size_t i = 0; i < size; i += max_size) result.emplace_back (in_vector.begin() + i, in_vector.begin() + i + min (size - i, max_size)); return result; } audiowmark-0.6.5/src/syncfinder.hh000066400000000000000000000127671501136502400171530ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_SYNC_FINDER_HH #define AUDIOWMARK_SYNC_FINDER_HH #include "convcode.hh" #include "wavdata.hh" #include "random.hh" #include "threadpool.hh" /* * The SyncFinder class searches for sync bits in an input WavData. It is used * by both, the BlockDecoder and ClipDecoder to find a time index where * decoding should start. * * The first step for finding sync bits is search_approx, which generates a * list of approximate locations where sync bits match, using a stepping of * sync_search_step=256 (for a frame size of 1024). The approximate candidate * locations are later refined with search_refine using sync_search_fine=8 as * stepping. * * BlockDecoder and ClipDecoder have similar but not identical needs, so * both use this class, using either Mode::BLOCK or Mode::CLIP. * * BlockDecoder (Mode::BLOCK) * - search for full A or full B blocks * - select candidates by threshold(s) only * - zero samples are not treated any special * * ClipDecoder (Mode::CLIP) * - search for AB block (one A block followed by one B block) or BA block * - select candidates by threshold, but only keep at most the 5 best matches * - zero samples at beginning/end don't affect the score returned by sync_decode * - zero samples at beginning/end don't cost much cpu time (no fft performed) * * The ClipDecoder will always use a big amount of zero padding at the beginning * and end to be able to find "partial" AB blocks, where most of the data is * matched with zeros. * * ORIG: |AAAAA|BBBBB|AAAAA|BBBBB| * CLIP: |A|BB| * ZEROPAD: 00000|A|BB|00000 * MATCH AAAAA|BBBBB * * In this example a clip (CLIP) is generated from an original file (ORIG). By * zero padding we get a file that contains the clip (ZEROPAD). Finally we are * able to match an AB block to the zeropadded file (MATCH). This gives us an * index in the zeropadded file that can be used for decoding the available * data. */ class SyncFinder { public: enum class Mode { BLOCK, CLIP }; struct Score { size_t index; double quality; ConvBlockType block_type; }; struct FrameBit { int frame; std::vector up; std::vector down; }; struct KeyResult { Key key; std::vector sync_scores; }; private: static constexpr int local_mean_distance = 20; struct SearchScore { size_t index; double raw_quality; double local_mean; double abs_quality() const { return fabs (raw_quality - local_mean); } }; struct SearchKeyResult { Key key; std::vector scores; }; double sync_decode (const std::vector>& sync_bits, const size_t start_frame, const std::vector& fft_out_db, const std::vector& have_frames); void scan_silence (const WavData& wav_data); void search_approx (std::vector& key_results, const std::vector>>& sync_bits, const WavData& wav_data, Mode mode); void sync_select_local_maxima (std::vector& sync_scores); void sync_mask_avg_false_positives (std::vector& sync_scores); void sync_select_by_threshold (std::vector& sync_scores); void sync_select_threshold_and_n_best (std::vector& sync_scores, double threshold); void sync_select_truncate_n (std::vector& sync_scores, size_t n); void search_refine (const WavData& wav_data, Mode mode, SearchKeyResult& key_result, const std::vector>& sync_bits); std::vector fake_sync (const std::vector& key_list, const WavData& wav_data, Mode mode); // non-zero sample range: [wav_data_first, wav_data_last) size_t wav_data_first = 0; size_t wav_data_last = 0; public: std::vector search (const std::vector& key_list, const WavData& wav_data, Mode mode); static std::vector> get_sync_bits (const Key& key, Mode mode); static double bit_quality (float umag, float dmag, int bit); static double normalize_sync_quality (double raw_quality); private: void sync_fft_parallel (ThreadPool& thread_pool, const WavData& wav_data, size_t index, std::vector& fft_out_db, std::vector& have_frames); void sync_fft (const WavData& wav_data, size_t index, size_t frame_count, std::vector& fft_out_db, std::vector& have_frames, const std::vector& want_frames); std::string find_closest_sync (size_t index); std::vector> split_vector (std::vector& in_vector, size_t max_size); }; #endif audiowmark-0.6.5/src/test_list000066400000000000000000000047651501136502400164220ustar00rootroot00000000000000/home/stefan/files/music/artists/air/2009__love_2/09_sing_sang_sung.flac /home/stefan/files/music/artists/jamie_cullum/the_pursuit/12_music_is_through.flac /home/stefan/files/music/artists/gotye/making_mirrors/10 - Giving Me A Chance.flac /home/stefan/files/music/artists/alice_cooper/the_definitive_alice_cooper/18_how_you_gonna_see_me_now.flac /home/stefan/files/music/artists/mendelssohn/The Hebrides, Symphonies Nos.1, 4/02 - Symphony No. 1 C minor Op. 11 - Allegro di molto.flac /home/stefan/files/music/artists/depeche_mode/the_singles_81_to_85/09 - Love In Itself.flac /home/stefan/files/music/artists/beethoven/Klavierkonzerte Nrr.3 & 4 (CD 2)/02 - Klavierkonzert Nr. 3 C-moll - 2. Largo.flac /home/stefan/files/music/artists/joe_henderson/page_one/05 - Jinrikisha.flac /home/stefan/files/music/artists/boehse_onkelz/gehasst_verdammt_vergoettert/15_fr_immer.flac /home/stefan/files/music/artists/dj_bobo/because_of_you/03 - Because Of You (Twister Hard Club - Radio Edit).flac /home/stefan/files/music/artists/mixed/the_world_of_trance_2/cd1/10 - Nature One - The Sense Of Live (Hurricanmix).flac /home/stefan/files/music/artists/charles_mingus/Mingus Plays Piano/08 - Meditations for Moses.flac /home/stefan/files/music/artists/lena/good_news/06 - Mama Told Me.flac /home/stefan/files/music/artists/mixed/tunnel_trance_force/43_cd1/24 - Niosecontrollers - Crump.flac /home/stefan/files/music/artists/paniq/beyond_good_and_evil/paniq - Beyond Good and Evil - 02 Tartaros (The Barren Acres of Open Source).flac /home/stefan/files/music/artists/mixed/Katia Marielle Labeque - Rhapsody in Blue/05 - Strawinsky: Petrushka_Volksfest waehrend der Fastnacht.flac /home/stefan/files/music/artists/rosenstolz/mondkuss_cd1/09_die_zigarette_danach.flac /home/stefan/files/music/artists/tears_for_fears/the_collection/03 - Shout.flac /home/stefan/files/music/artists/duke_ellington/Kings of Swing - Anthology/18 - The Hawk Talks.flac /home/stefan/files/music/artists/ich__ich/gute_reise__cd_1/09_stein.flac /home/stefan/files/music/artists/brahms/Double Concerto for Violin and Cello 1 Am Op102/05 - Brahms Double Concerto for Violin and Cello 2 Am Op102 Andante.flac /home/stefan/files/music/artists/mixed/Katia Marielle Labeque - Rhapsody in Blue/01 - Gershwin: Rhapsody in Blue.flac /home/stefan/files/music/artists/loreena_mckennitt/collection/04 - Loreena Mckennitt - Caravanserai.flac /home/stefan/files/music/artists/mumford_and_sons/sigh_no_more/05 - White Blank Page.flac /home/stefan/files/music/artists/daft_punk/tron_legacy/22 - Finale.flac audiowmark-0.6.5/src/testconvcode.cc000066400000000000000000000151131501136502400174610ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "convcode.hh" #include #include using std::vector; using std::string; vector generate_error_vector (size_t n, int errors) { vector ev (n); while (errors) { size_t pos = rand() % ev.size(); if (ev[pos] != 1) { ev[pos] = 1; errors--; } } return ev; } static bool no_case_equal (const string& s1, const string& s2) { if (s1.size() != s2.size()) return false; return std::equal (s1.begin(), s1.end(), s2.begin(), [] (char c1, char c2) -> bool { return tolower (c1) == tolower (c2);}); } int main (int argc, char **argv) { string btype = (argc > 1) ? argv[1] : ""; ConvBlockType block_type; if (no_case_equal (btype, "A")) block_type = ConvBlockType::a; else if (no_case_equal (btype, "B")) block_type = ConvBlockType::b; else if (no_case_equal (btype, "AB")) block_type = ConvBlockType::ab; else { printf ("first argument must be A, B, or AB\n"); return 1; } if (argc == 2) { vector in_bits = bit_str_to_vec ("80f12381"); printf ("input vector (k=%zd): ", in_bits.size()); for (auto b : in_bits) printf ("%d", b); printf ("\n"); vector coded_bits = conv_encode (block_type, in_bits); printf ("coded vector (n=%zd): ", coded_bits.size()); for (auto b : coded_bits) printf ("%d", b); printf ("\n"); printf ("coded hex: %s\n", bit_vec_to_str (coded_bits).c_str()); assert (coded_bits.size() == conv_code_size (block_type, in_bits.size())); vector decoded_bits = conv_decode_hard (block_type, coded_bits); printf ("output vector (k=%zd): ", decoded_bits.size()); for (auto b : decoded_bits) printf ("%d", b); printf ("\n"); assert (decoded_bits.size() == in_bits.size()); int errors = 0; for (size_t i = 0; i < decoded_bits.size(); i++) if (decoded_bits[i] != in_bits[i]) errors++; printf ("decoding errors: %d\n", errors); } if (argc == 3 && string (argv[2]) == "error") { size_t max_bit_errors = conv_code_size (block_type, 128) * 0.5; for (size_t bit_errors = 0; bit_errors < max_bit_errors; bit_errors++) { size_t coded_bit_count = 0; int bad_decode = 0; constexpr int test_size = 20; for (int i = 0; i < test_size; i++) { vector in_bits; while (in_bits.size() != 128) in_bits.push_back (rand() & 1); vector coded_bits = conv_encode (block_type, in_bits); coded_bit_count = coded_bits.size(); vector error_bits = generate_error_vector (coded_bits.size(), bit_errors); for (size_t pos = 0; pos < coded_bits.size(); pos++) coded_bits[pos] ^= error_bits[pos]; vector decoded_bits = conv_decode_hard (block_type, coded_bits); assert (decoded_bits.size() == 128); int errors = 0; for (size_t i = 0; i < 128; i++) if (decoded_bits[i] != in_bits[i]) errors++; if (errors > 0) bad_decode++; } printf ("%f %f\n", (100.0 * bit_errors) / coded_bit_count, (100.0 * bad_decode) / test_size); } } if (argc == 3 && string (argv[2]) == "soft-error") { for (double stddev = 0; stddev < 1.5; stddev += 0.01) { size_t coded_bit_count = 0; int bad_decode1 = 0, bad_decode2 = 0; constexpr int test_size = 20; int local_be = 0; for (int i = 0; i < test_size; i++) { vector in_bits; while (in_bits.size() != 128) in_bits.push_back (rand() & 1); vector coded_bits = conv_encode (block_type, in_bits); coded_bit_count = coded_bits.size(); std::default_random_engine generator; std::normal_distribution dist (0, stddev); vector recv_bits; for (auto b : coded_bits) recv_bits.push_back (b + dist (generator)); vector decoded_bits1 = conv_decode_soft (block_type, recv_bits); vector recv_hard_bits; for (auto b : recv_bits) recv_hard_bits.push_back ((b > 0.5) ? 1 : 0); for (size_t x = 0; x < recv_hard_bits.size(); x++) local_be += coded_bits[x] ^ recv_hard_bits[x]; vector decoded_bits2 = conv_decode_hard (block_type, recv_hard_bits); assert (decoded_bits1.size() == 128); assert (decoded_bits2.size() == 128); int e1 = 0; int e2 = 0; for (size_t i = 0; i < 128; i++) { if (decoded_bits1[i] != in_bits[i]) e1++; if (decoded_bits2[i] != in_bits[i]) e2++; } if (e1) bad_decode1++; if (e2) bad_decode2++; } printf ("%f %f %f\n", double (100 * local_be) / test_size / coded_bit_count, (100.0 * bad_decode1) / test_size, (100.0 * bad_decode2) / test_size); } } if (argc == 3 && string (argv[2]) == "perf") { vector in_bits; while (in_bits.size() != 128) in_bits.push_back (rand() & 1); const double start_t = get_time(); const size_t runs = 20; for (size_t i = 0; i < runs; i++) { vector out_bits = conv_decode_hard (block_type, conv_encode (block_type, in_bits)); assert (out_bits == in_bits); } printf ("%.1f ms/block\n", (get_time() - start_t) / runs * 1000.0); } if (argc == 3 && string (argv[2]) == "table") conv_print_table (block_type); } audiowmark-0.6.5/src/testhls.cc000066400000000000000000000123361501136502400164530ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "mpegts.hh" #include "wavdata.hh" #include "wmcommon.hh" #include "hls.hh" #include "sfinputstream.hh" #include "hlsoutputstream.hh" using std::string; using std::regex; using std::vector; using std::map; using std::min; class WDInputStream : public AudioInputStream { WavData *wav_data; size_t read_pos = 0; public: WDInputStream (WavData *wav_data) : wav_data (wav_data) { } int bit_depth() const override { return wav_data->bit_depth(); } Encoding encoding() const override { return Encoding::SIGNED; } int sample_rate() const override { return wav_data->sample_rate(); } int n_channels() const override { return wav_data->n_channels(); } size_t n_frames() const override { return wav_data->n_values() / wav_data->n_channels(); } Error read_frames (std::vector& samples, size_t count) override { size_t read_count = min (n_frames() - read_pos, count); const auto& wsamples = wav_data->samples(); samples.assign (wsamples.begin() + read_pos * n_channels(), wsamples.begin() + (read_pos + read_count) * n_channels()); read_pos += read_count; return Error::Code::NONE; } }; class WDOutputStream : public AudioOutputStream { WavData *wav_data; vector samples; public: WDOutputStream (WavData *wav_data) : wav_data (wav_data) { } int bit_depth() const override { return wav_data->bit_depth(); } int sample_rate() const override { return wav_data->sample_rate(); } int n_channels() const override { return wav_data->n_channels(); } Error write_frames (const std::vector& frames) override { samples.insert (samples.end(), frames.begin(), frames.end()); return Error::Code::NONE; } Error close() override { wav_data->set_samples (samples); // only do this once at end for performance reasons return Error::Code::NONE; } }; int mark_zexpand (const Key& key, WavData& wav_data, size_t zero_frames, const string& bits) { WDInputStream in_stream (&wav_data); WavData wav_data_out ({ /* no samples */ }, wav_data.n_channels(), wav_data.sample_rate(), wav_data.bit_depth()); WDOutputStream out_stream (&wav_data_out); int rc = add_stream_watermark (key, &in_stream, &out_stream, bits, zero_frames); if (rc != 0) return rc; wav_data.set_samples (wav_data_out.samples()); return 0; } int test_seek (const Key& key, const string& in, const string& out, int pos, const string& bits) { vector samples; WavData wav_data; Error err = wav_data.load (in); if (err) { error ("load error: %s\n", err.message()); return 1; } samples = wav_data.samples(); samples.erase (samples.begin(), samples.begin() + pos * wav_data.n_channels()); wav_data.set_samples (samples); int rc = mark_zexpand (key, wav_data, pos, bits); if (rc != 0) { return rc; } samples = wav_data.samples(); samples.insert (samples.begin(), pos * wav_data.n_channels(), 0); wav_data.set_samples (samples); err = wav_data.save (out); if (err) { error ("save error: %s\n", err.message()); return 1; } return 0; } int seek_perf (const Key& key, int sample_rate, double seconds) { vector samples (100); WavData wav_data (samples, 2, sample_rate, 16); double start_time = get_time(); int rc = mark_zexpand (key, wav_data, seconds * sample_rate, "0c"); if (rc != 0) return rc; double end_time = get_time(); info ("\n\n"); info ("total time %7.3f sec\n", end_time - start_time); info ("per second %7.3f ms\n", (end_time - start_time) / seconds * 1000); return 0; } int main (int argc, char **argv) { Key global_key; if (argc == 6 && strcmp (argv[1], "test-seek") == 0) { return test_seek (global_key, argv[2], argv[3], atoi (argv[4]), argv[5]); } else if (argc == 4 && strcmp (argv[1], "seek-perf") == 0) { return seek_perf (global_key, atoi (argv[2]), atof (argv[3])); } else if (argc == 4 && strcmp (argv[1], "ff-decode") == 0) { WavData wd; Error err = ff_decode (argv[2], wd); if (err) { error ("audiowmark: hls: ff_decode failed: %s\n", err.message()); return 1; } err = wd.save (argv[3]); if (err) { error ("audiowmark: hls: save failed: %s\n", err.message()); return 1; } return 0; } else { error ("testhls: error parsing command line arguments\n"); return 1; } } audiowmark-0.6.5/src/testlimiter.cc000066400000000000000000000065531501136502400173360ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "sfinputstream.hh" #include "sfoutputstream.hh" #include "utils.hh" #include "limiter.hh" using std::string; using std::vector; using std::max; using std::min; int perf() { Limiter limiter (2, 44100); limiter.set_block_size_ms (1000); vector samples (2 * 1024); int n_frames = 0; double start = get_time(); for (int i = 0; i < 100000; i++) { n_frames += samples.size() / 2; vector out_samples = limiter.process (samples); } double end = get_time(); printf ("%f ns/frame\n", (end - start) * 1000 * 1000 * 1000 / n_frames); return 0; } int impulses() { Limiter limiter (2, 44100); limiter.set_block_size_ms (3); limiter.set_ceiling (0.9); vector in_all, out_all; int pos = 0; for (int block = 0; block < 10; block++) { vector in_samples; for (int i = 0; i < 1024; i++) { double d = (pos++ % 441) == 440 ? 1.0 : 0.5; in_samples.push_back (d); in_samples.push_back (d); /* stereo */ } vector out_samples = limiter.process (in_samples); in_all.insert (in_all.end(), in_samples.begin(), in_samples.end()); out_all.insert (out_all.end(), out_samples.begin(), out_samples.end()); } vector out_samples = limiter.flush(); out_all.insert (out_all.end(), out_samples.begin(), out_samples.end()); assert (in_all.size() == out_all.size()); for (size_t i = 0; i < out_all.size(); i += 2) { assert (out_all[i] == out_all[i + 1]); /* stereo */ printf ("%f %f\n", in_all[i], out_all[i]); } return 0; } int main (int argc, char **argv) { if (argc == 2 && strcmp (argv[1], "perf") == 0) return perf(); if (argc == 2 && strcmp (argv[1], "impulses") == 0) return impulses(); SFInputStream in; SFOutputStream out; Error err = in.open (argv[1]); if (err) { fprintf (stderr, "testlimiter: open input failed: %s\n", err.message()); return 1; } err = out.open (argv[2], in.n_channels(), in.sample_rate(), 16, Encoding::SIGNED); if (err) { fprintf (stderr, "testlimiter: open output failed: %s\n", err.message()); return 1; } Limiter limiter (in.n_channels(), in.sample_rate()); limiter.set_block_size_ms (1000); limiter.set_ceiling (0.9); vector in_samples; do { in.read_frames (in_samples, 1024); for (auto& s: in_samples) s *= 1.1; vector out_samples = limiter.process (in_samples); out.write_frames (out_samples); } while (in_samples.size()); out.write_frames (limiter.flush()); } audiowmark-0.6.5/src/testmp3.cc000066400000000000000000000033621501136502400163630ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "mp3inputstream.hh" #include "wavdata.hh" using std::string; int main (int argc, char **argv) { WavData wd; if (argc >= 2) { if (MP3InputStream::detect (argv[1])) { MP3InputStream m3i; Error err = m3i.open (argv[1]); if (err) { printf ("mp3 open %s failed: %s\n", argv[1], err.message()); return 1; } err = wd.load (&m3i); if (!err) { int sec = wd.n_values() / wd.n_channels() / wd.sample_rate(); printf ("loaded mp3 %s: %d:%02d\n", argv[1], sec / 60, sec % 60); if (argc == 3) { wd.save (argv[2]); printf ("saved wav: %s\n", argv[2]); } } else { printf ("mp3 load %s failed: %s\n", argv[1], err.message()); return 1; } } else { printf ("mp3 detect %s failed\n", argv[1]); return 1; } } } audiowmark-0.6.5/src/testmpegts.cc000066400000000000000000000047461501136502400171720ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "mpegts.hh" using std::string; using std::vector; using std::map; using std::regex; int main (int argc, char **argv) { if (argc == 5 && strcmp (argv[1], "append") == 0) { printf ("append: in=%s out=%s fn=%s\n", argv[2], argv[3], argv[4]); TSWriter writer; writer.append_file (argv[4], argv[4]); Error err = writer.process (argv[2], argv[3]); if (err) { error ("ts_append: %s\n", err.message()); return 1; } } else if (argc == 3 && strcmp (argv[1], "list") == 0) { TSReader reader; Error err = reader.load (argv[2]); for (auto entry : reader.entries()) printf ("%s %zd\n", entry.filename.c_str(), entry.data.size()); } else if (argc == 4 && strcmp (argv[1], "get") == 0) { TSReader reader; Error err = reader.load (argv[2]); for (auto entry : reader.entries()) if (entry.filename == argv[3]) fwrite (&entry.data[0], 1, entry.data.size(), stdout); } else if (argc == 3 && strcmp (argv[1], "vars") == 0) { TSReader reader; Error err = reader.load (argv[2]); map vars = reader.parse_vars ("vars"); for (auto v : vars) printf ("%s=%s\n", v.first.c_str(), v.second.c_str()); } else if (argc == 3 && strcmp (argv[1], "perf") == 0) { for (int i = 0; i < 1000; i++) { TSReader reader; Error err = reader.load (argv[2]); if (i == 42) for (auto entry : reader.entries()) printf ("%s %zd\n", entry.filename.c_str(), entry.data.size()); } } else { error ("testmpegts: error parsing command line arguments\n"); } } audiowmark-0.6.5/src/testrandom.cc000066400000000000000000000025221501136502400171410ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "utils.hh" #include "random.hh" using std::vector; using std::string; int main (int argc, char **argv) { Key key; Random rng (key, 0xf00f1234b00b5678U, Random::Stream::bit_order); for (size_t i = 0; i < 20; i++) { uint64_t x = rng(); printf ("%016" PRIx64 "\n", x); } for (size_t i = 0; i < 20; i++) printf ("%f\n", rng.random_double()); uint64_t s = 0; double t_start = get_time(); size_t runs = 25000000; for (size_t i = 0; i < runs; i++) { s += rng(); } double t_end = get_time(); printf ("s=%016" PRIx64 "\n\n", s); printf ("%f Mvalues/sec\n", runs / (t_end - t_start) / 1000000); } audiowmark-0.6.5/src/testrawconverter.cc000066400000000000000000000114211501136502400204000ustar00rootroot00000000000000/* * Copyright (C) 2018-2024 Stefan Westerfeld * * 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 "rawconverter.hh" #include "config.h" using std::vector; using std::string; void test_int16 (const char *label, const vector& in_samples, Encoding encoding) { RawFormat format; format.set_bit_depth (16); format.set_encoding (encoding); #ifdef WORDS_BIGENDDIAN format.set_endian (RawFormat::BIG); #else format.set_endian (RawFormat::LITTLE); #endif Error error; RawConverter *converter = RawConverter::create (format, error); if (error) { printf ("error: %s\n", error.message()); exit (1); } vector conv (in_samples.size() * 4); converter->to_raw (in_samples.data(), conv.data(), in_samples.size()); float max_diff = 0; for (size_t i = 0; i < in_samples.size(); i++) { if (encoding == Encoding::SIGNED) max_diff = std::max (fabs (in_samples[i] * (1 << 15) - ((int16_t *)conv.data())[i]), max_diff); else max_diff = std::max (fabs ((in_samples[i] + 1) * (1 << 15) - ((uint16_t *)conv.data())[i]), max_diff); } printf ("%s: max_diff = %f [ should be less than 1.01 ]\n", label, max_diff); assert (max_diff < 1.01); } string hash_bytes (vector& bytes) { string result; std::array hash; gcry_md_hash_buffer (GCRY_MD_SHA1, hash.data(), bytes.data(), bytes.size()); for (auto ch : hash) result += string_printf ("%02x", ch); return result; } int main (int argc, char **argv) { std::set hashes; Error error; RawFormat format; uint64_t K = 33452759; // prime vector in_samples (K), out_samples (K); vector bytes (K * 4); for (uint64_t k = 0; k < K; k++) in_samples[k] = (-1 + double (2 * k) / (K - 1)); test_int16 ("int16", in_samples, Encoding::SIGNED); test_int16 ("uint16", in_samples, Encoding::UNSIGNED); printf ("\n"); for (auto bit_depth : { 16, 24, 32 }) { for (auto encoding : { Encoding::SIGNED, Encoding::UNSIGNED }) { for (auto endian : { RawFormat::LITTLE, RawFormat::BIG }) { format.set_bit_depth (bit_depth); format.set_encoding (encoding); format.set_endian (endian); RawConverter *converter = RawConverter::create (format, error); if (error) { printf ("error: %s\n", error.message()); return 1; } std::fill (bytes.begin(), bytes.end(), 0); double time1 = get_time(); converter->to_raw (in_samples.data(), bytes.data(), in_samples.size()); double time2 = get_time(); converter->from_raw (bytes.data(), out_samples.data(), in_samples.size()); double time3 = get_time(); double max_err = 0; for (size_t i = 0; i < in_samples.size(); i++) max_err = std::max (max_err, std::abs (double (in_samples[i]) - double (out_samples[i]))); double ebits = log2 (max_err); printf ("%s %d %s endian %f", format.encoding() == Encoding::SIGNED ? "signed" : "unsigned", format.bit_depth(), format.endian() == RawFormat::LITTLE ? "little" : "big", ebits); double min_ebits = -format.bit_depth() + 0.9; double max_ebits = -format.bit_depth() + 1; double ns_per_sample_to = (time2 - time1) * 1e9 / K; double ns_per_sample_from = (time3 - time2) * 1e9 / K; printf (" (should be in [%.2f,%.2f]) - to raw: %f ns/sample - from raw: %f ns/sample\n", min_ebits, max_ebits, ns_per_sample_to, ns_per_sample_from); assert (ebits <= max_ebits && ebits >= min_ebits); /* every raw converted buffer should be different */ string hash = hash_bytes (bytes); assert (hashes.count (hash) == 0); hashes.insert (hash); } } printf ("\n"); } } audiowmark-0.6.5/src/testshortcode.cc000066400000000000000000000127161501136502400176610ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "shortcode.hh" using std::vector; using std::string; using std::map; static double gettime() { timeval tv; gettimeofday (&tv, 0); return tv.tv_sec + tv.tv_usec / 1000000.0; } vector generate_error_vector (size_t n, int errors) { vector ev (n); while (errors) { size_t pos = rand() % ev.size(); if (ev[pos] != 1) { ev[pos] = 1; errors--; } } return ev; } int hamming_weight (const vector& bits) { int w = 0; for (auto b : bits) w += b; return w; } double factorial (int x) { double p = 1; for (int i = 1; i <= x; i++) p *= i; return p; } string number_format (double d) { std::ostringstream buff; buff.imbue (std::locale("")); buff << (uint64_t) d; return buff.str(); } int main (int argc, char **argv) { srand (time (NULL)); if (argc < 2) { printf ("first argument must be code size (12, 16, 20)\n"); return 1; } size_t K = atoi (argv[1]); size_t N = short_code_init (K); if (!N) { printf ("bad code size\n"); return 1; } printf ("using (%zd,%zd) code\n", N, K); if (argc == 2) { vector in_bits; while (in_bits.size() != K) in_bits.push_back (rand() & 1); printf ("in: "); for (auto b : in_bits) printf ("%d", b); printf ("\n"); printf ("coded: "); vector coded_bits = short_encode_blk (in_bits); for (auto b : coded_bits) printf ("%d", b); printf ("\n"); vector decoded_bits = short_decode_blk (coded_bits); printf ("out: "); for (auto b : decoded_bits) printf ("%d", b); printf ("\n"); } if (argc == 3 && string (argv[2]) == "perf") { const double start_t = gettime(); const size_t runs = 100; for (size_t i = 0; i < runs; i++) { vector in_bits; while (in_bits.size() != K) in_bits.push_back (rand() & 1); vector out_bits = short_decode_blk (short_encode_blk (in_bits)); assert (out_bits == in_bits); } printf ("%.1f ms/block\n", (gettime() - start_t) / runs * 1000.0); } if (argc == 3 && string (argv[2]) == "table") { map, vector> table; vector weight (N + 1); for (size_t i = 0; i < size_t (1 << K); i++) { vector in; for (size_t bit = 0; bit < K; bit++) { if (i & (1 << bit)) in.push_back (1); else in.push_back (0); } vector coded_bits = short_encode_blk (in); table[coded_bits] = in; weight[hamming_weight (coded_bits)]++; printf ("T: "); for (auto b : coded_bits) printf ("%d", b); printf ("\n"); } for (size_t i = 0; i <= N; i++) { if (weight[i]) printf ("W %3zd %6d %20s\n", i, weight[i], number_format (factorial (N) / (factorial (i) * factorial (N - i)) / weight[i]).c_str()); } /* decoding test */ for (auto it : table) { assert (short_decode_blk (it.first) == it.second); } const size_t runs = 50LL * 1000 * 1000 * 1000; size_t match = 0; for (size_t i = 0; i < runs; i++) { vector in_bits = generate_error_vector (N, K); auto it = table.find (in_bits); if (it != table.end()) match++; if ((i % 1000000) == 0) { printf ("%zd / %zd\r", match, i); fflush (stdout); } } } if (argc == 3 && string (argv[2]) == "distance") { vector> cwords; for (size_t i = 0; i < size_t (1 << K); i++) { vector in; for (size_t bit = 0; bit < K; bit++) { if (i & (1 << bit)) in.push_back (1); else in.push_back (0); } cwords.push_back (short_encode_blk (in)); } int mhd = 100000; for (size_t a = 0; a < cwords.size(); a++) { for (size_t b = 0; b < cwords.size(); b++) { if (a != b) { int hd = 0; for (size_t c = 0; c < cwords[a].size(); c++) hd += cwords[a][c] ^ cwords[b][c]; if (hd < mhd) mhd = hd; } } if ((a & 255) == 0) { printf ("%zd\r", a); fflush (stdout); } } printf ("\n"); printf ("%d\n", mhd); } } audiowmark-0.6.5/src/teststream.cc000066400000000000000000000030521501136502400171530ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "sfinputstream.hh" #include "stdoutwavoutputstream.hh" #include "utils.hh" using std::string; using std::vector; int main (int argc, char **argv) { SFInputStream in; StdoutWavOutputStream out; std::string filename = (argc >= 2) ? argv[1] : "-"; Error err = in.open (filename.c_str()); if (err) { fprintf (stderr, "teststream: open input failed: %s\n", err.message()); return 1; } err = out.open (in.n_channels(), in.sample_rate(), 16, Encoding::SIGNED, in.n_frames(), false); if (err) { fprintf (stderr, "teststream: open output failed: %s\n", err.message()); return 1; } vector samples; do { in.read_frames (samples, 1024); out.write_frames (samples); } while (samples.size()); } audiowmark-0.6.5/src/testthreadpool.cc000066400000000000000000000021241501136502400200200ustar00rootroot00000000000000/* * Copyright (C) 2020 Stefan Westerfeld * * 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 "threadpool.hh" int main() { ThreadPool tp; int result1 = 0; int result2 = 0; tp.add_job ([&result1](){printf ("A\n"); sleep (2); printf ("A done\n"); result1 = 123;}); tp.add_job ([&result2](){printf ("B\n"); sleep (3); printf ("B done\n"); result2 = 456;}); tp.wait_all(); printf ("===\n"); printf ("results: %d, %d\n", result1, result2); } audiowmark-0.6.5/src/testwavformat.cc000066400000000000000000000065411501136502400176740ustar00rootroot00000000000000/* * Copyright (C) 2018-2024 Stefan Westerfeld * * 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 "sfinputstream.hh" #include "stdoutwavoutputstream.hh" #include "utils.hh" using std::string; using std::vector; int main (int argc, char **argv) { std::map formats { { "pcm_8", SF_FORMAT_PCM_U8 }, { "pcm_16", SF_FORMAT_PCM_16 }, { "pcm_24", SF_FORMAT_PCM_24 }, { "pcm_32", SF_FORMAT_PCM_32 }, { "float", SF_FORMAT_FLOAT }, { "double", SF_FORMAT_DOUBLE } }; if (argc == 2 && !strcmp (argv[1], "list")) { for (auto fi : formats) printf ("%s\n", fi.first.c_str()); } else if (argc == 3 && !strcmp (argv[1], "detect")) { SF_INFO sfinfo = { 0, }; auto sndfile = sf_open (argv[2], SFM_READ, &sfinfo); assert (sndfile); for (auto fi : formats) { if ((fi.second | SF_FORMAT_WAV) == sfinfo.format) { printf ("%s\n", fi.first.c_str()); return 0; } } fprintf (stderr, "unsupported format %d\n", sfinfo.format); return 1; } else if (argc == 5 && !strcmp (argv[1], "convert")) { SFInputStream in; std::string in_filename = argv[2]; std::string out_filename = argv[3]; std::string out_format = argv[4]; Error err = in.open (in_filename.c_str()); if (err) { fprintf (stderr, "testwavformat: open input failed: %s\n", err.message()); return 1; } SF_INFO sfinfo = {0,}; sfinfo.samplerate = in.sample_rate(); sfinfo.channels = in.n_channels(); sfinfo.format = formats[out_format]; if (!sfinfo.format) { fprintf (stderr, "testwavformat: unsupported output format %s\n", out_format.c_str()); return 1; } sfinfo.format |= SF_FORMAT_WAV; auto sndfile = sf_open (out_filename.c_str(), SFM_WRITE, &sfinfo); int error = sf_error (sndfile); if (error) { fprintf (stderr, "%s\n", sf_strerror (sndfile)); return 1; } vector samples; do { in.read_frames (samples, 1024); sf_count_t count = sf_write_float (sndfile, samples.data(), samples.size()); assert ((uint64_t) count == samples.size()); } while (samples.size()); sf_close (sndfile); } else { fprintf (stderr, "usage: testwavformat convert \n"); fprintf (stderr, "or testwavformat detect \n"); fprintf (stderr, "or testwavformat list\n"); return 1; } } audiowmark-0.6.5/src/threadpool.cc000066400000000000000000000044241501136502400171250ustar00rootroot00000000000000/* * Copyright (C) 2020 Stefan Westerfeld * * 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 "threadpool.hh" #include "utils.hh" bool ThreadPool::worker_next_job (Job& job) { std::unique_lock lck (mutex); if (stop_workers) return false; if (jobs.empty()) cond.wait (lck); if (jobs.empty()) return false; job = jobs.front(); jobs.erase (jobs.begin()); return true; } void ThreadPool::worker_run() { while (!stop_workers) { Job job; if (worker_next_job (job)) { job.fun(); std::lock_guard lg (mutex); jobs_done++; main_cond.notify_one(); } } } ThreadPool::ThreadPool() { for (unsigned int i = 0; i < std::thread::hardware_concurrency(); i++) { threads.push_back (std::thread (&ThreadPool::worker_run, this)); } } void ThreadPool::add_job (std::function fun) { std::lock_guard lg (mutex); Job job; job.fun = fun; jobs.push_back (job); jobs_added++; cond.notify_one(); } void ThreadPool::wait_all() { for (;;) { std::unique_lock lck (mutex); if (jobs_added == jobs_done) return; main_cond.wait (lck); } } size_t ThreadPool::n_threads() { return threads.size(); } ThreadPool::~ThreadPool() { { std::lock_guard lg (mutex); stop_workers = true; cond.notify_all(); } for (auto& t : threads) t.join(); if (jobs_added != jobs_done) { // user must wait before deleting the ThreadPool error ("audiowmark: open jobs in ThreadPool::~ThreadPool() [added=%zd, done=%zd] - this should not happen\n", jobs_added, jobs_done); } } audiowmark-0.6.5/src/threadpool.hh000066400000000000000000000027411501136502400171370ustar00rootroot00000000000000/* * Copyright (C) 2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_THREAD_POOL_HH #define AUDIOWMARK_THREAD_POOL_HH #include #include #include #include #include #include class ThreadPool { std::vector threads; struct Job { std::function fun; }; std::mutex mutex; std::condition_variable cond; std::condition_variable main_cond; std::vector jobs; size_t jobs_added = 0; size_t jobs_done = 0; bool stop_workers = false; bool worker_next_job (Job& job); void worker_run(); public: ThreadPool(); ~ThreadPool(); void add_job (std::function fun); void wait_all(); size_t n_threads(); }; #endif /* AUDIOWMARK_THREAD_POOL_HH */ audiowmark-0.6.5/src/ttfb-test.py000077500000000000000000000012011501136502400167340ustar00rootroot00000000000000#!/usr/bin/env python3 # test how long the watermarker takes until the first audio sample is available import subprocess import shlex import time import sys seconds = 0 for i in range (10): start_time = time.time() * 1000 proc = subprocess.Popen (shlex.split (sys.argv[1]), stdout=subprocess.PIPE, stderr=subprocess.PIPE) # we wait for actual audio data, so we read somewhat larger amount of data that the wave header x = proc.stdout.read (1000) end_time = time.time() * 1000 seconds += end_time - start_time print ("%.2f" % (end_time - start_time), x[0:4], len (x)) print ("%.2f" % (seconds / 10), "avg") audiowmark-0.6.5/src/utils.cc000066400000000000000000000121041501136502400161160ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifdef __CYGWIN__ /* needed for vasprintf */ #define _GNU_SOURCE #endif #include "utils.hh" #include "stdarg.h" #include #include #include #include #include using std::vector; using std::string; double get_time() { /* return timestamp in seconds as double */ timeval tv; gettimeofday (&tv, 0); return tv.tv_sec + tv.tv_usec / 1000000.0; } void print_memory_usage (const std::string& where) { printf ("=== memory usage (%s): ===\n", where.c_str()); // Get Peak RSS from getrusage struct rusage usage; if (getrusage (RUSAGE_SELF, &usage) == 0) { printf (" - Peak RSS: %.2f MB\n", usage.ru_maxrss / 1024.0); } else { perror ("getrusage failed"); } // Get Virtual Memory and Current RSS from /proc/self/statm FILE *fp = fopen ("/proc/self/statm", "r"); if (!fp) { perror ("Failed to open /proc/self/statm"); return; } long page_size = sysconf (_SC_PAGESIZE); // Get page size in bytes long vm_size, rss; if (fscanf (fp, "%ld %ld", &vm_size, &rss) != 2) { perror("Failed to read memory usage"); fclose (fp); return; } fclose (fp); printf (" - Current RSS: %.2f MB\n", (rss * page_size) / (1024.0 * 1024.0)); printf (" - Virtual Memory: %.2f MB\n", (vm_size * page_size) / (1024.0 * 1024.0)); } static unsigned char from_hex_nibble (char c) { int uc = (unsigned char)c; if (uc >= '0' && uc <= '9') return uc - (unsigned char)'0'; if (uc >= 'a' && uc <= 'f') return uc + 10 - (unsigned char)'a'; if (uc >= 'A' && uc <= 'F') return uc + 10 - (unsigned char)'A'; return 16; // error } vector bit_str_to_vec (const string& bits) { vector bitvec; for (auto nibble : bits) { unsigned char c = from_hex_nibble (nibble); if (c >= 16) return vector(); // error bitvec.push_back ((c & 8) > 0); bitvec.push_back ((c & 4) > 0); bitvec.push_back ((c & 2) > 0); bitvec.push_back ((c & 1) > 0); } return bitvec; } string bit_vec_to_str (const vector& bit_vec) { string bit_str; for (size_t pos = 0; pos + 3 < bit_vec.size(); pos += 4) // convert only groups of 4 bits { int nibble = 0; for (int j = 0; j < 4; j++) { if (bit_vec[pos + j]) { // j == 0 has the highest value, then 1, 2, 3 (lowest) nibble |= 1 << (3 - j); } } const char *to_hex = "0123456789abcdef"; bit_str += to_hex[nibble]; } return bit_str; } vector hex_str_to_vec (const string& str) { vector result; if ((str.size() % 2) != 0) // even length return vector(); for (size_t i = 0; i < str.size() / 2; i++) { unsigned char h = from_hex_nibble (str[i * 2]); unsigned char l = from_hex_nibble (str[i * 2 + 1]); if (h >= 16 || l >= 16) return vector(); result.push_back ((h << 4) + l); } return result; } string vec_to_hex_str (const vector& vec) { string s; for (auto byte : vec) s += string_printf ("%02x", byte); return s; } static string string_vprintf (const char *format, va_list vargs) { string s; char *str = NULL; if (vasprintf (&str, format, vargs) >= 0 && str) { s = str; free (str); } else s = format; return s; } string string_printf (const char *format, ...) { va_list ap; va_start (ap, format); string s = string_vprintf (format, ap); va_end (ap); return s; } static Log log_level = Log::INFO; void set_log_level (Log level) { log_level = level; } static void logv (Log log, const char *format, va_list vargs) { if (log >= log_level) { string s = string_vprintf (format, vargs); /* could support custom log function here */ fprintf (stderr, "%s", s.c_str()); fflush (stderr); } } void error (const char *format, ...) { va_list ap; va_start (ap, format); logv (Log::ERROR, format, ap); va_end (ap); } void warning (const char *format, ...) { va_list ap; va_start (ap, format); logv (Log::WARNING, format, ap); va_end (ap); } void info (const char *format, ...) { va_list ap; va_start (ap, format); logv (Log::INFO, format, ap); va_end (ap); } void debug (const char *format, ...) { va_list ap; va_start (ap, format); logv (Log::DEBUG, format, ap); va_end (ap); } audiowmark-0.6.5/src/utils.hh000066400000000000000000000067711501136502400161450ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_UTILS_HH #define AUDIOWMARK_UTILS_HH #include #include #ifndef __STDC_FORMAT_MACROS // some compilers/platforms (i.e. very old macOS) need this for macros like PRId64 (#61) #define __STDC_FORMAT_MACROS #endif #include std::vector bit_str_to_vec (const std::string& bits); std::string bit_vec_to_str (const std::vector& bit_vec); std::vector hex_str_to_vec (const std::string& str); std::string vec_to_hex_str (const std::vector& vec); double get_time(); void print_memory_usage (const std::string& where); template inline const T& bound (const T& min_value, const T& value, const T& max_value) { return std::min (std::max (value, min_value), max_value); } // detect compiler #if __clang__ #define AUDIOWMARK_COMP_CLANG #define AUDIOWMARK_EXTRA_OPT #elif __GNUC__ > 2 #define AUDIOWMARK_COMP_GCC #define AUDIOWMARK_EXTRA_OPT __attribute__((optimize("-O3"))) /* enable auto vectorization, some functions benefit a lot from this */ #else #error "unsupported compiler" #endif #ifdef AUDIOWMARK_COMP_GCC #define AUDIOWMARK_PRINTF(format_idx, arg_idx) __attribute__ ((__format__ (gnu_printf, format_idx, arg_idx))) #else #define AUDIOWMARK_PRINTF(format_idx, arg_idx) __attribute__ ((__format__ (__printf__, format_idx, arg_idx))) #endif /* bswap for g++ / clang++ - may need different implementation for other compilers */ static inline uint32_t bswap32 (uint32_t i) { return __builtin_bswap32 (i); } static inline uint64_t bswap64 (uint64_t i) { return __builtin_bswap64 (i); } void error (const char *format, ...) AUDIOWMARK_PRINTF (1, 2); void warning (const char *format, ...) AUDIOWMARK_PRINTF (1, 2); void info (const char *format, ...) AUDIOWMARK_PRINTF (1, 2); void debug (const char *format, ...) AUDIOWMARK_PRINTF (1, 2); enum class Log { ERROR = 3, WARNING = 2, INFO = 1, DEBUG = 0 }; void set_log_level (Log level); std::string string_printf (const char *fmt, ...) AUDIOWMARK_PRINTF (1, 2); class Error { public: enum class Code { NONE, STR }; Error (Code code = Code::NONE) : m_code (code) { switch (code) { case Code::NONE: m_message = "OK"; break; default: m_message = "Unknown error"; } } explicit Error (const std::string& message) : m_code (Code::STR), m_message (message) { } Code code() { return m_code; } const char * message() { return m_message.c_str(); } operator bool() { return m_code != Code::NONE; } private: Code m_code; std::string m_message; }; class ScopedFile { FILE *m_file; public: ScopedFile (FILE *f) : m_file (f) { } ~ScopedFile() { if (m_file) fclose (m_file); } }; #endif /* AUDIOWMARK_UTILS_HH */ audiowmark-0.6.5/src/videowmark000077500000000000000000000113561501136502400165550ustar00rootroot00000000000000#!/bin/bash function die { echo >&2 "videowmark: error: $@" exit 1 } # auto detect codec and bitrate from input stream, generate ffmpeg options for audio encoder function audio_encode_options { ffprobe -v error -print_format compact -show_streams "$1" | grep codec_type=audio | awk -F'|' '$1 == "stream" { for (i = 0; i < NF; i++) print $i }' | awk -F= ' $1 == "codec_name" { codec = $2; # opus encoder is experimental, ffmpeg recommends libopus for encoding if (codec == "opus") codec = "libopus"; printf (" -c:a %s", codec); } $1 == "bit_rate" { bit_rate = $2; if (bit_rate != "N/A") printf (" -ab %s", bit_rate); }' } # count number of audio and video streams, typical output: "audio=1:video=1" function audio_video_stream_count { ffprobe -v error -print_format compact -show_streams "$1" | awk -F'|' ' { for (i = 1; i < NF; i++) x[$i]++; } END { printf "audio=%d:video=%d\n",x["codec_type=audio"],x["codec_type=video"] }' } function create_temp_files { local fd for fd in "$@" do local tmpfile=$(mktemp /tmp/videowmark.XXXXXX) eval "exec $fd>$tmpfile" rm "$tmpfile" done } function extension { echo $1 | awk -F. '{ if (NF > 1) print $NF; }' } function add_watermark { local in_file="$1" local out_file="$2" local bits="$3" # check file extensions local ext_in=$(extension "$in_file") local ext_out=$(extension "$out_file") [ "$ext_in" == "$ext_out" ] || die "input/output extension must match ('$ext_in' vs. '$ext_out')" # check audio/video stream count local stream_count=$(audio_video_stream_count "$in_file") [ "$stream_count" == "audio=1:video=1" ] || { \ echo >&2 "videowmark: detected input file stream count: $stream_count" die "input file must have one audio stream and one video stream" } # create tmpfiles create_temp_files 3 4 local orig_wav=/dev/fd/3 local wm_wav=/dev/fd/4 # get audio as wav ffmpeg $FFMPEG_VERBOSE -y -i "$in_file" -f wav -rf64 always "$orig_wav" || die "extracting audio from video failed (ffmpeg)" # watermark [ -z "$QUIET" ] && echo >&2 "Audio Codec: $(audio_encode_options "$in_file")" audiowmark add "${AUDIOWMARK_ARGS[@]}" "$orig_wav" "$wm_wav" "$bits" \ --set-input-label "$in_file" --set-output-label "$out_file" --output-format rf64 || die "watermark generation failed (audiowmark)" # rejoin ffmpeg $FFMPEG_VERBOSE -y -i "$in_file" -i "$wm_wav" -c:v copy $(audio_encode_options "$in_file") -map 0:v:0 -map 1:a:0 "$out_file" || \ die "merging video and watermarked audio failed (ffmpeg)" } function get_watermark { local in_file="$1" # check audio/video stream count local stream_count=$(audio_video_stream_count "$in_file") [ "$stream_count" == "audio=1:video=1" ] || { \ echo >&2 "videowmark: detected input file stream count: $stream_count" die "input file must have one audio stream and one video stream" } # create tmpfiles create_temp_files 3 local wav=/dev/fd/3 # get audio as wav ffmpeg $FFMPEG_VERBOSE -y -i "$in_file" -f wav -rf64 always "$wav" || die "extracting audio from video failed (ffmpeg)" # get watermark audiowmark get "${AUDIOWMARK_ARGS[@]}" "$wav" || die "retrieving watermark from audio failed (audiowmark)" } function show_help_and_exit { cat << EOH usage: videowmark [ ... ] Commands: * create a watermarked video file with a message videowmark add * retrieve message videowmark get Global options: --strength set watermark strength --key load watermarking key from file -q, --quiet disable information messages -v, --verbose enable ffmpeg verbose output EOH exit 0 } GETOPT_TEMP=`getopt -o vhq --long verbose,quiet,help,key:,strength: -n 'videowmark' -- "$@"` [ $? != 0 ] && exit 1 # exit on option parser errors eval set -- "$GETOPT_TEMP" AUDIOWMARK_ARGS=() FFMPEG_VERBOSE="-v error" QUIET="" export AV_LOG_FORCE_NOCOLOR=1 # disable colored messages from ffmpeg while true; do case "$1" in -v | --verbose ) FFMPEG_VERBOSE="-v info"; shift ;; -q | --quiet ) AUDIOWMARK_ARGS+=("-q"); QUIET=1; shift ;; -h | --help ) show_help_and_exit ;; --key ) AUDIOWMARK_ARGS+=("--key" "$2"); shift 2 ;; --strength ) AUDIOWMARK_ARGS+=("--strength" "$2"); shift 2 ;; -- ) shift; break ;; * ) break ;; esac done if [ "$1" == "add" ] && [ "$#" == 4 ]; then add_watermark "$2" "$3" "$4" elif [ "$1" == "get" ] && [ "$#" == 2 ]; then get_watermark "$2" elif [ "$1" == "probe" ] && [ "$#" == 2 ]; then echo $2 $(audio_encode_options "$2") else echo "videowmark: error parsing command line arguments (use videowmark -h)" fi audiowmark-0.6.5/src/wavchunkloader.cc000066400000000000000000000173751501136502400200120ustar00rootroot00000000000000/* * Copyright (C) 2025 Stefan Westerfeld * * 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 "wavchunkloader.hh" #include "wmcommon.hh" #include #include using std::vector; /* WavChunkLoader reads the input file, resamples it to the watermark sample * rate and splits it into overlapping chunks. This works without knowing the * length of the input file before eof is reached. * * Example: * <-------- m_wav_data ---------> * <-overlap-> * Chunk 1: [ A A A A A A A A A A A A A A ] * * Chunk 2: [ A A A A A B B B B B B B B B ] * * Chunk 3: [ B B B B B C C ] * * Chunk 1: Fill m_wav_data by reading from the input stream (A). * * Chunk 2: Keep some overlap samples from the end of m_wav_data (A) and * fill the remaining space by reading from the input stream (B). * * Chunk 3: Keep some overlap samples (B) and try to fill the block from the * input stream (C). Since EOF was reached, we're done at this point. * * Overlap should be larger than one AB block to get all BlockDecoder results. */ WavChunkLoader::WavChunkLoader (const std::string& filename) : m_filename (filename) { } Error WavChunkLoader::open() { assert (m_state == State::NEW); Error err; m_in_stream = AudioInputStream::create (m_filename, err); if (err) { m_state = State::ERROR; return err; } m_state = State::OPEN; m_wav_data = WavData ({}, m_in_stream->n_channels(), Params::mark_sample_rate, m_in_stream->bit_depth()); /* initialize resampler if input sample rate != watermark rate */ if (m_in_stream->sample_rate() != m_wav_data.sample_rate()) m_resampler.reset (ResamplerImpl::create (m_in_stream->n_channels(), m_in_stream->sample_rate(), m_wav_data.sample_rate())); /* maximum length of the m_wav_data samples (chunk size) */ m_wav_data_max_size = lrint (Params::get_chunk_size * 60 * m_wav_data.sample_rate()) * m_wav_data.n_channels(); /* overlap size: * - should be large enough for BlockDecoder overlap (1 AB block == 2 blocks) * - take speed factor into account for speed detection */ const int overlap_blocks = 2; const double speed_factor = 1.3; const double block_seconds = (mark_sync_frame_count() + mark_data_frame_count()) * Params::frame_size / double (Params::mark_sample_rate); m_n_overlap_samples = lrint (overlap_blocks * block_seconds * speed_factor * m_wav_data.sample_rate()) * m_wav_data.n_channels(); if (m_in_stream->n_frames() != AudioInputStream::N_FRAMES_UNKNOWN) { size_t n_reserve_frames = m_in_stream->n_frames() * double (m_wav_data.sample_rate()) / m_in_stream->sample_rate(); /* since we possibly resample the input signal, we expect a slight difference between * predicted and actual space requirements, so we reserve a bit more than predicted */ n_reserve_frames *= 1.001; n_reserve_frames += 100; m_wav_data.mutable_samples().reserve (std::min (m_wav_data_max_size, n_reserve_frames * m_wav_data.n_channels())); } return Error::Code::NONE; } Error WavChunkLoader::load_next_chunk() { assert (m_state != State::ERROR); if (m_state == State::LAST_CHUNK) { m_state = State::DONE; return Error::Code::NONE; } if (m_state == State::NEW) { Error err = open(); assert (m_state != State::NEW); // m_state should be State::OPEN or State::ERROR now if (err) return err; } vector& ref_samples = m_wav_data.mutable_samples(); if (!ref_samples.empty()) /* second block or later */ { /* overlap samples with last block */ assert (ref_samples.size() >= m_n_overlap_samples); m_time_offset += ((ref_samples.size() - m_n_overlap_samples) / m_wav_data.n_channels()) / double (m_wav_data.sample_rate()); ref_samples.erase (ref_samples.begin(), ref_samples.end() - m_n_overlap_samples); } bool eof = false; Error err = refill (ref_samples, m_wav_data_max_size, &eof); if (err) { m_state = State::ERROR; return err; } if (eof) { if (ref_samples.size()) m_state = State::LAST_CHUNK; else m_state = State::DONE; } if (Params::test_truncate) { const size_t want_n_samples = m_wav_data.sample_rate() * m_wav_data.n_channels() * Params::test_truncate; if (want_n_samples > m_wav_data_max_size) return Error ("test truncate must be less than chunk size"); if (want_n_samples < ref_samples.size()) ref_samples.resize (want_n_samples); if (ref_samples.size()) m_state = State::LAST_CHUNK; else m_state = State::DONE; } return Error::Code::NONE; } void WavChunkLoader::update_capacity (vector& samples, size_t need_space, size_t max_size) { assert (need_space <= max_size); if (samples.capacity() < need_space) { /* We double the capacity of the std::vector at each step. However, if * the new capacity exceeds 40% of the total maximum size, we use the * maximum total size instead. This approach reduces peak memory usage * compared to always doubling. */ size_t cap = 8192; while (cap < need_space) cap *= 2; double new_percent_full = cap * 100. / max_size; if (new_percent_full > 40) cap = max_size; samples.reserve (cap); } assert (samples.capacity() >= need_space); } Error WavChunkLoader::refill (std::vector& samples, size_t max_size, bool *eof) { *eof = false; constexpr size_t block_size = 4096; vector buffer; while (samples.size() < max_size) { if (m_resampler) { if (m_resampler->can_read_frames() < block_size && !m_resampler_in_eof) { Error err = m_in_stream->read_frames (buffer, block_size * double (m_in_stream->sample_rate()) / m_wav_data.sample_rate()); if (err) return err; m_resampler->write_frames (buffer); if (!buffer.size()) { /* input file reached eof */ m_resampler->write_trailing_frames(); m_resampler_in_eof = true; } } buffer = m_resampler->read_frames (std::min (m_resampler->can_read_frames(), (max_size - samples.size()) / m_wav_data.n_channels())); } else { Error err = m_in_stream->read_frames (buffer, std::min (block_size, (max_size - samples.size()) / m_wav_data.n_channels())); if (err) return err; } if (!buffer.size()) { /* reached eof */ *eof = true; return Error::Code::NONE; } update_capacity (samples, samples.size() + buffer.size(), max_size); samples.insert (samples.end(), buffer.begin(), buffer.end()); m_n_total_samples += buffer.size(); } return Error::Code::NONE; } bool WavChunkLoader::done() { return m_state == State::DONE; } double WavChunkLoader::length() { assert (m_state == State::DONE); return m_n_total_samples / double (m_wav_data.sample_rate() * m_wav_data.n_channels()); } const WavData& WavChunkLoader::wav_data() { return m_wav_data; } double WavChunkLoader::time_offset() { return m_time_offset; } audiowmark-0.6.5/src/wavchunkloader.hh000066400000000000000000000036421501136502400200140ustar00rootroot00000000000000/* * Copyright (C) 2025 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_WAV_CHUNK_LOADER_HH #define AUDIOWMARK_WAV_CHUNK_LOADER_HH #include #include "utils.hh" #include "wavdata.hh" #include "resample.hh" class WavChunkLoader { std::string m_filename; double m_time_offset = 0; std::unique_ptr m_in_stream; std::unique_ptr m_resampler; bool m_resampler_in_eof = false; WavData m_wav_data; size_t m_wav_data_max_size = 0; size_t m_n_overlap_samples = 0; size_t m_n_total_samples = 0; enum class State { NEW, OPEN, LAST_CHUNK, DONE, ERROR }; State m_state = State::NEW; Error open(); void update_capacity (std::vector& samples, size_t need_space, size_t max_size); Error refill (std::vector& samples, size_t max_size, bool *eof); public: WavChunkLoader (const std::string& filename); Error load_next_chunk(); bool done(); const WavData& wav_data(); double time_offset(); double length(); }; #endif audiowmark-0.6.5/src/wavdata.cc000066400000000000000000000052511501136502400164120ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "wavdata.hh" #include "utils.hh" #include "audiostream.hh" #include "sfinputstream.hh" #include "sfoutputstream.hh" #include "mp3inputstream.hh" #include #include using std::string; using std::vector; WavData::WavData() { } WavData::WavData (const vector& samples, int n_channels, int sample_rate, int bit_depth) { m_samples = samples; m_n_channels = n_channels; m_sample_rate = sample_rate; m_bit_depth = bit_depth; } Error WavData::load (const string& filename) { Error err; std::unique_ptr in_stream = AudioInputStream::create (filename, err); if (err) return err; return load (in_stream.get()); } Error WavData::load (AudioInputStream *in_stream) { m_samples.clear(); // get rid of old contents if (in_stream->n_frames() != AudioInputStream::N_FRAMES_UNKNOWN) m_samples.reserve (in_stream->n_frames() * in_stream->n_channels()); vector m_buffer; while (true) { Error err = in_stream->read_frames (m_buffer, 1024); if (err) return err; if (!m_buffer.size()) { /* reached eof */ break; } m_samples.insert (m_samples.end(), m_buffer.begin(), m_buffer.end()); } m_sample_rate = in_stream->sample_rate(); m_n_channels = in_stream->n_channels(); m_bit_depth = in_stream->bit_depth(); return Error::Code::NONE; } Error WavData::save (const string& filename) const { std::unique_ptr out_stream; Error err; out_stream = AudioOutputStream::create (filename, m_n_channels, m_sample_rate, m_bit_depth, Encoding::SIGNED, m_samples.size() / m_n_channels, err); if (err) return err; err = out_stream->write_frames (m_samples); if (err) return err; err = out_stream->close(); return err; } int WavData::sample_rate() const { return m_sample_rate; } int WavData::bit_depth() const { return m_bit_depth; } void WavData::set_samples (const vector& samples) { m_samples = samples; } audiowmark-0.6.5/src/wavdata.hh000066400000000000000000000036031501136502400164230ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_WAV_DATA_HH #define AUDIOWMARK_WAV_DATA_HH #include #include #include "utils.hh" #include "audiostream.hh" class WavData { std::vector m_samples; int m_sample_rate = 0; int m_n_channels = 0; int m_bit_depth = 0; public: WavData(); WavData (const std::vector& samples, int n_channels, int sample_rate, int bit_depth); Error load (AudioInputStream *in_stream); Error load (const std::string& filename); Error save (const std::string& filename) const; int sample_rate() const; int bit_depth() const; int n_channels() const { return m_n_channels; } size_t n_values() const { return m_samples.size(); } size_t n_frames() const { return m_samples.size() / m_n_channels; } const std::vector& samples() const { return m_samples; } void set_samples (const std::vector& samples); std::vector& mutable_samples() { /* allow direct access to samples vector to optimize for performance and low memory usage */ return m_samples; } }; #endif /* AUDIOWMARK_WAV_DATA_HH */ audiowmark-0.6.5/src/wavpipeinputstream.cc000066400000000000000000000151271501136502400207350ustar00rootroot00000000000000/* * Copyright (C) 2018-2024 Stefan Westerfeld * * 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 "wavpipeinputstream.hh" #include "rawconverter.hh" #include #include #include #include using std::string; using std::vector; using std::min; using std::max; WavPipeInputStream::~WavPipeInputStream() { close(); } string header_get_4cc (unsigned char *bytes) { string s; s += bytes[0]; s += bytes[1]; s += bytes[2]; s += bytes[3]; return s; } static uint16_t header_get_u16 (unsigned char *bytes) { return bytes[0] + (bytes[1] << 8); } static uint32_t header_get_u32 (unsigned char *bytes) { return bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24); } Error WavPipeInputStream::read_error (const std::string& message) { if (ferror (m_input_file)) return Error (string_printf ("wav input read error: %s", strerror (errno))); else return Error (message); } Error WavPipeInputStream::open (const string& filename) { assert (m_state == State::NEW); Error err = Error::Code::NONE; if (err) return err; if (filename == "-") { m_input_file = stdin; m_close_file = false; } else { m_input_file = fopen (filename.c_str(), "r"); if (!m_input_file) return Error (strerror (errno)); m_close_file = true; } RawFormat format; unsigned char riff_buffer[12]; bool riff_ok = fread (riff_buffer, sizeof (riff_buffer), 1, m_input_file); if (!riff_ok || (header_get_4cc (riff_buffer) != "RIFF" && header_get_4cc (riff_buffer) != "RF64") || header_get_4cc (riff_buffer + 8) != "WAVE") return read_error ("input file is not a valid wav file"); bool in_data_chunk = false; bool have_fmt_chunk = false; do { unsigned char chunk[8]; if (!fread (chunk, sizeof (chunk), 1, m_input_file)) return read_error ("wav input is incomplete (no data chunk found)"); uint32_t chunk_size = header_get_u32 (chunk + 4); if (header_get_4cc (chunk) == "fmt " && chunk_size >= 16 && chunk_size <= 64 * 1024 && !have_fmt_chunk) { vector buffer (chunk_size); if (!fread (buffer.data(), buffer.size(), 1, m_input_file)) return read_error ("wav input is incomplete (error reading fmt chunk)"); int format_type = header_get_u16 (&buffer[0]); if (format_type == 3) // float encoding { format.set_encoding (Encoding::FLOAT); } else if (format_type != 1) { if (format_type == 0xFFFE && chunk_size >= 40) /* extended */ { static const std::array pcm_fmt_guid { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }; if (!std::equal (pcm_fmt_guid.begin(), pcm_fmt_guid.end(), &buffer[24])) return Error (string_printf ("wav input has unsupported extended format type, expected PCM")); } else { return Error (string_printf ("wav input has unsupported format type (%d), expected PCM", format_type)); } } format.set_channels (header_get_u16 (&buffer[2])); format.set_sample_rate (header_get_u32 (&buffer[4])); format.set_bit_depth (header_get_u16 (&buffer[14])); // 8-bit wav files are always unsigned if (format.bit_depth() == 8) format.set_encoding (Encoding::UNSIGNED); have_fmt_chunk = true; } else if (header_get_4cc (chunk) == "data") { in_data_chunk = true; } else // skip unknown chunk { char junk[1024]; while (chunk_size) { uint32_t todo = min (chunk_size, sizeof (junk)); if (!fread (junk, todo, 1, m_input_file)) return read_error ("wav input is incomplete (error skipping unknown chunk)"); chunk_size -= todo; } } } while (!in_data_chunk); if (!have_fmt_chunk) return Error ("wav input is incomplete (missing fmt chunk)"); m_raw_converter.reset (RawConverter::create (format, err)); if (err) return err; m_format = format; m_state = State::OPEN; return Error::Code::NONE; } int WavPipeInputStream::sample_rate() const { return m_format.sample_rate(); } int WavPipeInputStream::bit_depth() const { return m_format.bit_depth(); } size_t WavPipeInputStream::n_frames() const { return N_FRAMES_UNKNOWN; } int WavPipeInputStream::n_channels() const { return m_format.n_channels(); } Encoding WavPipeInputStream::encoding() const { return m_format.encoding(); } Error WavPipeInputStream::read_frames (vector& samples, size_t count) { assert (m_state == State::OPEN); const size_t block_size = 8192; const int n_channels = m_format.n_channels(); const int sample_width = m_format.bit_depth() / 8; m_input_bytes.resize (block_size * n_channels * sample_width); size_t pos = 0; while (size_t todo = min (count, block_size)) { size_t r_count = fread (m_input_bytes.data(), n_channels * sample_width, todo, m_input_file); if (ferror (m_input_file)) return Error (string_printf ("error reading wav input sample data: %s", strerror (errno))); if (!r_count) break; samples.resize (max (samples.size(), (pos + r_count) * n_channels)); m_raw_converter->from_raw (m_input_bytes.data(), samples.data() + pos * n_channels, r_count * n_channels); pos += r_count; count -= r_count; } samples.resize (pos * n_channels); return Error::Code::NONE; } void WavPipeInputStream::close() { if (m_state == State::OPEN) { if (m_close_file && m_input_file) { fclose (m_input_file); m_input_file = nullptr; m_close_file = false; } m_state = State::CLOSED; } } audiowmark-0.6.5/src/wavpipeinputstream.hh000066400000000000000000000032661501136502400207500ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_WAV_PIPE_INPUT_STREAM_HH #define AUDIOWMARK_WAV_PIPE_INPUT_STREAM_HH #include #include #include #include "audiostream.hh" #include "rawinputstream.hh" class WavPipeInputStream : public AudioInputStream { enum class State { NEW, OPEN, CLOSED }; State m_state = State::NEW; RawFormat m_format; FILE *m_input_file = nullptr; bool m_close_file = false; std::vector m_input_bytes; std::unique_ptr m_raw_converter; Error read_error (const std::string& message); public: ~WavPipeInputStream(); Error open (const std::string& filename); Error read_frames (std::vector& samples, size_t count) override; void close(); int bit_depth() const override; int sample_rate() const override; size_t n_frames() const override; int n_channels() const override; Encoding encoding() const override; }; #endif /* AUDIOWMARK_WAV_PIPE_INPUT_STREAM_HH */ audiowmark-0.6.5/src/wmadd.cc000066400000000000000000000477071501136502400160730ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "wmcommon.hh" #include "fft.hh" #include "convcode.hh" #include "limiter.hh" #include "sfinputstream.hh" #include "sfoutputstream.hh" #include "mp3inputstream.hh" #include "rawinputstream.hh" #include "rawoutputstream.hh" #include "stdoutwavoutputstream.hh" #include "shortcode.hh" #include "audiobuffer.hh" #include "resample.hh" using std::string; using std::vector; using std::complex; using std::min; using std::max; enum class FrameMod : uint8_t { KEEP = 0, UP, DOWN }; static void prepare_frame_mod (UpDownGen& up_down_gen, int f, vector& frame_mod, int data_bit) { UpDownArray up, down; up_down_gen.get (f, up, down); for (auto u : up) frame_mod[u] = data_bit ? FrameMod::UP : FrameMod::DOWN; for (auto d : down) frame_mod[d] = data_bit ? FrameMod::DOWN : FrameMod::UP; } static void apply_frame_mod (const vector& frame_mod, const vector>& fft_out, vector>& fft_delta_spect) { const float min_mag = 1e-7; // avoid computing pow (0.0, -water_delta) which would be inf for (size_t i = 0; i < frame_mod.size(); i++) { if (frame_mod[i] == FrameMod::KEEP) continue; int data_bit_sign = (frame_mod[i] == FrameMod::UP) ? 1 : -1; /* * for up bands, we want to use [for a 1 bit] (pow (mag, 1 - water_delta)) * * this actually increases the amount of energy because mag is less than 1.0 */ const float mag = abs (fft_out[i]); if (mag > min_mag) { const float mag_factor = powf (mag, -Params::water_delta * data_bit_sign); fft_delta_spect[i] = fft_out[i] * (mag_factor - 1); } } } static void mark_data (const Key& key, vector>& frame_mod, const vector& bitvec) { assert (bitvec.size() == mark_data_frame_count() / Params::frames_per_bit); assert (frame_mod.size() >= mark_data_frame_count()); const int frame_count = mark_data_frame_count(); if (Params::mix) { vector mix_entries = gen_mix_entries (key); for (int f = 0; f < frame_count; f++) { for (size_t frame_b = 0; frame_b < Params::bands_per_frame; frame_b++) { int b = f * Params::bands_per_frame + frame_b; const int data_bit = bitvec[f / Params::frames_per_bit]; const int u = mix_entries[b].up; const int d = mix_entries[b].down; const int index = mix_entries[b].frame; frame_mod[index][u] = data_bit ? FrameMod::UP : FrameMod::DOWN; frame_mod[index][d] = data_bit ? FrameMod::DOWN : FrameMod::UP; } } } else { UpDownGen up_down_gen (key, Random::Stream::data_up_down); BitPosGen bit_pos_gen (key); for (int f = 0; f < frame_count; f++) { size_t index = bit_pos_gen.data_frame (f); prepare_frame_mod (up_down_gen, f, frame_mod[index], bitvec[f / Params::frames_per_bit]); } } } static void mark_sync (const Key& key, vector>& frame_mod, int ab) { const int frame_count = mark_sync_frame_count(); assert (frame_mod.size() >= mark_sync_frame_count()); UpDownGen up_down_gen (key, Random::Stream::sync_up_down); BitPosGen bit_pos_gen (key); // sync block always written in linear order (no mix) for (int f = 0; f < frame_count; f++) { size_t index = bit_pos_gen.sync_frame (f); int data_bit = (f / Params::sync_frames_per_bit + ab) & 1; /* write 010101 for a block, 101010 for b block */ prepare_frame_mod (up_down_gen, f, frame_mod[index], data_bit); } } static void init_frame_mod_vec (const Key& key, vector>& frame_mod_vec, int ab, const vector& bitvec) { frame_mod_vec.resize (mark_sync_frame_count() + mark_data_frame_count()); for (auto& frame_mod : frame_mod_vec) frame_mod.resize (Params::max_band + 1); /* forward error correction */ ConvBlockType block_type = ab ? ConvBlockType::b : ConvBlockType::a; vector bitvec_fec = randomize_bit_order (key, code_encode (block_type, bitvec), /* encode */ true); mark_sync (key, frame_mod_vec, ab); mark_data (key, frame_mod_vec, bitvec_fec); } /* synthesizes a watermark stream (overlap add with synthesis window) * * input: per-channel fft delta values (always one frame) * output: samples */ class WatermarkSynth { const int n_channels = 0; vector window; vector synth_samples; bool first_frame = true; FFTProcessor fft_processor; void generate_window() { window.resize (Params::frame_size * 3); for (size_t i = 0; i < window.size(); i++) { const double overlap = 0.1; // triangular basic window double tri; double norm_pos = (double (i) - Params::frame_size) / Params::frame_size; if (norm_pos > 0.5) /* symmetric window */ norm_pos = 1 - norm_pos; if (norm_pos < -overlap) { tri = 0; } else if (norm_pos < overlap) { tri = 0.5 + norm_pos / (2 * overlap); } else { tri = 1; } // cosine window[i] = (cos (tri*M_PI+M_PI)+1) * 0.5; } } public: WatermarkSynth (int n_channels) : n_channels (n_channels), fft_processor (Params::frame_size) { generate_window(); synth_samples.resize (window.size() * n_channels); } vector run (const vector>>& fft_delta_spect) { const size_t synth_frame_sz = Params::frame_size * n_channels; /* move frame 1 and frame 2 to frame 0 and frame 1 */ std::copy (synth_samples.begin() + synth_frame_sz, synth_samples.end(), synth_samples.begin()); /* zero out frame 2 */ std::fill (synth_samples.begin() + synth_frame_sz * 2, synth_samples.end(), 0); for (int ch = 0; ch < n_channels; ch++) { /* mix watermark signal to output frame */ vector fft_delta_out = fft_processor.ifft (fft_delta_spect[ch]); for (int dframe = 0; dframe <= 2; dframe++) { const int wstart = dframe * Params::frame_size; int pos = dframe * Params::frame_size * n_channels + ch; for (size_t x = 0; x < Params::frame_size; x++) { synth_samples[pos] += fft_delta_out[x] * window[wstart + x]; pos += n_channels; } } } if (first_frame) { first_frame = false; return {}; } else { vector out_samples (synth_samples.begin(), synth_samples.begin() + Params::frame_size * n_channels); return out_samples; } } size_t skip (size_t zeros) { assert (zeros % Params::frame_size == 0); if (first_frame && zeros > 0) { first_frame = false; return zeros - Params::frame_size; } else return zeros; } }; /* generates a watermark signal * * input: original signal samples (always for one complete frame) * output: watermark signal (to be mixed to the original sample) */ class WatermarkGen { const int n_channels = 0; const size_t frames_per_block = 0; size_t frame_number = 0; int m_data_blocks = 0; FFTAnalyzer fft_analyzer; WatermarkSynth wm_synth; vector bitvec; vector> frame_mod_vec_a; vector> frame_mod_vec_b; public: WatermarkGen (int n_channels, const vector& bitvec) : n_channels (n_channels), frames_per_block (mark_sync_frame_count() + mark_data_frame_count()), fft_analyzer (n_channels), wm_synth (n_channels), bitvec (bitvec) { /* start writing a partial B-block as padding */ assert (frames_per_block > Params::frames_pad_start); frame_number = 2 * frames_per_block - Params::frames_pad_start; } vector run (const Key& key, const vector& samples) { assert (samples.size() == Params::frame_size * n_channels); vector>> fft_out = fft_analyzer.run_fft (samples, 0); vector>> fft_delta_spect; for (int ch = 0; ch < n_channels; ch++) fft_delta_spect.push_back (vector> (fft_out.back().size())); const vector& frame_mod = get_frame_mod (key); for (int ch = 0; ch < n_channels; ch++) apply_frame_mod (frame_mod, fft_out[ch], fft_delta_spect[ch]); frame_number++; if (frame_number % frames_per_block == 0) m_data_blocks++; return wm_synth.run (fft_delta_spect); } size_t skip (size_t zeros) { assert (zeros % Params::frame_size == 0); frame_number += zeros / Params::frame_size; return wm_synth.skip (zeros); } const vector& get_frame_mod (const Key& key) { const size_t f = frame_number % (frames_per_block * 2); if (f >= frames_per_block) /* B block */ { if (frame_mod_vec_b.empty()) init_frame_mod_vec (key, frame_mod_vec_b, 1, bitvec); return frame_mod_vec_b[f - frames_per_block]; } else /* A block */ { if (frame_mod_vec_a.empty()) init_frame_mod_vec (key, frame_mod_vec_a, 0, bitvec); return frame_mod_vec_a[f]; } } int data_blocks() const { // first block is padding - a partial B block return max (m_data_blocks - 1, 0); } }; /* generate a watermark at Params::mark_sample_rate and resample to whatever the original signal has * * input: samples from original signal (always one frame) * output: watermark signal resampled to original signal sample rate */ class WatermarkResampler { std::unique_ptr in_resampler; std::unique_ptr out_resampler; WatermarkGen wm_gen; const bool need_resampler = false; public: WatermarkResampler (int n_channels, int input_rate, const vector& bitvec) : wm_gen (n_channels, bitvec), need_resampler (input_rate != Params::mark_sample_rate) { if (need_resampler) { in_resampler.reset (ResamplerImpl::create (n_channels, input_rate, Params::mark_sample_rate)); out_resampler.reset (ResamplerImpl::create (n_channels, Params::mark_sample_rate, input_rate)); } } bool init_ok() { if (need_resampler) return (in_resampler && out_resampler); else return true; } vector run (const Key& key, const vector& samples) { if (!need_resampler) { /* cheap case: if no resampling is necessary, just generate the watermark signal */ return wm_gen.run (key, samples); } /* resample to the watermark sample rate */ in_resampler->write_frames (samples); while (in_resampler->can_read_frames() >= Params::frame_size) { vector r_samples = in_resampler->read_frames (Params::frame_size); /* generate watermark at normalized sample rate */ vector wm_samples = wm_gen.run (key, r_samples); /* resample back to the original sample rate of the audio file */ out_resampler->write_frames (wm_samples); } size_t to_read = out_resampler->can_read_frames(); return out_resampler->read_frames (to_read); } size_t skip (size_t zeros) { assert (zeros % Params::frame_size == 0); if (!need_resampler) { return wm_gen.skip (zeros); /* cheap case */ } else { /* resample to the watermark sample rate */ size_t out = in_resampler->skip (zeros); out = wm_gen.skip (out); return out_resampler->skip (out); } } int data_blocks() const { return wm_gen.data_blocks(); } }; void info_format (const string& label, const RawFormat& format) { string e = "*unknown encoding*"; switch (format.encoding()) { case Encoding::SIGNED: e = "signed"; break; case Encoding::UNSIGNED: e = "unsigned"; break; case Encoding::FLOAT: e = "float"; break; } info ("%-13s %d Hz, %d Channels, %d Bit (%s %s-endian)\n", (label + ":").c_str(), format.sample_rate(), format.n_channels(), format.bit_depth(), e.c_str(), format.endian() == RawFormat::Endian::LITTLE ? "little" : "big"); } int add_stream_watermark (const Key& key, AudioInputStream *in_stream, AudioOutputStream *out_stream, const string& bits, size_t zero_frames) { auto bitvec = parse_payload (bits); if (bitvec.empty()) return 1; /* sanity checks */ if (in_stream->sample_rate() != out_stream->sample_rate()) { error ("audiowmark: input sample rate (%d) and output sample rate (%d) don't match\n", in_stream->sample_rate(), out_stream->sample_rate()); return 1; } if (in_stream->n_channels() != out_stream->n_channels()) { error ("audiowmark: input channels (%d) and output channels (%d) don't match\n", in_stream->n_channels(), out_stream->n_channels()); return 1; } /* write some informational messages */ info ("Message: %s\n", bit_vec_to_str (bitvec).c_str()); info ("Strength: %.6g\n\n", Params::water_delta * 1000); if (in_stream->n_frames() == AudioInputStream::N_FRAMES_UNKNOWN) { info ("Time: unknown\n"); } else { size_t orig_seconds = in_stream->n_frames() / in_stream->sample_rate(); info ("Time: %zd:%02zd\n", orig_seconds / 60, orig_seconds % 60); } info ("Sample Rate: %d\n", in_stream->sample_rate()); info ("Channels: %d\n", in_stream->n_channels()); vector samples; const int n_channels = in_stream->n_channels(); AudioBuffer audio_buffer (n_channels); WatermarkResampler wm_resampler (n_channels, in_stream->sample_rate(), bitvec); if (!wm_resampler.init_ok()) return 1; Limiter limiter (n_channels, in_stream->sample_rate()); limiter.set_block_size_ms (Params::limiter_block_size_ms); limiter.set_ceiling (Params::limiter_ceiling); /* for signal to noise ratio */ double snr_delta_power = 0; double snr_signal_power = 0; size_t total_input_frames = 0; size_t total_output_frames = 0; size_t zero_frames_in = zero_frames; size_t zero_frames_out = zero_frames; Error err; if (zero_frames_in >= Params::frame_size) { const size_t skip_frames = zero_frames_in - zero_frames_in % Params::frame_size; total_input_frames += skip_frames; size_t out = wm_resampler.skip (skip_frames); audio_buffer.write_frames (std::vector ((skip_frames - out) * n_channels)); out = limiter.skip (out); assert (out < zero_frames_out); zero_frames_out -= out; total_output_frames += out; zero_frames_in -= skip_frames; } while (true) { if (zero_frames_in > 0) { err = in_stream->read_frames (samples, Params::frame_size - zero_frames_in); samples.insert (samples.begin(), zero_frames_in * n_channels, 0); zero_frames_in = 0; } else { err = in_stream->read_frames (samples, Params::frame_size); } if (err) { error ("audiowmark: input stream read failed: %s\n", err.message()); return 1; } total_input_frames += samples.size() / n_channels; if (samples.size() < Params::frame_size * n_channels) { if (total_input_frames == total_output_frames) break; /* zero sample padding after the actual input */ samples.resize (Params::frame_size * n_channels); } audio_buffer.write_frames (samples); samples = wm_resampler.run (key, samples); size_t to_read = samples.size() / n_channels; vector orig_samples = audio_buffer.read_frames (to_read); assert (samples.size() == orig_samples.size()); if (Params::snr) { for (size_t i = 0; i < samples.size(); i++) { const double orig = orig_samples[i]; // original sample const double delta = samples[i]; // watermark snr_delta_power += delta * delta; snr_signal_power += orig * orig; } } for (size_t i = 0; i < samples.size(); i++) samples[i] += orig_samples[i]; if (!Params::test_no_limiter) samples = limiter.process (samples); size_t max_write_frames = total_input_frames - total_output_frames; if (samples.size() > max_write_frames * n_channels) samples.resize (max_write_frames * n_channels); const size_t cut_frames = min (samples.size() / n_channels, zero_frames_out); if (cut_frames > 0) { samples.erase (samples.begin(), samples.begin() + cut_frames * n_channels); total_output_frames += cut_frames; zero_frames_out -= cut_frames; } err = out_stream->write_frames (samples); if (err) { error ("audiowmark output write failed: %s\n", err.message()); return 1; } total_output_frames += samples.size() / n_channels; } if (Params::snr) info ("SNR: %f dB\n", 10 * log10 (snr_signal_power / snr_delta_power)); info ("Data Blocks: %d\n", wm_resampler.data_blocks()); if (in_stream->n_frames() != AudioInputStream::N_FRAMES_UNKNOWN) { const size_t expect_frames = in_stream->n_frames() + zero_frames; if (total_output_frames != expect_frames) { auto msg = string_printf ("unexpected EOF; input frames (%zd) != output frames (%zd)", expect_frames, total_output_frames); if (Params::strict) { error ("audiowmark: error: %s\n", msg.c_str()); return 1; } warning ("audiowmark: warning: %s\n", msg.c_str()); } } err = out_stream->close(); if (err) { error ("audiowmark: closing output stream failed: %s\n", err.message()); return 1; } return 0; } int add_watermark (const Key& key, const string& infile, const string& outfile, const string& bits) { /* open input stream */ Error err; std::unique_ptr in_stream = AudioInputStream::create (infile, err); if (err) { error ("audiowmark: error opening %s: %s\n", infile.c_str(), err.message()); return 1; } /* open output stream */ int out_bit_depth = in_stream->bit_depth(); Encoding out_encoding = in_stream->encoding(); if (in_stream->bit_depth() < 16) { out_bit_depth = 16; out_encoding = Encoding::SIGNED; } std::unique_ptr out_stream; out_stream = AudioOutputStream::create (outfile, in_stream->n_channels(), in_stream->sample_rate(), out_bit_depth, out_encoding, in_stream->n_frames(), err); if (err) { error ("audiowmark: error writing to %s: %s\n", outfile.c_str(), err.message()); return 1; } /* write input/output stream details */ info ("Input: %s\n", Params::input_label.size() ? Params::input_label.c_str() : infile.c_str()); if (Params::input_format == Format::RAW) info_format ("Raw Input", Params::raw_input_format); info ("Output: %s\n", Params::output_label.size() ? Params::output_label.c_str() : outfile.c_str()); if (Params::output_format == Format::RAW) info_format ("Raw Output", Params::raw_output_format); return add_stream_watermark (key, in_stream.get(), out_stream.get(), bits, 0); } audiowmark-0.6.5/src/wmcommon.cc000066400000000000000000000157011501136502400166200ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "wmcommon.hh" #include "fft.hh" #include "convcode.hh" #include "shortcode.hh" using std::string; using std::vector; using std::complex; int Params::frames_per_bit = 2; double Params::water_delta = 0.01; bool Params::mix = true; bool Params::hard = false; // hard decode bits? (soft decoding is better) bool Params::snr = false; // compute/show snr while adding watermark bool Params::strict = false; bool Params::detect_speed = false; bool Params::detect_speed_patient = false; double Params::try_speed = -1; double Params::test_speed = -1; double Params::sync_threshold2 = 0.35; int Params::get_n_best = 8; size_t Params::payload_size = 128; bool Params::payload_short = false; int Params::test_cut = 0; // for sync test bool Params::test_no_sync = false; // disable sync bool Params::test_no_limiter = false; // disable limiter int Params::test_truncate = 0; int Params::expect_matches = -1; double Params::get_chunk_size = 30; Format Params::input_format = Format::AUTO; Format Params::output_format = Format::AUTO; RawFormat Params::raw_input_format; RawFormat Params::raw_output_format; int Params::hls_bit_rate = 0; string Params::json_output; string Params::input_label; string Params::output_label; FFTAnalyzer::FFTAnalyzer (int n_channels) : m_n_channels (n_channels), m_fft_processor (Params::frame_size) { m_window = gen_normalized_window (Params::frame_size); } /* safe to call from any thread */ vector FFTAnalyzer::gen_normalized_window (size_t n_values) { vector window (n_values); /* generate analysis window */ double window_weight = 0; for (size_t i = 0; i < n_values; i++) { const double n_values_2 = n_values / 2.0; const double win = window_cos ((i - n_values_2) / n_values_2); window[i] = win; window_weight += win; } /* normalize window using window weight */ for (size_t i = 0; i < n_values; i++) { window[i] *= 2.0 / window_weight; } return window; } vector>> FFTAnalyzer::run_fft (const vector& samples, size_t start_index) { assert (samples.size() >= (Params::frame_size + start_index) * m_n_channels); float *frame = m_fft_processor.in(); float *frame_fft = m_fft_processor.out(); vector>> fft_out; for (int ch = 0; ch < m_n_channels; ch++) { size_t pos = start_index * m_n_channels + ch; assert (pos + (Params::frame_size - 1) * m_n_channels < samples.size()); /* deinterleave frame data and apply window */ for (size_t x = 0; x < Params::frame_size; x++) { frame[x] = samples[pos] * m_window[x]; pos += m_n_channels; } /* FFT transform */ m_fft_processor.fft(); /* complex and frame_fft have the same layout in memory */ const complex *first = (complex *) frame_fft; const complex *last = first + Params::frame_size / 2 + 1; fft_out.emplace_back (first, last); } return fft_out; } vector>> FFTAnalyzer::fft_range (const vector& samples, size_t start_index, size_t frame_count) { vector>> fft_out; /* if there is not enough space for frame_count values, return an error (empty vector) */ if (samples.size() < (start_index + frame_count * Params::frame_size) * m_n_channels) return fft_out; for (size_t f = 0; f < frame_count; f++) { const size_t frame_start = (f * Params::frame_size) + start_index; vector>> frame_result = run_fft (samples, frame_start); for (auto& fr : frame_result) fft_out.emplace_back (std::move (fr)); } return fft_out; } BitPosGen::BitPosGen (const Key& key) { int frame_count = mark_data_frame_count() + mark_sync_frame_count(); for (int i = 0; i < frame_count; i++) pos_vec.push_back (i); Random random (key, 0, Random::Stream::frame_position); random.shuffle (pos_vec); } int BitPosGen::sync_frame (int f) { assert (f >= 0 && size_t (f) < mark_sync_frame_count()); return pos_vec[f]; } int BitPosGen::data_frame (int f) { assert (f >= 0 && size_t (f) < mark_data_frame_count()); return pos_vec[f + mark_sync_frame_count()]; } size_t mark_data_frame_count() { return code_size (ConvBlockType::a, Params::payload_size) * Params::frames_per_bit; } size_t mark_sync_frame_count() { return Params::sync_bits * Params::sync_frames_per_bit; } vector gen_mix_entries (const Key& key) { const int frame_count = mark_data_frame_count(); vector mix_entries (frame_count * Params::bands_per_frame); UpDownGen up_down_gen (key, Random::Stream::data_up_down); BitPosGen bit_pos_gen (key); int entry = 0; for (int f = 0; f < frame_count; f++) { const int index = bit_pos_gen.data_frame (f); UpDownArray up, down; up_down_gen.get (f, up, down); assert (up.size() == down.size()); for (size_t i = 0; i < up.size(); i++) mix_entries[entry++] = { index, up[i], down[i] }; } Random random (key, /* seed */ 0, Random::Stream::mix); random.shuffle (mix_entries); return mix_entries; } int frame_count (const WavData& wav_data) { return wav_data.n_values() / wav_data.n_channels() / Params::frame_size; } vector parse_payload (const string& bits) { auto bitvec = bit_str_to_vec (bits); if (bitvec.empty()) { error ("audiowmark: cannot parse bits '%s'\n", bits.c_str()); return {}; } if ((Params::payload_short || Params::strict) && bitvec.size() != Params::payload_size) { error ("audiowmark: number of message bits must match payload size (%zd bits)\n", Params::payload_size); return {}; } if (bitvec.size() > Params::payload_size) { error ("audiowmark: number of bits in message '%s' larger than payload size\n", bits.c_str()); return {}; } if (bitvec.size() < Params::payload_size) { /* expand message automatically; good for testing, not so good in production (disabled by --strict) */ vector expanded_bitvec; for (size_t i = 0; i < Params::payload_size; i++) expanded_bitvec.push_back (bitvec[i % bitvec.size()]); bitvec = expanded_bitvec; } return bitvec; } audiowmark-0.6.5/src/wmcommon.hh000066400000000000000000000154121501136502400166310ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_WM_COMMON_HH #define AUDIOWMARK_WM_COMMON_HH #include #include #include "random.hh" #include "rawinputstream.hh" #include "wavdata.hh" #include "fft.hh" #include enum class Format { AUTO = 1, RAW, RF64, WAV_PIPE }; class Params { public: static constexpr size_t frame_size = 1024; static int frames_per_bit; static constexpr size_t bands_per_frame = 30; static constexpr int max_band = 100; static constexpr int min_band = 20; static double water_delta; static std::string json_output; static bool strict; static bool mix; static bool hard; // hard decode bits? (soft decoding is better) static bool snr; // compute/show snr while adding watermark static bool detect_speed; static bool detect_speed_patient; static double try_speed; // manual speed correction static double test_speed; // for debugging --detect-speed static size_t payload_size; // number of payload bits for the watermark static bool payload_short; static constexpr int sync_bits = 6; static constexpr int sync_frames_per_bit = 85; static constexpr int sync_search_step = 256; static constexpr int sync_search_fine = 8; static double sync_threshold2; // minimum refined quality static int get_n_best; // minimum number of matches per decode step static constexpr size_t frames_pad_start = 250; // padding at start, in case track starts with silence static constexpr int mark_sample_rate = 44100; // watermark generation and detection sample rate static constexpr double limiter_block_size_ms = 1000; static constexpr double limiter_ceiling = 0.99; static double get_chunk_size; // chunk size for audiowmark get to reduce memory usage static int test_cut; // for sync test static bool test_no_sync; static bool test_no_limiter; static int test_truncate; static int expect_matches; static Format input_format; static Format output_format; static RawFormat raw_input_format; static RawFormat raw_output_format; static int hls_bit_rate; // input/output labels can be set for pretty output for videowmark add static std::string input_label; static std::string output_label; }; typedef std::array UpDownArray; class UpDownGen { Random::Stream random_stream; Random random; std::vector bands_reorder; public: UpDownGen (const Key& key, Random::Stream random_stream) : random_stream (random_stream), random (key, 0, random_stream), bands_reorder (Params::max_band - Params::min_band + 1) { UpDownArray x; assert (x.size() == Params::bands_per_frame); } void get (int f, UpDownArray& up, UpDownArray& down) { for (size_t i = 0; i < bands_reorder.size(); i++) bands_reorder[i] = Params::min_band + i; random.seed (f, random_stream); // use per frame random seed random.shuffle (bands_reorder); assert (2 * Params::bands_per_frame < bands_reorder.size()); for (size_t i = 0; i < Params::bands_per_frame; i++) { up[i] = bands_reorder[i]; down[i] = bands_reorder[Params::bands_per_frame + i]; } } }; class BitPosGen { std::vector pos_vec; public: BitPosGen (const Key& key); int sync_frame (int f); int data_frame (int f); }; class FFTAnalyzer { int m_n_channels = 0; std::vector m_window; FFTProcessor m_fft_processor; public: FFTAnalyzer (int n_channels); std::vector>> run_fft (const std::vector& samples, size_t start_index); std::vector>> fft_range (const std::vector& samples, size_t start_index, size_t frame_count); static std::vector gen_normalized_window (size_t n_values); }; struct MixEntry { int frame; int up; int down; }; std::vector gen_mix_entries (const Key& key); size_t mark_data_frame_count(); size_t mark_sync_frame_count(); int frame_count (const WavData& wav_data); std::vector parse_payload (const std::string& str); template std::vector randomize_bit_order (const Key& key, const std::vector& bit_vec, bool encode) { std::vector order; for (size_t i = 0; i < bit_vec.size(); i++) order.push_back (i); Random random (key, /* seed */ 0, Random::Stream::bit_order); random.shuffle (order); std::vector out_bits (bit_vec.size()); for (size_t i = 0; i < bit_vec.size(); i++) { if (encode) out_bits[i] = bit_vec[order[i]]; else out_bits[order[i]] = bit_vec[i]; } return out_bits; } inline double window_cos (double x) /* von Hann window */ { if (fabs (x) > 1) return 0; return 0.5 * cos (x * M_PI) + 0.5; } inline double window_hamming (double x) /* sharp (rectangle) cutoffs at boundaries */ { if (fabs (x) > 1) return 0; return 0.54 + 0.46 * cos (M_PI * x); } static inline float db_from_complex (float re, float im, float min_dB) { float abs2 = re * re + im * im; if (abs2 > 0) { constexpr float log2_db_factor = 3.01029995663981; // 10 / log2 (10) // glibc log2f is a lot faster than glibc log10 return log2f (abs2) * log2_db_factor; } else return min_dB; } static inline float db_from_complex (std::complex f, float min_dB) { return db_from_complex (f.real(), f.imag(), min_dB); } int add_stream_watermark (const Key& key, AudioInputStream *in_stream, AudioOutputStream *out_stream, const std::string& bits, size_t zero_frames); int add_watermark (const Key& key, const std::string& infile, const std::string& outfile, const std::string& bits); int get_watermark (const std::vector& key_list, const std::string& infile, const std::string& orig_pattern); #endif /* AUDIOWMARK_WM_COMMON_HH */ audiowmark-0.6.5/src/wmget.cc000066400000000000000000001053351501136502400161120ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "wavdata.hh" #include "wmcommon.hh" #include "wmspeed.hh" #include "convcode.hh" #include "shortcode.hh" #include "syncfinder.hh" #include "resample.hh" #include "fft.hh" #include "threadpool.hh" #include "wavchunkloader.hh" using std::string; using std::vector; using std::map; using std::min; using std::max; using std::complex; static vector normalize_soft_bits (const vector& soft_bits) { vector norm_soft_bits; /* soft decoding produces better error correction than hard decoding */ if (Params::hard) { for (auto value : soft_bits) norm_soft_bits.push_back (value > 0 ? 1.0 : 0.0); } else { /* figure out average level of each bit */ double mean = 0; for (auto value : soft_bits) mean += fabs (value); mean /= soft_bits.size(); /* rescale from [-mean,+mean] to [0.0,1.0] */ for (auto value : soft_bits) norm_soft_bits.push_back (0.5 * (value / mean + 1)); } return norm_soft_bits; } static vector mix_decode (const Key& key, vector>>& fft_out, int n_channels) { vector raw_bit_vec; const int frame_count = mark_data_frame_count(); vector mix_entries = gen_mix_entries (key); double umag = 0, dmag = 0; for (int f = 0; f < frame_count; f++) { for (int ch = 0; ch < n_channels; ch++) { for (size_t frame_b = 0; frame_b < Params::bands_per_frame; frame_b++) { int b = f * Params::bands_per_frame + frame_b; const double min_db = -96; const size_t index = mix_entries[b].frame * n_channels + ch; const size_t next_index = (index + n_channels) < fft_out.size() ? index + n_channels : index - n_channels; const size_t prev_index = (int (index) - n_channels) >= 0 ? index - n_channels : index + n_channels; const int u = mix_entries[b].up; const int d = mix_entries[b].down; umag += db_from_complex (fft_out[index][u], min_db); umag -= (db_from_complex (fft_out[prev_index][u], min_db) + db_from_complex (fft_out[next_index][u], min_db)) * 0.5; dmag += db_from_complex (fft_out[index][d], min_db); dmag -= (db_from_complex (fft_out[prev_index][d], min_db) + db_from_complex (fft_out[next_index][d], min_db)) * 0.5; } } if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1)) { raw_bit_vec.push_back (umag - dmag); umag = 0; dmag = 0; } } return raw_bit_vec; } static vector linear_decode (const Key& key, vector>>& fft_out, int n_channels) { UpDownGen up_down_gen (key, Random::Stream::data_up_down); BitPosGen bit_pos_gen (key); vector raw_bit_vec; const int frame_count = mark_data_frame_count(); double umag = 0, dmag = 0; for (int f = 0; f < frame_count; f++) { for (int ch = 0; ch < n_channels; ch++) { const size_t index = bit_pos_gen.data_frame (f) * n_channels + ch; const size_t next_index = (index + n_channels) < fft_out.size() ? index + n_channels : index - n_channels; const size_t prev_index = (int (index) - n_channels) >= 0 ? index - n_channels : index + n_channels; UpDownArray up, down; up_down_gen.get (f, up, down); const double min_db = -96; for (auto u : up) { umag += db_from_complex (fft_out[index][u], min_db); umag -= 0.5 * (db_from_complex (fft_out[prev_index][u], min_db) + db_from_complex (fft_out[next_index][u], min_db)); } for (auto d : down) { dmag += db_from_complex (fft_out[index][d], min_db); dmag -= 0.5 * (db_from_complex (fft_out[prev_index][d], min_db) + db_from_complex (fft_out[next_index][d], min_db)); } } if ((f % Params::frames_per_bit) == (Params::frames_per_bit - 1)) { raw_bit_vec.push_back (umag - dmag); umag = 0; dmag = 0; } } return raw_bit_vec; } static vector mix_or_linear_decode (const Key& key, vector>>& fft_out, int n_channels) { if (Params::mix) return mix_decode (key, fft_out, n_channels); else return linear_decode (key, fft_out, n_channels); } class ResultSet { public: enum class Type { BLOCK, CLIP, ALL }; struct Pattern { Key key; double time = 0; vector bit_vec; float decode_error = 0; SyncFinder::Score sync_score; Type type; double speed = 0; double rating = 0; bool approx_match (const Pattern& p) const { const double time_delta = Params::frame_size / double (Params::mark_sample_rate); const double speed_delta = 0.01; return key == p.key && (fabs (time - p.time) < time_delta || (type == Type::ALL)) && bit_vec == p.bit_vec && sync_score.block_type == p.sync_score.block_type && type == p.type && fabs (speed - p.speed) < speed_delta; } }; private: std::mutex pattern_mutex; vector patterns; std::string debug_sync; void rate_patterns (const Key& key) { map pattern_rating; /* compute pattern rating for key */ for (const auto& p : patterns) { if (p.key == key) { /* all pattern is a combination of multiple patterns, which means that * the error correction is really good; so if an all pattern exists, * it is likely to be the correct result */ float all_factor = (p.type == Type::ALL) ? 2 : 1; /* use sync quality sum to priorize patterns */ string bits = bit_vec_to_str (p.bit_vec); pattern_rating[bits] += p.sync_score.quality * all_factor; } } for (auto& p : patterns) { if (p.key == key) { string bits = bit_vec_to_str (p.bit_vec); p.rating = pattern_rating[bits]; } } } public: void add_pattern (const Key& key, double time, SyncFinder::Score sync_score, const vector& bit_vec, float decode_error, Type pattern_type, double speed) { /* add_pattern can be called by any thread (safe to use from ThreadPool jobs) */ std::lock_guard lg (pattern_mutex); Pattern p; p.key = key; p.time = time; p.sync_score = sync_score; p.bit_vec = bit_vec; p.decode_error = decode_error; p.type = pattern_type; p.speed = speed; patterns.push_back (p); } void apply_time_offset (double time_offset) { for (auto& p : patterns) p.time += time_offset; } void sort (const vector& key_list) { for (const auto& key : key_list) rate_patterns (key); std::sort (patterns.begin(), patterns.end(), [](const Pattern& p1, const Pattern& p2) { const int all1 = p1.type == Type::ALL; const int all2 = p2.type == Type::ALL; const auto p1bits = bit_vec_to_str (p1.bit_vec); const auto p2bits = bit_vec_to_str (p2.bit_vec); auto ab = [] (const Pattern& pattern) { switch (pattern.sync_score.block_type) { case ConvBlockType::a: return 0; case ConvBlockType::b: return 1; case ConvBlockType::ab: return 2; }; return 99; // should not happen }; if (p1.key.name() != p2.key.name()) return p1.key.name() < p2.key.name(); else if (p1.rating != p2.rating) return p1.rating > p2.rating; else if (all1 != all2) return all1 < all2; else if (p1.time != p2.time) return p1.time < p2.time; else if (ab (p1) != ab (p2)) return ab (p1) < ab (p2); else { return p1bits < p2bits; } }); } void merge (ResultSet& other) { /* since the ResultSet "other" was usually filled from a ThreadPool, the order * of the patterns to merge is not always the same - to make the output of * audiowmark deterministic, sort the patterns to be merged by timestamp */ vector to_merge = other.patterns; std::sort (to_merge.begin(), to_merge.end(), [](const Pattern& p1, const Pattern& p2) { return p1.time < p2.time; }); for (const auto& p : to_merge) { bool merge = true; for (auto& my_p : patterns) { if (my_p.approx_match (p)) merge = false; } if (merge) patterns.push_back (p); } /* only keep track of debug sync information for the first chunk */ if (debug_sync.empty()) debug_sync = other.debug_sync; } string json_escape (const string& s) { string result; for (unsigned ch : s) { if (ch == '"' || ch == '\\') { result += '\\'; result += ch; } else if (ch < 32) { result += string_printf ("\\u%04x", ch); } else { result += ch; } } return result; } void print_json (size_t time_length, const std::string &json_file) { FILE *outfile = fopen (json_file == "-" ? "/dev/stdout" : json_file.c_str(), "w"); if (!outfile) { perror (("audiowmark: failed to open \"" + json_file + "\":").c_str()); exit (127); } fprintf (outfile, "{ \"length\": \"%ld:%02ld\",\n", time_length / 60, time_length % 60); fprintf (outfile, " \"matches\": [\n"); int nth = 0; for (const auto& pattern : patterns) { if (nth++ != 0) fprintf (outfile, ",\n"); std::string btype; switch (pattern.sync_score.block_type) { case ConvBlockType::a: btype = "A"; break; case ConvBlockType::b: btype = "B"; break; case ConvBlockType::ab: btype = "AB"; break; } if (pattern.type == Type::ALL) btype = "ALL"; if (pattern.type == Type::CLIP) btype = "CLIP-" + btype; if (pattern.speed != 1) btype += "-SPEED"; const int seconds = pattern.time; fprintf (outfile, " { \"key\": \"%s\", \"pos\": \"%d:%02d\", \"bits\": \"%s\", \"quality\": %.5f, \"error\": %.6f, \"rating\": %.5f, \"type\": \"%s\", \"speed\": %.6f }", json_escape (pattern.key.name()).c_str(), seconds / 60, seconds % 60, bit_vec_to_str (pattern.bit_vec).c_str(), pattern.sync_score.quality, pattern.decode_error, pattern.rating, btype.c_str(), pattern.speed); } fprintf (outfile, " ]\n}\n"); fclose (outfile); } void print() { string last_key_name; bool print_speed = true; for (const auto& pattern : patterns) { if (pattern.key.name() != last_key_name) { printf ("key %s\n", pattern.key.name().c_str()); last_key_name = pattern.key.name(); print_speed = true; // one speed per key } if (print_speed) { // currently we assume that speed detection returns one best speed for each key for (auto p : patterns) if (p.key == pattern.key && p.speed != 1) { printf ("speed %.6f\n", p.speed); break; } print_speed = false; } if (pattern.type == Type::ALL) /* this is the combined pattern "all" */ { const char *extra = ""; if (pattern.speed != 1) extra = " SPEED"; printf ("pattern all %s %.3f %.3f%s\n", bit_vec_to_str (pattern.bit_vec).c_str(), pattern.sync_score.quality, pattern.decode_error, extra); } else { string block_str; switch (pattern.sync_score.block_type) { case ConvBlockType::a: block_str = "A"; break; case ConvBlockType::b: block_str = "B"; break; case ConvBlockType::ab: block_str = "AB"; break; } if (pattern.type == Type::CLIP) block_str = "CLIP-" + block_str; if (pattern.speed != 1) block_str += "-SPEED"; const int seconds = pattern.time; printf ("pattern %2d:%02d %s %.3f %.3f %s\n", seconds / 60, seconds % 60, bit_vec_to_str (pattern.bit_vec).c_str(), pattern.sync_score.quality, pattern.decode_error, block_str.c_str()); } } } int print_match_count (const vector& orig_bits) { int match_count = 0; for (auto p : patterns) { if (p.bit_vec == orig_bits) match_count++; } printf ("match_count %d %zd\n", match_count, patterns.size()); return match_count; } void set_debug_sync (const std::string& ds) { debug_sync = ds; } void print_debug_sync() { printf ("%s", debug_sync.c_str()); } double best_quality() const { double q = -1; for (const auto& pattern : patterns) if (pattern.sync_score.quality > q) q = pattern.sync_score.quality; return q; } }; /* * The block decoder is responsible for finding whole data blocks inside the * input file and decoding them. This only works for files that are large * enough to contain one data block. Incomplete blocks are ignored. * * INPUT: AA|BBBBB|AAAAA|BBB * MATCH BBBBB * MATCH AAAAA * * The basic algorithm is this: * * - use sync finder to find start index for the blocks * - decode the blocks * - try to combine A + B blocks for better error correction (AB) * - try to combine all available blocks for better error correction (all pattern) */ class BlockDecoder { int debug_sync_frame_count = 0; const double speed = 0; vector key_results; // stored here for sync debugging public: BlockDecoder (double speed) : speed (speed) { } void run (const vector& key_list, const WavData& wav_data, ResultSet& result_set) { ThreadPool thread_pool; SyncFinder sync_finder; FFTAnalyzer fft_analyzer (wav_data.n_channels()); key_results = sync_finder.search (key_list, wav_data, SyncFinder::Mode::BLOCK); for (const auto& key_result : key_results) { const Key& key = key_result.key; struct PatternRawBits { size_t index; double quality; vector raw_bit_vec; ConvBlockType block_type; }; vector pattern_raw_vec; for (auto sync_score : key_result.sync_scores) { const size_t count = mark_sync_frame_count() + mark_data_frame_count(); const size_t index = sync_score.index; auto fft_range_out = fft_analyzer.fft_range (wav_data.samples(), index, count); if (fft_range_out.size()) { /* ---- retrieve bits from watermark ---- */ vector raw_bit_vec = mix_or_linear_decode (key, fft_range_out, wav_data.n_channels()); assert (raw_bit_vec.size() == code_size (ConvBlockType::a, Params::payload_size)); raw_bit_vec = randomize_bit_order (key, raw_bit_vec, /* encode */ false); PatternRawBits raw_bits; raw_bits.index = index; raw_bits.quality = sync_score.quality; raw_bits.raw_bit_vec = raw_bit_vec; raw_bits.block_type = sync_score.block_type; pattern_raw_vec.push_back (raw_bits); /* ---- deal with this pattern ---- */ const double time = double (sync_score.index) / wav_data.sample_rate(); thread_pool.add_job ([this, key, sync_score, raw_bit_vec, time, &result_set]() { float decode_error = 0; vector bit_vec = code_decode_soft (sync_score.block_type, normalize_soft_bits (raw_bit_vec), &decode_error); if (!bit_vec.empty()) result_set.add_pattern (key, time, sync_score, bit_vec, decode_error, ResultSet::Type::BLOCK, speed); }); } } /* AB pattern: try to find an A block followed by a B block with the right distance (sync + data frame count) */ for (size_t i = 0; i < pattern_raw_vec.size(); i++) { if (pattern_raw_vec[i].block_type == ConvBlockType::b) { int best_j = -1; int best_abs_dist = Params::frame_size / 2; for (size_t j = 0; j < i; j++) { if (pattern_raw_vec[j].block_type == ConvBlockType::a) { size_t count = mark_sync_frame_count() + mark_data_frame_count(); int abs_dist = std::abs (int (pattern_raw_vec[i].index - pattern_raw_vec[j].index) - int (count * Params::frame_size)); if (abs_dist < best_abs_dist) { best_j = j; best_abs_dist = abs_dist; } } } /* join A and B block -> AB block */ if (best_j >= 0) { const auto& a_pattern = pattern_raw_vec[best_j]; const auto& b_pattern = pattern_raw_vec[i]; vector ab_bits (a_pattern.raw_bit_vec.size() * 2); for (size_t k = 0; k < a_pattern.raw_bit_vec.size(); k++) { ab_bits[k * 2] = a_pattern.raw_bit_vec[k]; ab_bits[k * 2 + 1] = b_pattern.raw_bit_vec[k]; } const double time = double (b_pattern.index) / wav_data.sample_rate(); thread_pool.add_job ([this, key, a_pattern, b_pattern, ab_bits, time, &result_set]() { float decode_error = 0; vector bit_vec = code_decode_soft (ConvBlockType::ab, normalize_soft_bits (ab_bits), &decode_error); if (!bit_vec.empty()) { SyncFinder::Score score_ab { 0, 0, ConvBlockType::ab }; score_ab.index = b_pattern.index; score_ab.quality = (a_pattern.quality + b_pattern.quality) / 2; result_set.add_pattern (key, time, score_ab, bit_vec, decode_error, ResultSet::Type::BLOCK, speed); } }); } } } /* all pattern: try to detect consecutive blocks with the right distance (sync + data frame count) */ vector best_all_blocks; for (size_t i = 0; i < pattern_raw_vec.size(); i++) { size_t count = mark_sync_frame_count() + mark_data_frame_count(); size_t max_block_idx = lrint (pattern_raw_vec.back().index / double (count * Params::frame_size) + 0.5); vector all_blocks; all_blocks.push_back (i); size_t block_idx = 1; while (block_idx <= max_block_idx) { size_t expect_start = pattern_raw_vec[all_blocks.back()].index + block_idx * (count * Params::frame_size); int best_j = -1; int best_abs_dist = block_idx * Params::frame_size / 2; /* enforce A B A B block ordering */ auto expect_block_type = pattern_raw_vec[all_blocks.back()].block_type; if (block_idx & 1) expect_block_type = expect_block_type == ConvBlockType::a ? ConvBlockType::b : ConvBlockType::a; for (size_t j = all_blocks.back(); j < pattern_raw_vec.size(); j++) { int abs_dist = std::abs (int (expect_start) - int (pattern_raw_vec[j].index)); if (abs_dist < best_abs_dist) { if (pattern_raw_vec[j].block_type == expect_block_type) { best_j = j; best_abs_dist = abs_dist; } } } if (best_j >= 0) { all_blocks.push_back (best_j); block_idx = 1; } else { block_idx++; } } auto sync_sum = [&] (auto blocks) { float sum = 0; for (auto block_idx : blocks) sum += pattern_raw_vec[block_idx].quality; return sum; }; /* prefer "all" patterns with higher sync sum */ if (sync_sum (all_blocks) > sync_sum (best_all_blocks)) best_all_blocks = all_blocks; } /* all pattern: average the A / B bits of the consecutive blocks for an "all" pattern */ if (best_all_blocks.size() > 1) { vector raw_bit_vec_all (code_size (ConvBlockType::ab, Params::payload_size)); vector raw_bit_vec_norm (2); SyncFinder::Score score_all { 0, 0 }; for (auto block_index : best_all_blocks) { const auto& pattern = pattern_raw_vec[block_index]; /* ---- update "all" pattern ---- */ score_all.quality += pattern.quality; int ab = pattern.block_type == ConvBlockType::b ? 1 : 0; for (size_t i = 0; i < pattern.raw_bit_vec.size(); i++) { raw_bit_vec_all[i * 2 + ab] += pattern.raw_bit_vec[i]; } raw_bit_vec_norm[ab]++; } for (size_t i = 0; i < raw_bit_vec_all.size(); i += 2) { raw_bit_vec_all[i] /= max (raw_bit_vec_norm[0], 1); /* normalize A soft bits with number of A blocks */ raw_bit_vec_all[i + 1] /= max (raw_bit_vec_norm[1], 1); /* normalize B soft bits with number of B blocks */ } score_all.quality /= raw_bit_vec_norm[0] + raw_bit_vec_norm[1]; vector soft_bit_vec = normalize_soft_bits (raw_bit_vec_all); thread_pool.add_job ([this, key, score_all, soft_bit_vec, &result_set]() { float decode_error = 0; vector bit_vec = code_decode_soft (ConvBlockType::ab, soft_bit_vec, &decode_error); if (!bit_vec.empty()) result_set.add_pattern (key, /* time */ 0.0, score_all, bit_vec, decode_error, ResultSet::Type::ALL, speed); }); } } thread_pool.wait_all(); debug_sync_frame_count = frame_count (wav_data); } std::string debug_sync() { /* this is really only useful for debugging, and should be used with exactly one key */ if (key_results.size() != 1) return ""; const auto& sync_scores = key_results[0].sync_scores; /* search sync markers at typical positions */ const int expect0 = Params::frames_pad_start * Params::frame_size; const int expect_step = (mark_sync_frame_count() + mark_data_frame_count()) * Params::frame_size; const int expect_end = debug_sync_frame_count * Params::frame_size; int sync_match = 0; for (int expect_index = expect0; expect_index + expect_step < expect_end; expect_index += expect_step) { for (auto sync_score : sync_scores) { if (abs (int (sync_score.index + Params::test_cut) - expect_index) < Params::frame_size / 2) { sync_match++; break; } } } return string_printf ("sync_match %d %zd\n", sync_match, sync_scores.size()); } }; /* * The clip decoder is responsible for decoding short clips. It is designed to * handle input sizes that are smaller than one data block. One case is that * the clip contains a partial A block (so the data could start after the start * of the A block and end before the end of the A block). * * ORIG: |AAAAA|BBBBB|AAAAA|BBBBB| * CLIP: |AAA| * * A clip could also contain the end of one block and the start of the next block, * like this: * * ORIG: |AAAAA|BBBBB|AAAAA|BBBBB| * CLIP: |A|BB| * * The basic algorithm is this: * * - zeropad |AAA| => 00000|AAA|00000 * - use sync finder to find start index of one long block in the zeropadded data * - decode the bits * * For files larger than one data block, we decode twice, at the beginning and end * * INPUT AAA|BBBBB|A * CLIP #1 AAA|BB * CLIP #2 BBBB|A */ class ClipDecoder { const int frames_per_block = 0; const double speed = 0; void run_padded (const vector& key_list, const WavData& wav_data, ResultSet& result_set, double time_offset_sec) { SyncFinder sync_finder; vector key_results = sync_finder.search (key_list, wav_data, SyncFinder::Mode::CLIP); FFTAnalyzer fft_analyzer (wav_data.n_channels()); ThreadPool thread_pool; for (const auto& key_result : key_results) { const Key& key = key_result.key; for (const auto& sync_score : key_result.sync_scores) { const size_t count = mark_sync_frame_count() + mark_data_frame_count(); const size_t index = sync_score.index; auto fft_range_out1 = fft_analyzer.fft_range (wav_data.samples(), index, count); auto fft_range_out2 = fft_analyzer.fft_range (wav_data.samples(), index + count * Params::frame_size, count); if (fft_range_out1.size() && fft_range_out2.size()) { const auto raw_bit_vec1 = randomize_bit_order (key, mix_or_linear_decode (key, fft_range_out1, wav_data.n_channels()), /* encode */ false); const auto raw_bit_vec2 = randomize_bit_order (key, mix_or_linear_decode (key, fft_range_out2, wav_data.n_channels()), /* encode */ false); const size_t bits_per_block = raw_bit_vec1.size(); vector raw_bit_vec; for (size_t i = 0; i < bits_per_block; i++) { if (sync_score.block_type == ConvBlockType::a) { raw_bit_vec.push_back (raw_bit_vec1[i]); raw_bit_vec.push_back (raw_bit_vec2[i]); } else { raw_bit_vec.push_back (raw_bit_vec2[i]); raw_bit_vec.push_back (raw_bit_vec1[i]); } } SyncFinder::Score sync_score_nopad = sync_score; sync_score_nopad.index = time_offset_sec * wav_data.sample_rate(); thread_pool.add_job ([this, key, raw_bit_vec, sync_score_nopad, time_offset_sec, &result_set]() { float decode_error = 0; vector bit_vec = code_decode_soft (ConvBlockType::ab, normalize_soft_bits (raw_bit_vec), &decode_error); if (!bit_vec.empty()) result_set.add_pattern (key, time_offset_sec, sync_score_nopad, bit_vec, decode_error, ResultSet::Type::CLIP, speed); }); } } } thread_pool.wait_all(); } enum class Pos { START, END }; void run_block (const vector& key_list, const WavData& wav_data, ResultSet& result_set, Pos pos) { const size_t n = (frames_per_block + 5) * Params::frame_size * wav_data.n_channels(); // range of samples used by clip: [first_sample, last_sample) size_t first_sample; size_t last_sample; size_t pad_samples_start = n; size_t pad_samples_end = n; if (pos == Pos::START) { first_sample = 0; last_sample = min (n, wav_data.n_values()); // increase padding at start for small blocks // -> (available samples + padding) must always be one L-block if (last_sample < n) pad_samples_start += n - last_sample; } else // (pos == Pos::END) { if (wav_data.n_values() <= n) return; first_sample = wav_data.n_values() - n; last_sample = wav_data.n_values(); } const double time_offset = double (first_sample) / wav_data.sample_rate() / wav_data.n_channels(); vector ext_samples (wav_data.samples().begin() + first_sample, wav_data.samples().begin() + last_sample); if (0) { printf ("%d: %f..%f\n", int (pos), time_offset, time_offset + double (ext_samples.size()) / wav_data.sample_rate() / wav_data.n_channels()); printf ("%f< >%f\n", double (pad_samples_start) / wav_data.sample_rate() / wav_data.n_channels(), double (pad_samples_end) / wav_data.sample_rate() / wav_data.n_channels()); } ext_samples.insert (ext_samples.begin(), pad_samples_start, 0); ext_samples.insert (ext_samples.end(), pad_samples_end, 0); WavData l_wav_data (ext_samples, wav_data.n_channels(), wav_data.sample_rate(), wav_data.bit_depth()); run_padded (key_list, l_wav_data, result_set, time_offset); } public: ClipDecoder(double speed) : frames_per_block (mark_sync_frame_count() + mark_data_frame_count()), speed (speed) { } void run (const vector& key_list, const WavData& wav_data, ResultSet& result_set) { const int wav_frames = wav_data.n_values() / (Params::frame_size * wav_data.n_channels()); if (wav_frames < frames_per_block * 3.1) /* clip decoder is only used for small wavs */ { run_block (key_list, wav_data, result_set, Pos::START); run_block (key_list, wav_data, result_set, Pos::END); } } }; static void decode (ResultSet& result_set, const vector& key_list, const WavData& wav_data, const vector& orig_bits, bool first_chunk) { /* * The strategy for integrating speed detection into decoding is this: * - we always (unconditionally) try to decode the watermark on the original wav data * - if detected speed is somewhat different than 1.0, we also try to decode stretched data * - we report all normal and speed results we get * * The reason to do it this way is that the detected speed may be wrong (on short clips) * and we don't want to loose a successful clip decoder match in this case. */ if (Params::detect_speed || Params::detect_speed_patient || Params::try_speed > 0) { vector speed_results; if (Params::detect_speed || Params::detect_speed_patient) speed_results = detect_speed (key_list, wav_data, !orig_bits.empty()); else { for (const auto& key : key_list) { DetectSpeedResult speed_result; speed_result.key = key; speed_result.speed = Params::try_speed; speed_results.push_back (speed_result); } } for (const auto& speed_result : speed_results) { WavData wav_data_speed = resample_ratio (wav_data, speed_result.speed, Params::mark_sample_rate * speed_result.speed); BlockDecoder block_decoder (speed_result.speed); block_decoder.run ({ speed_result.key }, wav_data_speed, result_set); if (first_chunk) { ClipDecoder clip_decoder (speed_result.speed); clip_decoder.run ({ speed_result.key }, wav_data_speed, result_set); } } } BlockDecoder block_decoder (1); block_decoder.run (key_list, wav_data, result_set); if (first_chunk) { ClipDecoder clip_decoder (1); clip_decoder.run (key_list, wav_data, result_set); } result_set.set_debug_sync (block_decoder.debug_sync()); } int report (ResultSet& result_set, size_t time_length, const vector& orig_bits) { if (!Params::json_output.empty()) result_set.print_json (time_length, Params::json_output); if (Params::json_output != "-") result_set.print(); if (!orig_bits.empty()) { int match_count = result_set.print_match_count (orig_bits); result_set.print_debug_sync(); if (Params::expect_matches >= 0) { printf ("expect_matches %d\n", Params::expect_matches); if (match_count != Params::expect_matches) return 1; } else { if (!match_count) return 1; } } return 0; } int get_watermark (const vector& key_list, const string& infile, const string& orig_pattern) { ResultSet result_set; vector orig_bitvec; if (!orig_pattern.empty()) { orig_bitvec = parse_payload (orig_pattern); if (orig_bitvec.empty()) return 1; } bool first_chunk = true; WavChunkLoader wav_chunk_loader (infile); while (!wav_chunk_loader.done()) { Error err = wav_chunk_loader.load_next_chunk(); if (err) { error ("audiowmark: error loading %s: %s\n", infile.c_str(), err.message()); return 1; } if (!wav_chunk_loader.done()) { const WavData& wav_data = wav_chunk_loader.wav_data(); assert (wav_data.sample_rate() == Params::mark_sample_rate); ResultSet chunk_result_set; decode (chunk_result_set, key_list, wav_data, orig_bitvec, first_chunk); chunk_result_set.apply_time_offset (wav_chunk_loader.time_offset()); result_set.merge (chunk_result_set); first_chunk = false; } } result_set.sort (key_list); size_t time_length = lrint (wav_chunk_loader.length()); return report (result_set, time_length, orig_bitvec); } audiowmark-0.6.5/src/wmspeed.cc000066400000000000000000000540011501136502400164240ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 "wmspeed.hh" #include "wmcommon.hh" #include "syncfinder.hh" #include "threadpool.hh" #include "fft.hh" #include "resample.hh" #include using std::function; using std::vector; using std::sort; using std::max; using std::min; static WavData get_speed_clip (double location, const WavData& in_data, double clip_seconds) { double end_sec = double (in_data.n_frames()) / in_data.sample_rate(); double start_sec = location * (end_sec - clip_seconds); if (start_sec < 0) start_sec = 0; size_t start_point = start_sec * in_data.sample_rate(); size_t end_point = std::min (start_point + clip_seconds * in_data.sample_rate(), in_data.n_frames()); #if 0 printf ("[%f %f] l%f\n", double (start_point) / in_data.sample_rate(), double (end_point) / in_data.sample_rate(), double (end_point - start_point) / in_data.sample_rate()); #endif vector out_signal (in_data.samples().begin() + start_point * in_data.n_channels(), in_data.samples().begin() + end_point * in_data.n_channels()); WavData clip_data (out_signal, in_data.n_channels(), in_data.sample_rate(), in_data.bit_depth()); return clip_data; } struct SpeedScanParams { double seconds = 0; double step = 0; int n_steps = 0; int n_center_steps = 0; }; class MagMatrix { public: struct Mags { float umag = 0; float dmag = 0; }; private: vector m_data; int m_cols = 0; int m_rows = 0; public: Mags& operator() (int row, int col) { return m_data[col * m_rows + row]; } void resize (int rows, int cols) { m_rows = rows; m_cols = cols; /* - don't preserve contents on resize * - free unused memory on resize */ vector new_data (m_rows * m_cols); m_data.swap (new_data); } int rows() { return m_rows; } }; class SpeedSync { public: struct Score { double speed = 0; double quality = 0; }; struct SyncBit { int bit = 0; int frame = 0; std::vector up; std::vector down; }; struct BitValue { float umag = 0; float dmag = 0; int count = 0; }; struct CmpState { int offset = 0; BitValue bit_values[Params::sync_bits]; }; private: static constexpr int OFFSET_SHIFT = 16; vector sync_bits; MagMatrix sync_matrix; void prepare_mags (const SpeedScanParams& scan_params); void compare (double relative_speed); template void compare_bits (vector& cmp_states, double relative_speed); std::mutex mutex; vector result_scores; const WavData& in_data; const double center; const int frames_per_block; public: SpeedSync (const Key& key, const WavData& in_data, double center) : in_data (in_data), center (center), frames_per_block (mark_sync_frame_count() + mark_data_frame_count()) { // constructor is run in the main thread; everything that is not thread-safe must happen here auto sync_finder_bits = SyncFinder::get_sync_bits (key, SyncFinder::Mode::BLOCK); for (size_t bit = 0; bit < sync_finder_bits.size(); bit++) { for (const auto& frame_bit : sync_finder_bits[bit]) { SyncBit sb; sb.bit = bit; sb.frame = frame_bit.frame; sb.up = frame_bit.up; sb.down = frame_bit.down; sync_bits.push_back (sb); } } std::sort (sync_bits.begin(), sync_bits.end(), [](const auto& s1, const auto& s2) { return s1.frame < s2.frame; }); } struct Jobs { function prepare_job; vector> search_jobs; function free_memory; }; Jobs get_jobs (const SpeedScanParams& scan_params, double speed) { Jobs jobs; jobs.prepare_job = [this, &scan_params]() { prepare_mags (scan_params); }; result_scores.clear(); for (int p = -scan_params.n_steps; p <= scan_params.n_steps; p++) { const double relative_speed = pow (scan_params.step, p) * speed / center; jobs.search_jobs.push_back ([relative_speed, this]() { compare (relative_speed); }); } jobs.free_memory = [this]() { sync_matrix.resize (0, 0); }; return jobs; } vector get_scores() { return result_scores; } double center_speed() const { return center; } }; void SpeedSync::prepare_mags (const SpeedScanParams& scan_params) { // we downsample the audio by factor 2 to improve performance WavData in_data_sub (resample_ratio_truncate (in_data, center / 2, Params::mark_sample_rate / 2, /* truncate to length */ scan_params.seconds / center)); const int sub_frame_size = Params::frame_size / 2; const int sub_sync_search_step = Params::sync_search_step / 2; vector window = FFTAnalyzer::gen_normalized_window (sub_frame_size); FFTProcessor fft_processor (sub_frame_size); float *in = fft_processor.in(); float *out = fft_processor.out(); /* set mag matrix size */ int n_sync_rows = 0; int n_sync_cols = sync_bits.size(); for (size_t ppos = 0; ppos + sub_frame_size < in_data_sub.n_frames(); ppos += sub_sync_search_step) n_sync_rows++; sync_matrix.resize (n_sync_rows, n_sync_cols); size_t pos = 0; int row = 0; while (pos + sub_frame_size < in_data_sub.n_frames()) { int col = 0; const std::vector& samples = in_data_sub.samples(); std::array fft_out_db; fft_out_db.fill (0); for (int ch = 0; ch < in_data_sub.n_channels(); ch++) { for (int i = 0; i < sub_frame_size; i++) { in[i] = samples[ch + (pos + i) * in_data_sub.n_channels()] * window[i]; } fft_processor.fft(); for (int i = Params::min_band; i <= Params::max_band; i++) { const float min_db = -96; fft_out_db[i - Params::min_band] += db_from_complex (out[i * 2], out[i * 2 + 1], min_db); } } for (const auto& sync_bit : sync_bits) { float umag = 0, dmag = 0; for (size_t i = 0; i < sync_bit.up.size(); i++) { umag += fft_out_db[sync_bit.up[i]]; dmag += fft_out_db[sync_bit.down[i]]; } sync_matrix (row, col++) = MagMatrix::Mags {umag, dmag}; } assert (col == n_sync_cols); row++; pos += sub_sync_search_step; } assert (row == n_sync_rows); } template void SpeedSync::compare_bits (vector& cmp_states, double relative_speed) { const int steps_per_frame = Params::frame_size / Params::sync_search_step; const double relative_speed_inv = 1 / relative_speed; auto begin = cmp_states.end(); auto end = cmp_states.end(); for (size_t mi = 0; mi < sync_bits.size(); mi++) { const int frame_offset = ((BLOCK * frames_per_block + sync_bits[mi].frame) * steps_per_frame * relative_speed_inv + 0.5) * (1 << OFFSET_SHIFT); while (begin > cmp_states.begin()) { auto prev = begin - 1; /* * don't use OFFSET_SHIFT here; just ensure that index is positive * to ensure that shifted value will properly round to nearest frame * later on */ int index = prev->offset + frame_offset; if (index < 0) break; begin = prev; } while (end > cmp_states.begin()) { auto prev = end - 1; int index = (prev->offset + frame_offset) >> OFFSET_SHIFT; if (index < sync_matrix.rows()) break; end = prev; } for (auto it = begin; it != end; it++) { int index = (it->offset + frame_offset) >> OFFSET_SHIFT; auto& bv = it->bit_values[sync_bits[mi].bit]; auto mags = sync_matrix (index, mi); if (BLOCK & 1) { bv.umag += mags.dmag; bv.dmag += mags.umag; } else { bv.umag += mags.umag; bv.dmag += mags.dmag; } bv.count++; } } } void SpeedSync::compare (double relative_speed) { const int steps_per_frame = Params::frame_size / Params::sync_search_step; const int pad_start = frames_per_block * steps_per_frame + /* add a bit of overlap to handle boundaries */ steps_per_frame; assert (steps_per_frame * Params::sync_search_step == Params::frame_size); vector cmp_states; for (int offset = -pad_start; offset < 0; offset++) { CmpState cs; cs.offset = offset * ((1 << OFFSET_SHIFT) / relative_speed); cmp_states.push_back (cs); } /* * we need to compare 3 blocks here: * - one block is necessary because we need to test all offsets (-pad_start..0) * - two more blocks are necessary since speed detection ScanParams uses 50 seconds at most, * and short payload (12 bits) has a block length of slightly over 30 seconds */ compare_bits<0> (cmp_states, relative_speed); compare_bits<1> (cmp_states, relative_speed); compare_bits<2> (cmp_states, relative_speed); Score best_score; for (const auto& cs : cmp_states) { double sync_quality = 0; int bit_count = 0; for (size_t bit = 0; bit < Params::sync_bits; bit++) { const auto& bv = cs.bit_values[bit]; sync_quality += SyncFinder::bit_quality (bv.umag, bv.dmag, bit) * bv.count; bit_count += bv.count; } if (bit_count) { sync_quality /= bit_count; sync_quality = fabs (SyncFinder::normalize_sync_quality (sync_quality)); if (sync_quality > best_score.quality) { best_score.quality = sync_quality; best_score.speed = relative_speed * center; } } } std::lock_guard lg (mutex); result_scores.push_back (best_score); } /* * The scores from speed search are usually a bit noisy, so the local maximum from the scores * vector is not necessarily the best choice. * * To get rid of the noise to some degree, this function smoothes the scores using a * cosine window and then finds the local maximum of this smooth function. */ static double score_smooth_find_best (const vector& in_scores, double step, double distance) { auto scores = in_scores; sort (scores.begin(), scores.end(), [] (auto s1, auto s2) { return s1.speed < s2.speed; }); double best_speed = 0; double best_quality = 0; for (double speed = scores.front().speed; speed < scores.back().speed; speed += 0.000001) { double quality_sum = 0; double quality_div = 0; for (auto s : scores) { double w = window_cos ((s.speed - speed) / (step * distance)); quality_sum += s.quality * w; quality_div += w; } quality_sum /= quality_div; if (quality_sum > best_quality) { best_speed = speed; best_quality = quality_sum; } } return best_speed; } class SpeedSearch { vector> speed_sync; const WavData& in_data; WavData clipped_in_data; double clip_location; SpeedSync * find_closest_speed_sync (double speed) { auto it = std::min_element (speed_sync.begin(), speed_sync.end(), [&](auto& x, auto& y) { return fabs (x->center_speed() - speed) < fabs (y->center_speed() - speed); }); return (*it).get(); } public: SpeedSearch (const WavData& in_data, double clip_location) : in_data (in_data), clip_location (clip_location) { } static void debug_range (const SpeedScanParams& scan_params) { auto bound = [&] (float f) { return 100 * pow (scan_params.step, f * (scan_params.n_center_steps * (scan_params.n_steps * 2 + 1) + scan_params.n_steps)); }; printf ("range = [ %.2f .. %.2f ]\n", bound (-1), bound (1)); } vector get_jobs (const Key& key, const SpeedScanParams& scan_params, const vector& speeds); vector get_results(); }; vector SpeedSearch::get_jobs (const Key& key, const SpeedScanParams& scan_params, const vector& speeds) { /* speed is between 0.8 and 1.25, so we use a clip seconds factor of 1.3 to provide enough samples */ clipped_in_data = get_speed_clip (clip_location, in_data, scan_params.seconds * 1.3); speed_sync.clear(); for (auto speed : speeds) { for (int c = -scan_params.n_center_steps; c <= scan_params.n_center_steps; c++) { double c_speed = speed * pow (scan_params.step, c * (scan_params.n_steps * 2 + 1)); speed_sync.push_back (std::make_unique (key, clipped_in_data, c_speed)); } } vector jobs; for (auto& s : speed_sync) jobs.push_back (s->get_jobs (scan_params, s->center_speed())); return jobs; } vector SpeedSearch::get_results() { vector scores; for (auto& s : speed_sync) { vector step_scores = s->get_scores(); scores.insert (scores.end(), step_scores.begin(), step_scores.end()); } return scores; } static void select_n_best_scores (vector& scores, size_t n) { sort (scores.begin(), scores.end(), [](auto a, auto b) { return a.speed < b.speed; }); auto get_quality = [&] (int pos) // handle corner cases { if (pos >= 0 && size_t (pos) < scores.size()) return scores[pos].quality; else return 0.0; }; vector lmax_scores; for (int x = 0; size_t (x) < scores.size(); x++) { /* check for peaks * - single peak : quality of the middle value is larger than the quality of the left and right neighbour * - double peak : two values have equal quality, this must be larger than left and right neighbour */ const double q1 = get_quality (x - 1); const double q2 = get_quality (x); const double q3 = get_quality (x + 1); if (q1 <= q2 && q2 >= q3) { lmax_scores.push_back (scores[x]); x++; // score with quality q3 cannot be a local maximum } } sort (lmax_scores.begin(), lmax_scores.end(), [](auto a, auto b) { return a.quality > b.quality; }); if (lmax_scores.size() > n) lmax_scores.resize (n); scores = lmax_scores; } static vector get_clip_locations (const Key& key, const WavData& in_data, int n) { Random rng (key, 0, Random::Stream::speed_clip); /* to improve performance, we don't hash all samples but just a few */ const vector& samples = in_data.samples(); vector xsamples; for (size_t p = 0; p < samples.size(); p += rng() % 1000) xsamples.push_back (samples[p]); rng.seed (Random::seed_from_hash (xsamples), Random::Stream::speed_clip); /* return a set of n possible clip locations */ vector result; for (int c = 0; c < n; c++) result.push_back (rng.random_double()); return result; } static double get_best_clip_location (const Key& key, const WavData& in_data, double seconds, int candidates) { double clip_location = 0; double best_energy = 0; /* try a few clip locations, use the one with highest signal energy */ for (auto location : get_clip_locations (key, in_data, candidates)) { WavData wd = get_speed_clip (location, in_data, seconds); double energy = 0; for (auto s : wd.samples()) energy += s * s; if (energy > best_energy) { best_energy = energy; clip_location = location; } } return clip_location; } /* Try to process blocks of jobs on our ThreadPool with high concurrency. Examples: * * number of jobs: split_jobs with threads == 32 * 1: 1 * 2: 2 * 3: 3 * [...] * 31: 31 * 32: 32 * 33: 17 16 * 34: 17 17 * 35: 18 17 * 36: 18 18 * [...] * 63: 32 31 * 64: 32 32 * 65: 32 17 16 * 66: 32 17 17 * [...] */ vector split_jobs (int jobs, int threads) { vector split_jobs; auto update_split_jobs = [&] (int j) { assert (j >= 0 && j <= jobs); if (j) { split_jobs.push_back (j); jobs -= j; } }; /* as long as the remaining number of jobs is very large, simply process one block using all threads */ while (jobs > 2 * threads) update_split_jobs (threads); /* remaining jobs in [threads, 2 * threads]: process half of the remaining jobs */ if (jobs > threads) update_split_jobs ((jobs + 1) / 2); /* process remaining jobs */ update_split_jobs (jobs); return split_jobs; } vector detect_speed (const vector& key_list, const WavData& in_data, bool print_results) { vector results; /* typically even for high strength we need at least a few seconds of audio * in in_data for successful speed detection, but our algorithm won't work at * all for very short input files */ double in_seconds = double (in_data.n_frames()) / in_data.sample_rate(); if (in_seconds < 0.25) return results; const SpeedScanParams scan1_normal /* first pass: find approximation: speed approximately 0.8..1.25 */ { .seconds = 25, .step = 1.0007, .n_steps = 5, .n_center_steps = 28, }; const SpeedScanParams scan1_patient { .seconds = 50, .step = 1.00035, .n_steps = 11, .n_center_steps = 28, }; const SpeedScanParams scan1 = Params::detect_speed_patient ? scan1_patient : scan1_normal; const SpeedScanParams scan2_normal /* second pass: improve approximation */ { .seconds = 50, .step = 1.00035, .n_steps = 1, }; const SpeedScanParams scan2_patient { .seconds = 50, .step = 1.000175, .n_steps = 1, }; const SpeedScanParams scan2 = Params::detect_speed_patient ? scan2_patient : scan2_normal; const SpeedScanParams scan3 /* third pass: fast refine (not always perfect) */ { .seconds = 50, .step = 1.00005, .n_steps = 40, }; const double scan3_smooth_distance = 20; const double speed_sync_threshold = 0.4; const int n_best = Params::detect_speed_patient ? 15 : 5; // SpeedSearch::debug_range (scan1); const int clip_candidates = 5; struct KeySpeedSearch { Key key; std::unique_ptr speed_search; vector scores; }; vector key_speed_search_vec; ThreadPool thread_pool; auto run_search = [&] (const SpeedScanParams& scan_params, auto get_speeds) { vector jobs; for (auto& key_speed_search : key_speed_search_vec) { auto more_jobs = key_speed_search.speed_search->get_jobs (key_speed_search.key, scan_params, get_speeds (key_speed_search)); jobs.insert (jobs.end(), more_jobs.begin(), more_jobs.end()); } size_t start = 0; for (size_t count : split_jobs (jobs.size(), thread_pool.n_threads())) { for (size_t i = 0; i < count; i++) thread_pool.add_job (jobs[start + i].prepare_job); thread_pool.wait_all(); for (size_t i = 0; i < count; i++) { for (auto job : jobs[start + i].search_jobs) thread_pool.add_job (job); } thread_pool.wait_all(); for (size_t i = 0; i < count; i++) jobs[start + i].free_memory(); start += count; } assert (start == jobs.size()); for (auto& key_speed_search : key_speed_search_vec) key_speed_search.scores = key_speed_search.speed_search->get_results(); }; /* initial search using grid */ for (auto& key : key_list) { const double clip_location = get_best_clip_location (key, in_data, scan1.seconds, clip_candidates); key_speed_search_vec.push_back ({key, std::make_unique (in_data, clip_location), {}}); } run_search (scan1, [] (auto& key_speed_search) -> vector { return { 1.0 }; }); /* improve N best matches */ run_search (scan2, [n_best] (auto& key_speed_search) -> vector { select_n_best_scores (key_speed_search.scores, n_best); vector speeds; for (auto score : key_speed_search.scores) speeds.push_back (score.speed); return speeds; }); /* improve best match */ for (auto& key_speed_search : key_speed_search_vec) select_n_best_scores (key_speed_search.scores, 1); run_search (scan3, [] (auto& key_speed_search) -> vector { return { key_speed_search.scores[0].speed }; }); for (auto& key_speed_search : key_speed_search_vec) { double best_speed = score_smooth_find_best (key_speed_search.scores, 1 - scan3.step, scan3_smooth_distance); double best_quality = 0; for (auto score : key_speed_search.scores) best_quality = max (best_quality, score.quality); if (print_results) { double delta = -1; if (Params::test_speed > 0) delta = 100 * fabs (best_speed - Params::test_speed) / Params::test_speed; printf ("detect_speed %f %f %.4f\n", best_speed, best_quality, delta); } if (best_quality > speed_sync_threshold) { // speeds closer to 1.0 than this usually work without stretching before decode if (best_speed < 0.9999 || best_speed > 1.0001) results.push_back ({ key_speed_search.key, best_speed }); } } return results; } audiowmark-0.6.5/src/wmspeed.hh000066400000000000000000000020101501136502400164270ustar00rootroot00000000000000/* * Copyright (C) 2018-2020 Stefan Westerfeld * * 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 . */ #ifndef AUDIOWMARK_WM_SPEED_HH #define AUDIOWMARK_WM_SPEED_HH #include "wavdata.hh" #include "random.hh" struct DetectSpeedResult { Key key; double speed = 0; }; std::vector detect_speed (const std::vector& key_list, const WavData& in_data, bool print_results); #endif /* AUDIOWMARK_WM_SPEED_HH */ audiowmark-0.6.5/tests/000077500000000000000000000000001501136502400150245ustar00rootroot00000000000000audiowmark-0.6.5/tests/.gitignore000066400000000000000000000000171501136502400170120ustar00rootroot00000000000000test-common.sh audiowmark-0.6.5/tests/Makefile.am000066400000000000000000000024501501136502400170610ustar00rootroot00000000000000CHECKS = detect-speed-test block-decoder-test clip-decoder-test \ pipe-test short-payload-test sync-test sample-rate-test \ key-test wav-pipe-test wav-subformat-test test-programs if COND_WITH_FFMPEG CHECKS += hls-test raw-format-test endif EXTRA_DIST = detect-speed-test.sh block-decoder-test.sh clip-decoder-test.sh \ pipe-test.sh short-payload-test.sh sync-test.sh sample-rate-test.sh \ key-test.sh hls-test.sh wav-pipe-test.sh wav-subformat-test.sh test-programs.sh \ raw-format-test.sh check: $(CHECKS) detect-speed-test: Q=1 $(top_srcdir)/tests/detect-speed-test.sh block-decoder-test: Q=1 $(top_srcdir)/tests/block-decoder-test.sh clip-decoder-test: Q=1 $(top_srcdir)/tests/clip-decoder-test.sh pipe-test: Q=1 $(top_srcdir)/tests/pipe-test.sh wav-pipe-test: Q=1 $(top_srcdir)/tests/wav-pipe-test.sh wav-subformat-test: Q=1 $(top_srcdir)/tests/wav-subformat-test.sh short-payload-test: Q=1 $(top_srcdir)/tests/short-payload-test.sh sync-test: Q=1 $(top_srcdir)/tests/sync-test.sh sample-rate-test: Q=1 $(top_srcdir)/tests/sample-rate-test.sh key-test: Q=1 $(top_srcdir)/tests/key-test.sh hls-test: Q=1 $(top_srcdir)/tests/hls-test.sh raw-format-test: Q=1 $(top_srcdir)/tests/raw-format-test.sh test-programs: Q=1 $(top_srcdir)/tests/test-programs.sh audiowmark-0.6.5/tests/block-decoder-test.sh000077500000000000000000000007351501136502400210420ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=block-decoder-test.wav OUT_WAV=block-decoder-test-out.wav audiowmark test-gen-noise $IN_WAV 200 44100 audiowmark_add $IN_WAV $OUT_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG check_length $IN_WAV $OUT_WAV audiowmark_add --test-no-limiter $IN_WAV $OUT_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG check_length $IN_WAV $OUT_WAV check_snr $IN_WAV $OUT_WAV 32.4 rm $IN_WAV $OUT_WAV exit 0 audiowmark-0.6.5/tests/clip-decoder-test.sh000077500000000000000000000006661501136502400207020ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=clip-decoder-test.wav OUT_WAV=clip-decoder-test-out.wav CUT_WAV=clip-decoder-test-out-cut.wav audiowmark test-gen-noise $IN_WAV 30 44100 audiowmark_add $IN_WAV $OUT_WAV $TEST_MSG audiowmark_cmp --expect-matches 1 $OUT_WAV $TEST_MSG # cut 1 second 300 samples audiowmark cut-start $OUT_WAV $CUT_WAV 44300 audiowmark_cmp --expect-matches 1 $CUT_WAV $TEST_MSG rm $IN_WAV $OUT_WAV $CUT_WAV exit 0 audiowmark-0.6.5/tests/detect-speed-test.sh000077500000000000000000000010141501136502400207020ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=detect-speed-test.wav OUT_WAV=detect-speed-test-out.wav OUTS_WAV=detect-speed-test-out-spd.wav audiowmark test-gen-noise detect-speed-test.wav 30 44100 for SPEED in 0.9764 1.0 1.01 do audiowmark_add $IN_WAV $OUT_WAV $TEST_MSG audiowmark test-change-speed $OUT_WAV $OUTS_WAV $SPEED audiowmark_cmp $OUTS_WAV $TEST_MSG --detect-speed --test-speed $SPEED audiowmark_cmp $OUTS_WAV $TEST_MSG --detect-speed-patient --test-speed $SPEED done rm $IN_WAV $OUT_WAV $OUTS_WAV exit 0 audiowmark-0.6.5/tests/hls-test.sh000077500000000000000000000021571501136502400171330ustar00rootroot00000000000000#!/bin/bash source test-common.sh if [ "x$Q" == "x1" ] && [ -z "$V" ]; then FFMPEG_Q="-v quiet" fi HLS_DIR=hls-test-dir.$$ mkdir -p $HLS_DIR # generate input sample audiowmark test-gen-noise $HLS_DIR/test-input.wav 200 44100 # convert to hls ffmpeg $FFMPEG_Q -nostdin -i $HLS_DIR/test-input.wav \ -f hls \ -c:a:0 aac -ab 192k \ -master_pl_name replay.m3u8 \ -hls_list_size 0 -hls_time 10 $HLS_DIR/as%v/out.m3u8 # prepare hls segments for watermarking audiowmark hls-prepare $HLS_DIR/as0 $HLS_DIR/as0prep out.m3u8 $HLS_DIR/test-input.wav # watermark hls segments individually mkdir -p $HLS_DIR/as0m for i in $(cd $HLS_DIR/as0; ls out*.ts) do audiowmark hls-add $HLS_DIR/as0prep/$i $HLS_DIR/as0m/$i $TEST_MSG done cp $HLS_DIR/as0/out.m3u8 $HLS_DIR/as0m/out.m3u8 # convert watermarked hls back to wav ffmpeg $FFMPEG_Q -nostdin -y -i $HLS_DIR/as0m/out.m3u8 $HLS_DIR/test-output.wav # detect watermark from wav audiowmark_cmp --expect-matches 5 $HLS_DIR/test-output.wav $TEST_MSG rm $HLS_DIR/as0*/*.ts rm $HLS_DIR/as0*/out.m3u8 rmdir $HLS_DIR/as0* rm $HLS_DIR/test-*.wav rm $HLS_DIR/replay.m3u8 rmdir $HLS_DIR exit 0 audiowmark-0.6.5/tests/key-test.sh000077500000000000000000000022401501136502400171260ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=key-test.wav KEY1=key-test-1.key KEY2=key-test-2.key OUT1_WAV=key-test-out1.wav OUT2_WAV=key-test-out2.wav TEST_MSG2=0123456789abcdef0123456789abcdef audiowmark test-gen-noise $IN_WAV 30 44100 audiowmark gen-key $KEY1 audiowmark gen-key $KEY2 audiowmark_add --key $KEY1 $IN_WAV $OUT1_WAV $TEST_MSG audiowmark_add --key $KEY2 $IN_WAV $OUT2_WAV $TEST_MSG2 # shouldn't be able to detect watermark without correct key audiowmark_cmp --key $KEY1 --expect-matches 1 $OUT1_WAV $TEST_MSG audiowmark_cmp --key $KEY2 --expect-matches 0 $OUT1_WAV $TEST_MSG audiowmark_cmp --expect-matches 0 $OUT1_WAV $TEST_MSG audiowmark_cmp --key $KEY2 --expect-matches 1 $OUT2_WAV $TEST_MSG2 audiowmark_cmp --key $KEY1 --expect-matches 0 $OUT2_WAV $TEST_MSG2 audiowmark_cmp --expect-matches 0 $OUT2_WAV $TEST_MSG2 rm $OUT1_WAV $OUT2_WAV # double watermark with two different keys audiowmark_add $IN_WAV $OUT1_WAV $TEST_MSG audiowmark_add --test-key 42 $OUT1_WAV $OUT2_WAV $TEST_MSG2 audiowmark_cmp --expect-matches 1 $OUT2_WAV $TEST_MSG audiowmark_cmp --test-key 42 --expect-matches 1 $OUT2_WAV $TEST_MSG2 rm $IN_WAV $KEY1 $KEY2 $OUT1_WAV $OUT2_WAV exit 0 audiowmark-0.6.5/tests/pipe-test.sh000077500000000000000000000006651501136502400173040ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=pipe-test.wav OUT_WAV=pipe-test-out.wav audiowmark test-gen-noise $IN_WAV 200 44100 cat $IN_WAV | audiowmark_add - - $TEST_MSG > $OUT_WAV || die "watermark from pipe failed" audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG cat $OUT_WAV | audiowmark_cmp --expect-matches 5 - $TEST_MSG || die "watermark detection from pipe failed" check_length $IN_WAV $OUT_WAV rm $IN_WAV $OUT_WAV exit 0 audiowmark-0.6.5/tests/raw-format-test.sh000077500000000000000000000033351501136502400204230ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=test-raw-format.wav OUT_WAV=test-raw-format-out.wav OUT2_WAV=test-raw-format-out2.wav raw_test() { FFMPEG_FMT="$1" shift AWM_FMT="$@" rm -f $IN_WAV $OUT_WAV $OUT2_WAV audiowmark test-gen-noise --bits 32 $IN_WAV 200 44100 ffmpeg -v quiet -nostdin -i $IN_WAV -f $FFMPEG_FMT -c:a pcm_$FFMPEG_FMT - | \ audiowmark_add - - $TEST_MSG --format raw --raw-rate 44100 $AWM_FMT --test-no-limiter | \ ffmpeg -v quiet -f $FFMPEG_FMT -ar 44100 -ac 2 -i - $OUT_WAV audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG check_snr $IN_WAV $OUT_WAV $SNR ffmpeg -v quiet -nostdin -i $IN_WAV -f $FFMPEG_FMT -c:a pcm_$FFMPEG_FMT - | \ audiowmark_add - $OUT2_WAV $TEST_MSG --input-format raw --raw-rate 44100 $AWM_FMT --test-no-limiter check_length $IN_WAV $OUT_WAV check_length $IN_WAV $OUT2_WAV rm -f $IN_WAV $OUT_WAV $OUT2_WAV } ## 8 bit SNR=31 raw_test s8 --raw-bits 8 raw_test u8 --raw-bits 8 --raw-encoding unsigned ## little endian SNR=32.4 raw_test s16le raw_test s24le --raw-bits 24 raw_test s32le --raw-bits 32 raw_test u16le --raw-encoding unsigned raw_test u24le --raw-bits 24 --raw-encoding unsigned raw_test u32le --raw-bits 32 --raw-encoding unsigned raw_test f32le --raw-encoding float raw_test f64le --raw-encoding double ## big endian raw_test s16be --raw-endian big raw_test s24be --raw-bits 24 --raw-endian big raw_test s32be --raw-bits 32 --raw-endian big raw_test u16be --raw-encoding unsigned --raw-endian big raw_test u24be --raw-bits 24 --raw-encoding unsigned --raw-endian big raw_test u32be --raw-bits 32 --raw-encoding unsigned --raw-endian big raw_test f32be --raw-encoding float --raw-endian big raw_test f64be --raw-encoding double --raw-endian big audiowmark-0.6.5/tests/sample-rate-test.sh000077500000000000000000000006671501136502400205630ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=sample-rate-test.wav OUT_WAV=sample-rate-test-out.wav OUT_48000_WAV=sample-rate-test-out-48000.wav audiowmark test-gen-noise $IN_WAV 200 32000 audiowmark_add $IN_WAV $OUT_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 $OUT_WAV $TEST_MSG audiowmark test-resample $OUT_WAV $OUT_48000_WAV 48000 audiowmark_cmp --expect-matches 5 $OUT_48000_WAV $TEST_MSG rm $IN_WAV $OUT_WAV $OUT_48000_WAV exit 0 audiowmark-0.6.5/tests/short-payload-test.sh000077500000000000000000000006701501136502400211310ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=short-playload-test.wav OUT_WAV=short-playload-test-out.wav audiowmark test-gen-noise $IN_WAV 200 44100 audiowmark_add --short 12 $IN_WAV $OUT_WAV abc audiowmark_cmp --short 12 $OUT_WAV abc audiowmark_add --short 16 $IN_WAV $OUT_WAV abcd audiowmark_cmp --short 16 $OUT_WAV abcd audiowmark_add --short 20 $IN_WAV $OUT_WAV abcde audiowmark_cmp --short 20 $OUT_WAV abcde rm $IN_WAV $OUT_WAV exit 0 audiowmark-0.6.5/tests/sync-test.sh000077500000000000000000000005551501136502400173210ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=sync-test.wav OUT_WAV=sync-test-out.wav CUT_WAV=sync-test-cut.wav audiowmark test-gen-noise $IN_WAV 200 44100 audiowmark_add $IN_WAV $OUT_WAV $TEST_MSG # cut 20 seconds and 300 samples audiowmark cut-start $OUT_WAV $CUT_WAV 882300 audiowmark_cmp --expect-matches 3 $CUT_WAV $TEST_MSG rm $IN_WAV $OUT_WAV $CUT_WAV exit 0 audiowmark-0.6.5/tests/test-common.sh.in000066400000000000000000000030731501136502400202350ustar00rootroot00000000000000# program locations AUDIOWMARK=@top_builddir@/src/audiowmark TEST_MSG=f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0 TOP_BUILDDIR=@top_builddir@ ## abort on error set -eo pipefail # common shell functions die() { echo >&2 "$0: $@" exit 1 } audiowmark() { if [ "x$Q" == "x1" ] && [ -z "$V" ]; then AUDIOWMARK_Q="-q" else echo >&2 ==== audiowmark "$@" ==== fi $AUDIOWMARK $AUDIOWMARK_Q --strict "$@" || die "failed to run audiowmark $@" } audiowmark_add() { if [ "x$Q" == "x1" ] && [ -z "$V" ]; then AUDIOWMARK_Q="-q" else echo >&2 ==== audiowmark add "$@" ==== fi $AUDIOWMARK $AUDIOWMARK_Q --strict add "$@" || die "failed to watermark $@" } audiowmark_cmp() { if [ "x$Q" == "x1" ] && [ -z "$V" ]; then AUDIOWMARK_OUT="/dev/null" else AUDIOWMARK_OUT="/dev/stdout" echo >&2 ==== audiowmark cmp "$@" ==== fi $AUDIOWMARK --strict cmp "$@" > $AUDIOWMARK_OUT || die "failed to detect watermark $@" } check_length() { local in1="$($AUDIOWMARK test-info $1 frames)" local in2="$($AUDIOWMARK test-info $2 frames)" [ "x$in1" != "x" ] || die "length of '$1' could not be detected" [ "x$in1" == "x$in2" ] || die "length of '$1' ($in1) and '$2' ($in2) differs" } check_snr() { local snr="$($AUDIOWMARK test-snr $1 $2)" if [ "x$Q" == "x1" ] && [ -z "$V" ]; then : else echo >&2 "==== snr of $1 and $2 is $snr (expected $3) ====" fi [ "x$snr" != "x" ] || die "snr of '$1' and '$2' could not be detected" [ "x$3" != "x" ] || die "need snr bound" awk "BEGIN {exit !($snr >= $3)}" || die "snr of '$1' and '$2' is worse than $3" } audiowmark-0.6.5/tests/test-programs.sh000077500000000000000000000003151501136502400201710ustar00rootroot00000000000000#!/bin/bash source test-common.sh for TEST in testrawconverter do if [ "x$Q" == "x1" ] && [ -z "$V" ]; then $TOP_BUILDDIR/src/$TEST > /dev/null else $TOP_BUILDDIR/src/$TEST fi done exit 0 audiowmark-0.6.5/tests/wav-pipe-test.sh000077500000000000000000000030211501136502400200640ustar00rootroot00000000000000#!/bin/bash source test-common.sh IN_WAV=wav-pipe-test.wav OUT1_WAV=wav-pipe-test-out1.wav OUT2_WAV=wav-pipe-test-out2.wav OUT3_WAV=wav-pipe-test-out3.wav for BITS in 16 24 32 do audiowmark test-gen-noise --bits $BITS $IN_WAV 200 44100 [ "x$BITS" == "x$(audiowmark test-info $IN_WAV bit_depth)" ] || die "generated input bit depth is not correct" cat $IN_WAV | audiowmark_add --test-key 1 --test-no-limiter --format wav-pipe - - $TEST_MSG > $OUT1_WAV || die "watermark from pipe failed" cat $OUT1_WAV | audiowmark_add --test-key 2 --test-no-limiter --format wav-pipe - - $TEST_MSG > $OUT2_WAV || die "watermark from pipe failed" cat $OUT2_WAV | audiowmark_add --test-key 3 --test-no-limiter --format wav-pipe - - $TEST_MSG > $OUT3_WAV || die "watermark from pipe failed" check_length $IN_WAV $OUT1_WAV check_length $IN_WAV $OUT2_WAV check_length $IN_WAV $OUT3_WAV check_snr $IN_WAV $OUT1_WAV 32 check_snr $IN_WAV $OUT2_WAV 29 check_snr $IN_WAV $OUT3_WAV 27 audiowmark_cmp --expect-matches 0 $OUT3_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 --test-key 1 $OUT3_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 --test-key 2 $OUT3_WAV $TEST_MSG audiowmark_cmp --expect-matches 5 --test-key 3 $OUT3_WAV $TEST_MSG # for wav-pipe format: 16 / 24 / 32 bit input should produce the same number of output bits BTEST=$BITS:$(audiowmark test-info $OUT3_WAV bit_depth) [[ "$BTEST" =~ ^(16:16|24:24|32:32)$ ]] || die "unexpected input/output bit depth $BTEST" rm $IN_WAV $OUT1_WAV $OUT2_WAV $OUT3_WAV done exit 0 audiowmark-0.6.5/tests/wav-subformat-test.sh000077500000000000000000000032151501136502400211360ustar00rootroot00000000000000#!/bin/bash source test-common.sh TESTWAVFORMAT=$TOP_BUILDDIR/src/testwavformat testwavformat() { if [ "x$Q" == "x1" ] && [ -z "$V" ]; then : # silent else echo >&2 ==== testwavformat "$@" ==== fi $TESTWAVFORMAT "$@" || die "failed to run testwavformat $@" } compare_fmt_snr() { INFMT=$(testwavformat detect $1) OUTFMT=$(testwavformat detect $2) EXPECTFMT=$(echo $INFMT | sed s/pcm_8/pcm_16/g) if [ "x$Q" == "x1" ] && [ -z "$V" ]; then : # silent else echo >&2 ==== infmt $INFMT outfmt $OUTFMT expectfmt $EXPECTFMT ==== fi if [ "x$EXPECTFMT" != "x$OUTFMT" ]; then die "format mismatch $EXPECTFMT $OUTFMT" exit 1 fi check_snr $1 $2 $3 } IN_WAV=wav-subformat-in.wav FMT_WAV=wav-subformat.wav MARK_WAV=wav-subformat-mark.wav audiowmark test-gen-noise --bits 32 $IN_WAV 200 44100 for FMT in $(testwavformat list) do testwavformat convert $IN_WAV $FMT_WAV $FMT # FILE -> FILE audiowmark_add $FMT_WAV $MARK_WAV $TEST_MSG --test-no-limiter compare_fmt_snr $FMT_WAV $MARK_WAV 32.3 # STDIN -> FILE cat $FMT_WAV | audiowmark_add - $MARK_WAV $TEST_MSG --test-no-limiter compare_fmt_snr $FMT_WAV $MARK_WAV 32.3 # FILE -> STDOUT audiowmark_add $FMT_WAV - $TEST_MSG --test-no-limiter > $MARK_WAV compare_fmt_snr $FMT_WAV $MARK_WAV 32.3 # STDIN (WavPipe) -> FILE cat $FMT_WAV | audiowmark_add - $MARK_WAV $TEST_MSG --test-no-limiter --format wav-pipe compare_fmt_snr $FMT_WAV $MARK_WAV 32.3 # STDIN (WavPipe) -> STDOUT (WavPipe) cat $FMT_WAV | audiowmark_add - - $TEST_MSG --test-no-limiter --format wav-pipe > $MARK_WAV compare_fmt_snr $FMT_WAV $MARK_WAV 32.3 done rm $IN_WAV $FMT_WAV $MARK_WAV