diff --git a/libraries/ESP32-audioI2S/.github/workflows/stale.yml b/libraries/ESP32-audioI2S/.github/workflows/stale.yml
new file mode 100644
index 0000000..923368f
--- /dev/null
+++ b/libraries/ESP32-audioI2S/.github/workflows/stale.yml
@@ -0,0 +1,24 @@
+name: Close inactive issues
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v9
+ with:
+ days-before-issue-stale: 30
+ days-before-issue-close: 14
+ stale-issue-label: "stale"
+ stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
+ close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
+ days-before-pr-stale: 45
+ days-before-pr-close: 10
+ stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
+ close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
+
diff --git a/libraries/ESP32-audioI2S/CMakeLists.txt b/libraries/ESP32-audioI2S/CMakeLists.txt
new file mode 100644
index 0000000..2ac4dd4
--- /dev/null
+++ b/libraries/ESP32-audioI2S/CMakeLists.txt
@@ -0,0 +1,9 @@
+
+get_filename_component(dir ${CMAKE_CURRENT_LIST_FILE} PATH)
+FILE(GLOB_RECURSE app_sources ${dir}/src/*.cpp)
+
+idf_component_register(SRCS ${app_sources}
+ REQUIRES "ESP32-audioI2S"
+ INCLUDE_DIRS "src"
+ REQUIRES wear_levelling Arduino
+)
diff --git a/libraries/ESP32-audioI2S/LICENSE b/libraries/ESP32-audioI2S/LICENSE
new file mode 100644
index 0000000..f288702
--- /dev/null
+++ b/libraries/ESP32-audioI2S/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/libraries/ESP32-audioI2S/README.md b/libraries/ESP32-audioI2S/README.md
new file mode 100644
index 0000000..9595ee4
--- /dev/null
+++ b/libraries/ESP32-audioI2S/README.md
@@ -0,0 +1,97 @@
+# ESP32-audioI2S
+
+:warning: **This library only works on multi-core chips like ESP32, ESP32-S3 and ESP32-P4. Your board must have PSRAM! It does not work on the ESP32-S2, ESP32-C3 etc** :warning:
+
+Plays mp3, m4a and wav files from SD card via I2S with external hardware.
+HELIX-mp3 and faad2-aac decoder is included. There is also an OPUS decoder for Fullband, an VORBIS decoder and a FLAC decoder.
+Works with MAX98357A (3 Watt amplifier with DAC), connected three lines (DOUT, BLCK, LRC) to I2S. The I2S output frequency is always 48kHz, regardless of the input source, so Bluetooth devices can also be connected without any problems.
+For stereo are two MAX98357A necessary. AudioI2S works with UDA1334A (Adafruit I2S Stereo Decoder Breakout Board), PCM5102A and CS4344.
+Other HW may work but not tested. Plays also icy-streams, GoogleTTS and OpenAIspeech. Can be compiled with Arduino IDE. [WIKI](https://github.com/schreibfaul1/ESP32-audioI2S/wiki)
+
+```` c++
+#include "Arduino.h"
+#include "WiFi.h"
+#include "Audio.h"
+
+// Digital I/O used
+#define I2S_DOUT 25
+#define I2S_BCLK 27
+#define I2S_LRC 26
+
+String ssid = "*******";
+String password = "*******";
+
+Audio audio;
+
+// callbacks
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info; // optional
+ Serial.begin(115200);
+ WiFi.begin(ssid.c_str(), password.c_str());
+ while (WiFi.status() != WL_CONNECTED) delay(1500);
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(21); // default 0...21
+ audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/");
+}
+
+void loop(){
+ audio.loop();
+ vTaskDelay(1);
+}
+
+````
+You can find more examples here: https://github.com/schreibfaul1/ESP32-audioI2S/tree/master/examples
+
+````c++
+// detailed cb output
+void my_audio_info(Audio::msg_t m) {
+ switch(m.e){
+ case Audio::evt_info: Serial.printf("info: ....... %s\n", m.msg); break;
+ case Audio::evt_eof: Serial.printf("end of file: %s\n", m.msg); break;
+ case Audio::evt_bitrate: Serial.printf("bitrate: .... %s\n", m.msg); break; // icy-bitrate or bitrate from metadata
+ case Audio::evt_icyurl: Serial.printf("icy URL: .... %s\n", m.msg); break;
+ case Audio::evt_id3data: Serial.printf("ID3 data: ... %s\n", m.msg); break; // id3-data or metadata
+ case Audio::evt_lasthost: Serial.printf("last URL: ... %s\n", m.msg); break;
+ case Audio::evt_name: Serial.printf("station name: %s\n", m.msg); break; // station name or icy-name
+ case Audio::evt_streamtitle: Serial.printf("stream title: %s\n", m.msg); break;
+ case Audio::evt_icylogo: Serial.printf("icy logo: ... %s\n", m.msg); break;
+ case Audio::evt_icydescription: Serial.printf("icy descr: .. %s\n", m.msg); break;
+ case Audio::evt_image: for(int i = 0; i < m.vec.size(); i += 2){
+ Serial.printf("cover image: segment %02i, pos %07lu, len %05lu\n", i / 2, m.vec[i], m.vec[i + 1]);} break; // APIC
+ case Audio::evt_lyrics: Serial.printf("sync lyrics: %s\n", m.msg); break;
+ case Audio::evt_log : Serial.printf("audio_logs: %s\n", m.msg); break;
+ default: Serial.printf("message:..... %s\n", m.msg); break;
+ }
+}
+````
+
+
+|Codec | ESP32 |ESP32-S3 or ESP32-P4 | |
+|------------|-------------|-----------------------------|--------------------------|
+| mp3 | y | y | |
+| aac | y | y | |
+| aacp | y (mono) | y (+SBR, +Parametric Stereo)| |
+| wav | y | y | |
+| flac | y | y |blocksize max 24576 bytes |
+| vorbis | y | y | <=196Kbit/s |
+| m4a | y | y | |
+| opus | y | y | |
+
+
+
+***
+Wiring
+
+
+***
+Impulse diagram
+
+***
+Yellobyte has developed an all-in-one board. It includes an ESP32-S3 N8R2, 2x MAX98357 and an SD card adapter.
+Documentation, circuit diagrams and examples can be found here: https://github.com/yellobyte/ESP32-DevBoards-Getting-Started
+
+
diff --git a/libraries/ESP32-audioI2S/additional_info/Arduino Library.png b/libraries/ESP32-audioI2S/additional_info/Arduino Library.png
new file mode 100644
index 0000000..d8e13c7
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Arduino Library.png differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Audio Duration.png b/libraries/ESP32-audioI2S/additional_info/Audio Duration.png
new file mode 100644
index 0000000..83d829a
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Audio Duration.png differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Breadboard.jpg b/libraries/ESP32-audioI2S/additional_info/Breadboard.jpg
new file mode 100644
index 0000000..a5bbe2a
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Breadboard.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/DAC CS4344.jpg b/libraries/ESP32-audioI2S/additional_info/DAC CS4344.jpg
new file mode 100644
index 0000000..e7eb418
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/DAC CS4344.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/DAC PCM5102A.jpg b/libraries/ESP32-audioI2S/additional_info/DAC PCM5102A.jpg
new file mode 100644
index 0000000..72dd2eb
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/DAC PCM5102A.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_MAX98357A .JPG b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_MAX98357A .JPG
new file mode 100644
index 0000000..3721ca8
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_MAX98357A .JPG differ
diff --git a/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_PCM5102A.JPG b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_PCM5102A.JPG
new file mode 100644
index 0000000..5d64a31
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_PCM5102A.JPG differ
diff --git a/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_PCM5102A_ONLY.JPG b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_PCM5102A_ONLY.JPG
new file mode 100644
index 0000000..f0c5dcd
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_PCM5102A_ONLY.JPG differ
diff --git a/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_UDA1334A.JPG b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_UDA1334A.JPG
new file mode 100644
index 0000000..2769427
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/ESP32_I2S_UDA1334A.JPG differ
diff --git a/libraries/ESP32-audioI2S/additional_info/FLAC Metadata Picture Block.jpg b/libraries/ESP32-audioI2S/additional_info/FLAC Metadata Picture Block.jpg
new file mode 100644
index 0000000..57a393f
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/FLAC Metadata Picture Block.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/ID3 Attatched Picture Frame.jpg b/libraries/ESP32-audioI2S/additional_info/ID3 Attatched Picture Frame.jpg
new file mode 100644
index 0000000..87d63f5
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/ID3 Attatched Picture Frame.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Impulsdiagramm.jpg b/libraries/ESP32-audioI2S/additional_info/Impulsdiagramm.jpg
new file mode 100644
index 0000000..447d88d
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Impulsdiagramm.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/M4A Coverart Block.jpg b/libraries/ESP32-audioI2S/additional_info/M4A Coverart Block.jpg
new file mode 100644
index 0000000..b1fa16e
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/M4A Coverart Block.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Ogg_Metadatapicture.png b/libraries/ESP32-audioI2S/additional_info/Ogg_Metadatapicture.png
new file mode 100644
index 0000000..f88b064
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Ogg_Metadatapicture.png differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Partition Scheme.png b/libraries/ESP32-audioI2S/additional_info/Partition Scheme.png
new file mode 100644
index 0000000..7267a3e
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Partition Scheme.png differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/Collide.ogg b/libraries/ESP32-audioI2S/additional_info/Testfiles/Collide.ogg
new file mode 100644
index 0000000..f829117
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/Collide.ogg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/Miss-Marple.m4a b/libraries/ESP32-audioI2S/additional_info/Testfiles/Miss-Marple.m4a
new file mode 100644
index 0000000..90773d7
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/Miss-Marple.m4a differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/Olsen-Banden.mp3 b/libraries/ESP32-audioI2S/additional_info/Testfiles/Olsen-Banden.mp3
new file mode 100644
index 0000000..e25afd3
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/Olsen-Banden.mp3 differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/Pink-Panther.wav b/libraries/ESP32-audioI2S/additional_info/Testfiles/Pink-Panther.wav
new file mode 100644
index 0000000..b603144
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/Pink-Panther.wav differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/Santiano-Wellerman.flac b/libraries/ESP32-audioI2S/additional_info/Testfiles/Santiano-Wellerman.flac
new file mode 100644
index 0000000..53df53f
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/Santiano-Wellerman.flac differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/myPlaylist.m3u b/libraries/ESP32-audioI2S/additional_info/Testfiles/myPlaylist.m3u
new file mode 100644
index 0000000..023c12f
--- /dev/null
+++ b/libraries/ESP32-audioI2S/additional_info/Testfiles/myPlaylist.m3u
@@ -0,0 +1,5 @@
+#EXTM3U
+#EXTINF:18,Bjarne Liller - Olsen Banden (Titelmusik der Olsenbande) - Olsen-Banden.mp3
+https://raw.githubusercontent.com/schreibfaul1/ESP32-audioI2S/master/additional_info/Testfiles/Olsen-Banden.mp3
+#EXTINF:10,Santiano-Wellermann - Santiano-Wellerman.flac
+https://raw.githubusercontent.com/schreibfaul1/ESP32-audioI2S/master/additional_info/Testfiles/Santiano-Wellerman.flac
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/sample.opus b/libraries/ESP32-audioI2S/additional_info/Testfiles/sample.opus
new file mode 100644
index 0000000..3eea946
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/sample.opus differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/seconds-CBR.mp3 b/libraries/ESP32-audioI2S/additional_info/Testfiles/seconds-CBR.mp3
new file mode 100644
index 0000000..c5ac91d
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/seconds-CBR.mp3 differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/seconds-VBR.mp3 b/libraries/ESP32-audioI2S/additional_info/Testfiles/seconds-VBR.mp3
new file mode 100644
index 0000000..1abc713
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/seconds-VBR.mp3 differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/test_16bit_mono.wav b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_16bit_mono.wav
new file mode 100644
index 0000000..08abecf
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_16bit_mono.wav differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/test_16bit_stereo.wav b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_16bit_stereo.wav
new file mode 100644
index 0000000..9c2d693
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_16bit_stereo.wav differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/test_8bit_mono.wav b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_8bit_mono.wav
new file mode 100644
index 0000000..44d0ce3
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_8bit_mono.wav differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Testfiles/test_8bit_stereo.wav b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_8bit_stereo.wav
new file mode 100644
index 0000000..e94b092
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Testfiles/test_8bit_stereo.wav differ
diff --git a/libraries/ESP32-audioI2S/additional_info/Volume_Settings_Dynamics.jpg b/libraries/ESP32-audioI2S/additional_info/Volume_Settings_Dynamics.jpg
new file mode 100644
index 0000000..225278c
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/Volume_Settings_Dynamics.jpg differ
diff --git a/libraries/ESP32-audioI2S/additional_info/lowpass.png b/libraries/ESP32-audioI2S/additional_info/lowpass.png
new file mode 100644
index 0000000..e6b6774
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/lowpass.png differ
diff --git a/libraries/ESP32-audioI2S/additional_info/minimal.JPG b/libraries/ESP32-audioI2S/additional_info/minimal.JPG
new file mode 100644
index 0000000..935dc8a
Binary files /dev/null and b/libraries/ESP32-audioI2S/additional_info/minimal.JPG differ
diff --git a/libraries/ESP32-audioI2S/additional_info/old/Helix AAC Decoder/aac_decoder.cpp_ b/libraries/ESP32-audioI2S/additional_info/old/Helix AAC Decoder/aac_decoder.cpp_
new file mode 100644
index 0000000..59af973
--- /dev/null
+++ b/libraries/ESP32-audioI2S/additional_info/old/Helix AAC Decoder/aac_decoder.cpp_
@@ -0,0 +1,10261 @@
+/*
+ * aac_decoder.cpp
+ * libhelix_HAACDECODER
+ *
+ * Created on: 26.10.2018
+ * Updated on: 22.05.2024
+ ************************************************************************************/
+
+#include "aac_decoder.h"
+
+const uint32_t SQRTHALF = 0x5a82799a; /* sqrt(0.5), format = Q31 */
+const uint32_t Q28_2 = 0x20000000; /* Q28: 2.0 */
+const uint32_t Q28_15 = 0x30000000; /* Q28: 1.5 */
+const uint8_t NUM_ITER_IRN = 5;
+const uint8_t NUM_TERMS_RPI = 5;
+const uint32_t LOG2_EXP_INV = 0x58b90bfc; /* 1/log2(e), Q31 */
+const uint8_t SF_OFFSET = 100;
+const uint8_t AAC_PROFILE_LC = 1;
+const uint8_t NUM_TIME_SLOTS = 16;
+const uint8_t SAMPLES_PER_SLOT = 2; /* RATE in spec */
+const uint8_t SYNCWORDH = 0xff; /* 12-bit syncword */
+const uint8_t SYNCWORDL = 0xf0;
+const uint8_t NUM_SAMPLE_RATES = 12;
+const uint8_t NUM_DEF_CHAN_MAPS = 8;
+const uint32_t NSAMPS_LONG = 1024;
+const uint8_t NSAMPS_SHORT = 128;
+const uint8_t NUM_SYN_ID_BITS = 3;
+const uint8_t NUM_INST_TAG_BITS = 4;
+const uint8_t NWINDOWS_LONG = 1;
+const uint8_t NWINDOWS_SHORT = 8;
+const uint8_t AAC_MAX_NCHANS = 2; /* set to default max number of channels */
+const uint16_t AAC_MAX_NSAMPS = 1024;
+const uint8_t MAX_NCHANS_ELEM = 2; /* max number of channels in any single bitstream element */
+const uint8_t MAX_NUM_PCE_ADIF = 16;
+const uint8_t ADIF_COPYID_SIZE = 9;
+const uint8_t HUFFTAB_SPEC_OFFSET = 1;
+const uint8_t FBITS_OUT_DQ_OFF = 20 - 15; /* (FBITS_OUT_DQ - SF_DQ_OFFSET) */
+const uint8_t GBITS_IN_DCT4 = 4; /* min guard bits in for DCT4 */
+const uint8_t FBITS_LOST_DCT4 = 1; /* number of fraction bits lost (>> out) in DCT-IV */
+const uint8_t FBITS_OUT_IMDCT = 3;
+const uint8_t NUM_IMDCT_SIZES = 2;
+const uint8_t FBITS_LPC_COEFS = 20;
+const uint8_t NUM_ITER_INVSQRT = 4;
+const uint32_t X0_COEF_2 = 0xc0000000; /* Q29: -2.0 */
+const uint32_t X0_OFF_2 = 0x60000000; /* Q29: 3.0 */
+const uint32_t Q26_3 = 0x0c000000; /* Q26: 3.0 */
+const uint8_t EXT_SBR_DATA = 0x0d;
+const uint8_t EXT_SBR_DATA_CRC = 0x0e;
+const uint8_t NUM_SAMPLE_RATES_SBR = 9; /* downsampled (single-rate) mode unsupported */
+const uint8_t MAX_NUM_PATCHES = 5;
+const uint8_t MAX_QMF_BANDS = 48; /* max QMF subbands covered by SBR (4.6.18.3.6) */
+const uint8_t MAX_NUM_ENV = 5;
+const uint8_t NUM_QMF_DELAY_BUFS = 10;
+const uint8_t FBITS_IN_QMFA = 14;
+const uint8_t NOISE_FLOOR_OFFSET = 6;
+const uint8_t FBITS_OUT_DQ_NOISE = 24; /* range of Q_orig = [2^-24, 2^6] */
+const uint8_t FBITS_LOST_QMFA = (1 + 2 + 3 + 2 + 1);
+const uint8_t FBITS_OUT_QMFA = (FBITS_IN_QMFA - FBITS_LOST_QMFA);
+const uint8_t FBITS_IN_QMFS = FBITS_OUT_QMFA;
+const uint8_t FBITS_LOST_DCT4_64 = (2 + 3 + 2); /* 2 in premul, 3 in FFT, 2 in postmul */
+const uint8_t FBITS_OUT_QMFS = (FBITS_IN_QMFS - FBITS_LOST_DCT4_64 + 6 - 1);
+const uint8_t RND_VAL = (1 << (FBITS_OUT_QMFS-1));
+const uint8_t HF_ADJ = 2;
+const uint8_t HF_GEN = 8;
+const uint8_t FBITS_LPCOEFS = 29; /* Q29 for range of (-4, 4) */
+const uint32_t MAG_16 = (16 * (1 << (32 - (2*(32-FBITS_LPCOEFS))))); /* i.e. 16 in Q26 format */
+const uint32_t RELAX_COEF = 0x7ffff79c; /* 1.0 / (1.0 + 1e-6), Q31 */
+const uint8_t MAX_NUM_SMOOTH_COEFS = 5;
+const uint8_t FBITS_OUT_DQ_ENV = 29; /* dequantized env scalefactors are Q(29 - envDataDequantScale) */
+const uint8_t FBITS_GLIM_BOOST = 24;
+const uint8_t FBITS_QLIM_BOOST = 14;
+const uint8_t MIN_GBITS_IN_QMFS = 2;
+const uint16_t nmdctTab[2] = {128, 1024};
+const uint8_t postSkip[2] = {15, 1};
+const uint16_t nfftTab[2] = {64, 512};
+const uint8_t nfftlog2Tab[2] = {6, 9};
+const uint8_t cos4sin4tabOffset[2] = {0, 128};
+
+PSInfoBase_t *m_PSInfoBase;
+AACDecInfo_t *m_AACDecInfo;
+AACFrameInfo_t m_AACFrameInfo;
+ADTSHeader_t m_fhADTS;
+ADIFHeader_t m_fhADIF;
+ProgConfigElement_t *m_pce[16];
+PulseInfo_t m_pulseInfo[2]; // [MAX_NCHANS_ELEM]
+aac_BitStreamInfo_t m_aac_BitStreamInfo;
+PSInfoSBR_t *m_PSInfoSBR;
+
+//----------------------------------------------------------------------------------------------------------------------
+inline int32_t MULSHIFT32(int32_t x, int32_t y){
+ int32_t z; z = (int64_t)x * (int64_t)y >> 32;
+ return z;
+}
+inline int32_t CLZ(int32_t x){
+#ifdef __XTENSA__
+ return __builtin_clz(x);
+#else
+ int32_t numZeros;
+ if(!x) return 32; /* count leading zeros with binary search (function should be 17 ARM instructions total) */
+ numZeros = 1;
+ if (!((uint32_t)x >> 16)) { numZeros += 16; x <<= 16; }
+ if (!((uint32_t)x >> 24)) { numZeros += 8; x <<= 8; }
+ if (!((uint32_t)x >> 28)) { numZeros += 4; x <<= 4; }
+ if (!((uint32_t)x >> 30)) { numZeros += 2; x <<= 2; }
+ numZeros -= ((uint32_t)x >> 31);
+ return numZeros;
+#endif
+}
+inline int32_t FASTABS(int32_t x){
+#ifdef __XTENSA__ //fb
+ return __builtin_abs(x);
+#else
+ int32_t sign;
+ sign = x >> (sizeof(int32_t) * 8 - 1);
+ x ^= sign; x -= sign; return x;
+#endif
+}
+inline int64_t MADD64(int64_t sum64, int32_t x, int32_t y){
+ sum64 += (int64_t)x * (int64_t)y;
+ return sum64;
+}
+inline int16_t CLIPTOSHORT(int32_t x){
+#ifdef __XTENSA__ //fb
+ asm ("clamps %0, %1, 15" : "=a" (x) : "a" (x) : );
+ return x;
+#else
+ int32_t sign; /* clip to [-32768, 32767] */
+ sign = x >> 31;
+ if (sign != (x >> 15)) x = sign ^ ((1 << 15) - 1);
+ return (int16_t)x;
+#endif
+}
+inline int32_t CLIP_2N(int32_t y, int32_t n){
+#ifdef __XTENSA__ //fb
+ int32_t x = 1 << n; \
+ if (y < -x) y = -x; \
+ x--; \
+ if (y > x) y = x; \
+ return y;
+#else
+ int32_t sign = y >> 31;
+ if(sign != (y >> n))
+ y = sign ^ ((1 << n) - 1);
+ return y;
+#endif
+}
+inline int32_t CLIP_2N_SHIFT30(int32_t y, int32_t n){
+ int32_t sign = y >> 31;
+ if(sign != (y >> (30 - n)))
+ y = sign ^ (0x3fffffff);
+ else
+ y = (y << n);
+ return y;
+}
+//----------------------------------------------------------------------------------------------------------------------
+
+const uint32_t cos4sin4tab[128 + 1024] PROGMEM = {
+/* 128 - format = Q30 * 2^-7 */
+0xbf9bc731, 0xff9b783c, 0xbed5332c, 0xc002c697, 0xbe112251, 0xfe096c8d, 0xbd4f9c30, 0xc00f1c4a,
+0xbc90a83f, 0xfc77ae5e, 0xbbd44dd9, 0xc0254e27, 0xbb1a9443, 0xfae67ba2, 0xba6382a6, 0xc04558c0,
+0xb9af200f, 0xf9561237, 0xb8fd7373, 0xc06f3726, 0xb84e83ac, 0xf7c6afdc, 0xb7a25779, 0xc0a2e2e3,
+0xb6f8f57c, 0xf6389228, 0xb652643e, 0xc0e05401, 0xb5aeaa2a, 0xf4abf67e, 0xb50dcd90, 0xc1278104,
+0xb46fd4a4, 0xf3211a07, 0xb3d4c57c, 0xc1785ef4, 0xb33ca614, 0xf19839a6, 0xb2a77c49, 0xc1d2e158,
+0xb2154dda, 0xf01191f3, 0xb186206b, 0xc236fa3b, 0xb0f9f981, 0xee8d5f29, 0xb070de82, 0xc2a49a2e,
+0xafead4b9, 0xed0bdd25, 0xaf67e14f, 0xc31bb049, 0xaee80952, 0xeb8d475b, 0xae6b51ae, 0xc39c2a2f,
+0xadf1bf34, 0xea11d8c8, 0xad7b5692, 0xc425f410, 0xad081c5a, 0xe899cbf1, 0xac9814fd, 0xc4b8f8ad,
+0xac2b44cc, 0xe7255ad1, 0xabc1aff9, 0xc555215a, 0xab5b5a96, 0xe5b4bed8, 0xaaf84896, 0xc5fa5603,
+0xaa987dca, 0xe44830dd, 0xaa3bfde3, 0xc6a87d2d, 0xa9e2cc73, 0xe2dfe917, 0xa98cece9, 0xc75f7bfe,
+0xa93a6296, 0xe17c1f15, 0xa8eb30a7, 0xc81f363d, 0xa89f5a2b, 0xe01d09b4, 0xa856e20e, 0xc8e78e5b,
+0xa811cb1b, 0xdec2df18, 0xa7d017fc, 0xc9b86572, 0xa791cb39, 0xdd6dd4a2, 0xa756e73a, 0xca919b4e,
+0xa71f6e43, 0xdc1e1ee9, 0xa6eb6279, 0xcb730e70, 0xa6bac5dc, 0xdad3f1b1, 0xa68d9a4c, 0xcc5c9c14,
+0xa663e188, 0xd98f7fe6, 0xa63d9d2b, 0xcd4e2037, 0xa61aceaf, 0xd850fb8e, 0xa5fb776b, 0xce47759a,
+0xa5df9894, 0xd71895c9, 0xa5c7333e, 0xcf4875ca, 0xa5b2485a, 0xd5e67ec1, 0xa5a0d8b5, 0xd050f926,
+0xa592e4fd, 0xd4bae5ab, 0xa5886dba, 0xd160d6e5, 0xa5817354, 0xd395f8ba, 0xa57df60f, 0xd277e518,
+/* 1024 - format = Q30 * 2^-10 */
+0xbff3703e, 0xfff36f02, 0xbfda5824, 0xc0000b1a, 0xbfc149ed, 0xffc12b16, 0xbfa845a0, 0xc0003c74,
+0xbf8f4b3e, 0xff8ee750, 0xbf765acc, 0xc0009547, 0xbf5d744e, 0xff5ca3d0, 0xbf4497c8, 0xc0011594,
+0xbf2bc53d, 0xff2a60b4, 0xbf12fcb2, 0xc001bd5c, 0xbefa3e2a, 0xfef81e1d, 0xbee189a8, 0xc0028c9c,
+0xbec8df32, 0xfec5dc28, 0xbeb03eca, 0xc0038356, 0xbe97a875, 0xfe939af5, 0xbe7f1c36, 0xc004a188,
+0xbe669a10, 0xfe615aa3, 0xbe4e2209, 0xc005e731, 0xbe35b423, 0xfe2f1b50, 0xbe1d5062, 0xc0075452,
+0xbe04f6cb, 0xfdfcdd1d, 0xbdeca760, 0xc008e8e8, 0xbdd46225, 0xfdcaa027, 0xbdbc2720, 0xc00aa4f3,
+0xbda3f652, 0xfd98648d, 0xbd8bcfbf, 0xc00c8872, 0xbd73b36d, 0xfd662a70, 0xbd5ba15d, 0xc00e9364,
+0xbd439995, 0xfd33f1ed, 0xbd2b9c17, 0xc010c5c7, 0xbd13a8e7, 0xfd01bb24, 0xbcfbc00a, 0xc0131f9b,
+0xbce3e182, 0xfccf8634, 0xbccc0d53, 0xc015a0dd, 0xbcb44382, 0xfc9d533b, 0xbc9c8411, 0xc018498c,
+0xbc84cf05, 0xfc6b2259, 0xbc6d2461, 0xc01b19a7, 0xbc558428, 0xfc38f3ac, 0xbc3dee5f, 0xc01e112b,
+0xbc266309, 0xfc06c754, 0xbc0ee22a, 0xc0213018, 0xbbf76bc4, 0xfbd49d70, 0xbbdfffdd, 0xc024766a,
+0xbbc89e77, 0xfba2761e, 0xbbb14796, 0xc027e421, 0xbb99fb3e, 0xfb70517d, 0xbb82b972, 0xc02b7939,
+0xbb6b8235, 0xfb3e2fac, 0xbb54558d, 0xc02f35b1, 0xbb3d337b, 0xfb0c10cb, 0xbb261c04, 0xc0331986,
+0xbb0f0f2b, 0xfad9f4f8, 0xbaf80cf4, 0xc03724b6, 0xbae11561, 0xfaa7dc52, 0xbaca2878, 0xc03b573f,
+0xbab3463b, 0xfa75c6f8, 0xba9c6eae, 0xc03fb11d, 0xba85a1d4, 0xfa43b508, 0xba6edfb1, 0xc044324f,
+0xba582849, 0xfa11a6a3, 0xba417b9e, 0xc048dad1, 0xba2ad9b5, 0xf9df9be6, 0xba144291, 0xc04daaa1,
+0xb9fdb635, 0xf9ad94f0, 0xb9e734a4, 0xc052a1bb, 0xb9d0bde4, 0xf97b91e1, 0xb9ba51f6, 0xc057c01d,
+0xb9a3f0de, 0xf94992d7, 0xb98d9aa0, 0xc05d05c3, 0xb9774f3f, 0xf91797f0, 0xb9610ebe, 0xc06272aa,
+0xb94ad922, 0xf8e5a14d, 0xb934ae6d, 0xc06806ce, 0xb91e8ea3, 0xf8b3af0c, 0xb90879c7, 0xc06dc22e,
+0xb8f26fdc, 0xf881c14b, 0xb8dc70e7, 0xc073a4c3, 0xb8c67cea, 0xf84fd829, 0xb8b093ea, 0xc079ae8c,
+0xb89ab5e8, 0xf81df3c5, 0xb884e2e9, 0xc07fdf85, 0xb86f1af0, 0xf7ec143e, 0xb8595e00, 0xc08637a9,
+0xb843ac1d, 0xf7ba39b3, 0xb82e0549, 0xc08cb6f5, 0xb818698a, 0xf7886442, 0xb802d8e0, 0xc0935d64,
+0xb7ed5351, 0xf756940a, 0xb7d7d8df, 0xc09a2af3, 0xb7c2698e, 0xf724c92a, 0xb7ad0561, 0xc0a11f9d,
+0xb797ac5b, 0xf6f303c0, 0xb7825e80, 0xc0a83b5e, 0xb76d1bd2, 0xf6c143ec, 0xb757e455, 0xc0af7e33,
+0xb742b80d, 0xf68f89cb, 0xb72d96fd, 0xc0b6e815, 0xb7188127, 0xf65dd57d, 0xb7037690, 0xc0be7901,
+0xb6ee773a, 0xf62c2721, 0xb6d98328, 0xc0c630f2, 0xb6c49a5e, 0xf5fa7ed4, 0xb6afbce0, 0xc0ce0fe3,
+0xb69aeab0, 0xf5c8dcb6, 0xb68623d1, 0xc0d615cf, 0xb6716847, 0xf59740e5, 0xb65cb815, 0xc0de42b2,
+0xb648133e, 0xf565ab80, 0xb63379c5, 0xc0e69686, 0xb61eebae, 0xf5341ca5, 0xb60a68fb, 0xc0ef1147,
+0xb5f5f1b1, 0xf5029473, 0xb5e185d1, 0xc0f7b2ee, 0xb5cd255f, 0xf4d11308, 0xb5b8d05f, 0xc1007b77,
+0xb5a486d2, 0xf49f9884, 0xb59048be, 0xc1096add, 0xb57c1624, 0xf46e2504, 0xb567ef08, 0xc1128119,
+0xb553d36c, 0xf43cb8a7, 0xb53fc355, 0xc11bbe26, 0xb52bbec4, 0xf40b538b, 0xb517c5be, 0xc12521ff,
+0xb503d845, 0xf3d9f5cf, 0xb4eff65c, 0xc12eac9d, 0xb4dc2007, 0xf3a89f92, 0xb4c85548, 0xc1385dfb,
+0xb4b49622, 0xf37750f2, 0xb4a0e299, 0xc1423613, 0xb48d3ab0, 0xf3460a0d, 0xb4799e69, 0xc14c34df,
+0xb4660dc8, 0xf314cb02, 0xb45288cf, 0xc1565a58, 0xb43f0f82, 0xf2e393ef, 0xb42ba1e4, 0xc160a678,
+0xb4183ff7, 0xf2b264f2, 0xb404e9bf, 0xc16b193a, 0xb3f19f3e, 0xf2813e2a, 0xb3de6078, 0xc175b296,
+0xb3cb2d70, 0xf2501fb5, 0xb3b80628, 0xc1807285, 0xb3a4eaa4, 0xf21f09b1, 0xb391dae6, 0xc18b5903,
+0xb37ed6f1, 0xf1edfc3d, 0xb36bdec9, 0xc1966606, 0xb358f26f, 0xf1bcf777, 0xb34611e8, 0xc1a1998a,
+0xb3333d36, 0xf18bfb7d, 0xb320745c, 0xc1acf386, 0xb30db75d, 0xf15b086d, 0xb2fb063b, 0xc1b873f5,
+0xb2e860fa, 0xf12a1e66, 0xb2d5c79d, 0xc1c41ace, 0xb2c33a26, 0xf0f93d86, 0xb2b0b898, 0xc1cfe80a,
+0xb29e42f6, 0xf0c865ea, 0xb28bd943, 0xc1dbdba3, 0xb2797b82, 0xf09797b2, 0xb26729b5, 0xc1e7f591,
+0xb254e3e0, 0xf066d2fa, 0xb242aa05, 0xc1f435cc, 0xb2307c27, 0xf03617e2, 0xb21e5a49, 0xc2009c4e,
+0xb20c446d, 0xf0056687, 0xb1fa3a97, 0xc20d290d, 0xb1e83cc9, 0xefd4bf08, 0xb1d64b06, 0xc219dc03,
+0xb1c46551, 0xefa42181, 0xb1b28bad, 0xc226b528, 0xb1a0be1b, 0xef738e12, 0xb18efca0, 0xc233b473,
+0xb17d473d, 0xef4304d8, 0xb16b9df6, 0xc240d9de, 0xb15a00cd, 0xef1285f2, 0xb1486fc5, 0xc24e255e,
+0xb136eae1, 0xeee2117c, 0xb1257223, 0xc25b96ee, 0xb114058e, 0xeeb1a796, 0xb102a524, 0xc2692e83,
+0xb0f150e9, 0xee81485c, 0xb0e008e0, 0xc276ec16, 0xb0cecd09, 0xee50f3ed, 0xb0bd9d6a, 0xc284cf9f,
+0xb0ac7a03, 0xee20aa67, 0xb09b62d8, 0xc292d914, 0xb08a57eb, 0xedf06be6, 0xb079593f, 0xc2a1086d,
+0xb06866d7, 0xedc0388a, 0xb05780b5, 0xc2af5da2, 0xb046a6db, 0xed901070, 0xb035d94e, 0xc2bdd8a9,
+0xb025180e, 0xed5ff3b5, 0xb014631e, 0xc2cc7979, 0xb003ba82, 0xed2fe277, 0xaff31e3b, 0xc2db400a,
+0xafe28e4d, 0xecffdcd4, 0xafd20ab9, 0xc2ea2c53, 0xafc19383, 0xeccfe2ea, 0xafb128ad, 0xc2f93e4a,
+0xafa0ca39, 0xec9ff4d6, 0xaf90782a, 0xc30875e5, 0xaf803283, 0xec7012b5, 0xaf6ff945, 0xc317d31c,
+0xaf5fcc74, 0xec403ca5, 0xaf4fac12, 0xc32755e5, 0xaf3f9822, 0xec1072c4, 0xaf2f90a5, 0xc336fe37,
+0xaf1f959f, 0xebe0b52f, 0xaf0fa712, 0xc346cc07, 0xaeffc500, 0xebb10404, 0xaeefef6c, 0xc356bf4d,
+0xaee02658, 0xeb815f60, 0xaed069c7, 0xc366d7fd, 0xaec0b9bb, 0xeb51c760, 0xaeb11636, 0xc377160f,
+0xaea17f3b, 0xeb223c22, 0xae91f4cd, 0xc3877978, 0xae8276ed, 0xeaf2bdc3, 0xae73059f, 0xc398022f,
+0xae63a0e3, 0xeac34c60, 0xae5448be, 0xc3a8b028, 0xae44fd31, 0xea93e817, 0xae35be3f, 0xc3b9835a,
+0xae268be9, 0xea649105, 0xae176633, 0xc3ca7bba, 0xae084d1f, 0xea354746, 0xadf940ae, 0xc3db993e,
+0xadea40e4, 0xea060af9, 0xaddb4dc2, 0xc3ecdbdc, 0xadcc674b, 0xe9d6dc3b, 0xadbd8d82, 0xc3fe4388,
+0xadaec067, 0xe9a7bb28, 0xad9fffff, 0xc40fd037, 0xad914c4b, 0xe978a7dd, 0xad82a54c, 0xc42181e0,
+0xad740b07, 0xe949a278, 0xad657d7c, 0xc4335877, 0xad56fcaf, 0xe91aab16, 0xad4888a0, 0xc44553f2,
+0xad3a2153, 0xe8ebc1d3, 0xad2bc6ca, 0xc4577444, 0xad1d7907, 0xe8bce6cd, 0xad0f380c, 0xc469b963,
+0xad0103db, 0xe88e1a20, 0xacf2dc77, 0xc47c2344, 0xace4c1e2, 0xe85f5be9, 0xacd6b41e, 0xc48eb1db,
+0xacc8b32c, 0xe830ac45, 0xacbabf10, 0xc4a1651c, 0xacacd7cb, 0xe8020b52, 0xac9efd60, 0xc4b43cfd,
+0xac912fd1, 0xe7d3792b, 0xac836f1f, 0xc4c73972, 0xac75bb4d, 0xe7a4f5ed, 0xac68145d, 0xc4da5a6f,
+0xac5a7a52, 0xe77681b6, 0xac4ced2c, 0xc4ed9fe7, 0xac3f6cef, 0xe7481ca1, 0xac31f99d, 0xc50109d0,
+0xac249336, 0xe719c6cb, 0xac1739bf, 0xc514981d, 0xac09ed38, 0xe6eb8052, 0xabfcada3, 0xc5284ac3,
+0xabef7b04, 0xe6bd4951, 0xabe2555b, 0xc53c21b4, 0xabd53caa, 0xe68f21e5, 0xabc830f5, 0xc5501ce5,
+0xabbb323c, 0xe6610a2a, 0xabae4082, 0xc5643c4a, 0xaba15bc9, 0xe633023e, 0xab948413, 0xc5787fd6,
+0xab87b962, 0xe6050a3b, 0xab7afbb7, 0xc58ce77c, 0xab6e4b15, 0xe5d72240, 0xab61a77d, 0xc5a17330,
+0xab5510f3, 0xe5a94a67, 0xab488776, 0xc5b622e6, 0xab3c0b0b, 0xe57b82cd, 0xab2f9bb1, 0xc5caf690,
+0xab23396c, 0xe54dcb8f, 0xab16e43d, 0xc5dfee22, 0xab0a9c27, 0xe52024c9, 0xaafe612a, 0xc5f5098f,
+0xaaf23349, 0xe4f28e96, 0xaae61286, 0xc60a48c9, 0xaad9fee3, 0xe4c50914, 0xaacdf861, 0xc61fabc4,
+0xaac1ff03, 0xe497945d, 0xaab612ca, 0xc6353273, 0xaaaa33b8, 0xe46a308f, 0xaa9e61cf, 0xc64adcc7,
+0xaa929d10, 0xe43cddc4, 0xaa86e57e, 0xc660aab5, 0xaa7b3b1b, 0xe40f9c1a, 0xaa6f9de7, 0xc6769c2e,
+0xaa640de6, 0xe3e26bac, 0xaa588b18, 0xc68cb124, 0xaa4d157f, 0xe3b54c95, 0xaa41ad1e, 0xc6a2e98b,
+0xaa3651f6, 0xe3883ef2, 0xaa2b0409, 0xc6b94554, 0xaa1fc358, 0xe35b42df, 0xaa148fe6, 0xc6cfc472,
+0xaa0969b3, 0xe32e5876, 0xa9fe50c2, 0xc6e666d7, 0xa9f34515, 0xe3017fd5, 0xa9e846ad, 0xc6fd2c75,
+0xa9dd558b, 0xe2d4b916, 0xa9d271b2, 0xc714153e, 0xa9c79b23, 0xe2a80456, 0xa9bcd1e0, 0xc72b2123,
+0xa9b215ea, 0xe27b61af, 0xa9a76744, 0xc7425016, 0xa99cc5ee, 0xe24ed13d, 0xa99231eb, 0xc759a20a,
+0xa987ab3c, 0xe222531c, 0xa97d31e3, 0xc77116f0, 0xa972c5e1, 0xe1f5e768, 0xa9686738, 0xc788aeb9,
+0xa95e15e9, 0xe1c98e3b, 0xa953d1f7, 0xc7a06957, 0xa9499b62, 0xe19d47b1, 0xa93f722c, 0xc7b846ba,
+0xa9355658, 0xe17113e5, 0xa92b47e5, 0xc7d046d6, 0xa92146d7, 0xe144f2f3, 0xa917532e, 0xc7e8699a,
+0xa90d6cec, 0xe118e4f6, 0xa9039413, 0xc800aef7, 0xa8f9c8a4, 0xe0ecea09, 0xa8f00aa0, 0xc81916df,
+0xa8e65a0a, 0xe0c10247, 0xa8dcb6e2, 0xc831a143, 0xa8d3212a, 0xe0952dcb, 0xa8c998e3, 0xc84a4e14,
+0xa8c01e10, 0xe0696cb0, 0xa8b6b0b1, 0xc8631d42, 0xa8ad50c8, 0xe03dbf11, 0xa8a3fe57, 0xc87c0ebd,
+0xa89ab95e, 0xe012250a, 0xa89181df, 0xc8952278, 0xa88857dc, 0xdfe69eb4, 0xa87f3b57, 0xc8ae5862,
+0xa8762c4f, 0xdfbb2c2c, 0xa86d2ac8, 0xc8c7b06b, 0xa86436c2, 0xdf8fcd8b, 0xa85b503e, 0xc8e12a84,
+0xa852773f, 0xdf6482ed, 0xa849abc4, 0xc8fac69e, 0xa840edd1, 0xdf394c6b, 0xa8383d66, 0xc91484a8,
+0xa82f9a84, 0xdf0e2a22, 0xa827052d, 0xc92e6492, 0xa81e7d62, 0xdee31c2b, 0xa8160324, 0xc948664d,
+0xa80d9675, 0xdeb822a1, 0xa8053756, 0xc96289c9, 0xa7fce5c9, 0xde8d3d9e, 0xa7f4a1ce, 0xc97ccef5,
+0xa7ec6b66, 0xde626d3e, 0xa7e44294, 0xc99735c2, 0xa7dc2759, 0xde37b199, 0xa7d419b4, 0xc9b1be1e,
+0xa7cc19a9, 0xde0d0acc, 0xa7c42738, 0xc9cc67fa, 0xa7bc4262, 0xdde278ef, 0xa7b46b29, 0xc9e73346,
+0xa7aca18e, 0xddb7fc1e, 0xa7a4e591, 0xca021fef, 0xa79d3735, 0xdd8d9472, 0xa795967a, 0xca1d2de7,
+0xa78e0361, 0xdd634206, 0xa7867dec, 0xca385d1d, 0xa77f061c, 0xdd3904f4, 0xa7779bf2, 0xca53ad7e,
+0xa7703f70, 0xdd0edd55, 0xa768f095, 0xca6f1efc, 0xa761af64, 0xdce4cb44, 0xa75a7bdd, 0xca8ab184,
+0xa7535602, 0xdcbacedb, 0xa74c3dd4, 0xcaa66506, 0xa7453353, 0xdc90e834, 0xa73e3681, 0xcac23971,
+0xa7374760, 0xdc671768, 0xa73065ef, 0xcade2eb3, 0xa7299231, 0xdc3d5c91, 0xa722cc25, 0xcafa44bc,
+0xa71c13ce, 0xdc13b7c9, 0xa715692c, 0xcb167b79, 0xa70ecc41, 0xdbea292b, 0xa7083d0d, 0xcb32d2da,
+0xa701bb91, 0xdbc0b0ce, 0xa6fb47ce, 0xcb4f4acd, 0xa6f4e1c6, 0xdb974ece, 0xa6ee8979, 0xcb6be341,
+0xa6e83ee8, 0xdb6e0342, 0xa6e20214, 0xcb889c23, 0xa6dbd2ff, 0xdb44ce46, 0xa6d5b1a9, 0xcba57563,
+0xa6cf9e13, 0xdb1baff2, 0xa6c9983e, 0xcbc26eee, 0xa6c3a02b, 0xdaf2a860, 0xa6bdb5da, 0xcbdf88b3,
+0xa6b7d94e, 0xdac9b7a9, 0xa6b20a86, 0xcbfcc29f, 0xa6ac4984, 0xdaa0dde7, 0xa6a69649, 0xcc1a1ca0,
+0xa6a0f0d5, 0xda781b31, 0xa69b5929, 0xcc3796a5, 0xa695cf46, 0xda4f6fa3, 0xa690532d, 0xcc55309b,
+0xa68ae4df, 0xda26db54, 0xa685845c, 0xcc72ea70, 0xa68031a6, 0xd9fe5e5e, 0xa67aecbd, 0xcc90c412,
+0xa675b5a3, 0xd9d5f8d9, 0xa6708c57, 0xccaebd6e, 0xa66b70db, 0xd9adaadf, 0xa6666330, 0xccccd671,
+0xa6616355, 0xd9857489, 0xa65c714d, 0xcceb0f0a, 0xa6578d18, 0xd95d55ef, 0xa652b6b6, 0xcd096725,
+0xa64dee28, 0xd9354f2a, 0xa6493370, 0xcd27deb0, 0xa644868d, 0xd90d6053, 0xa63fe781, 0xcd467599,
+0xa63b564c, 0xd8e58982, 0xa636d2ee, 0xcd652bcb, 0xa6325d6a, 0xd8bdcad0, 0xa62df5bf, 0xcd840134,
+0xa6299bed, 0xd8962456, 0xa6254ff7, 0xcda2f5c2, 0xa62111db, 0xd86e962b, 0xa61ce19c, 0xcdc20960,
+0xa618bf39, 0xd8472069, 0xa614aab3, 0xcde13bfd, 0xa610a40c, 0xd81fc328, 0xa60cab43, 0xce008d84,
+0xa608c058, 0xd7f87e7f, 0xa604e34e, 0xce1ffde2, 0xa6011424, 0xd7d15288, 0xa5fd52db, 0xce3f8d05,
+0xa5f99f73, 0xd7aa3f5a, 0xa5f5f9ed, 0xce5f3ad8, 0xa5f2624a, 0xd783450d, 0xa5eed88a, 0xce7f0748,
+0xa5eb5cae, 0xd75c63ba, 0xa5e7eeb6, 0xce9ef241, 0xa5e48ea3, 0xd7359b78, 0xa5e13c75, 0xcebefbb0,
+0xa5ddf82d, 0xd70eec60, 0xa5dac1cb, 0xcedf2380, 0xa5d79950, 0xd6e85689, 0xa5d47ebc, 0xceff699f,
+0xa5d17210, 0xd6c1da0b, 0xa5ce734d, 0xcf1fcdf8, 0xa5cb8272, 0xd69b76fe, 0xa5c89f80, 0xcf405077,
+0xa5c5ca77, 0xd6752d79, 0xa5c30359, 0xcf60f108, 0xa5c04a25, 0xd64efd94, 0xa5bd9edc, 0xcf81af97,
+0xa5bb017f, 0xd628e767, 0xa5b8720d, 0xcfa28c10, 0xa5b5f087, 0xd602eb0a, 0xa5b37cee, 0xcfc3865e,
+0xa5b11741, 0xd5dd0892, 0xa5aebf82, 0xcfe49e6d, 0xa5ac75b0, 0xd5b74019, 0xa5aa39cd, 0xd005d42a,
+0xa5a80bd7, 0xd59191b5, 0xa5a5ebd0, 0xd027277e, 0xa5a3d9b8, 0xd56bfd7d, 0xa5a1d590, 0xd0489856,
+0xa59fdf57, 0xd5468389, 0xa59df70e, 0xd06a269d, 0xa59c1cb5, 0xd52123f0, 0xa59a504c, 0xd08bd23f,
+0xa59891d4, 0xd4fbdec9, 0xa596e14e, 0xd0ad9b26, 0xa5953eb8, 0xd4d6b42b, 0xa593aa14, 0xd0cf813e,
+0xa5922362, 0xd4b1a42c, 0xa590aaa2, 0xd0f18472, 0xa58f3fd4, 0xd48caee4, 0xa58de2f8, 0xd113a4ad,
+0xa58c940f, 0xd467d469, 0xa58b5319, 0xd135e1d9, 0xa58a2016, 0xd44314d3, 0xa588fb06, 0xd1583be2,
+0xa587e3ea, 0xd41e7037, 0xa586dac1, 0xd17ab2b3, 0xa585df8c, 0xd3f9e6ad, 0xa584f24b, 0xd19d4636,
+0xa58412fe, 0xd3d5784a, 0xa58341a5, 0xd1bff656, 0xa5827e40, 0xd3b12526, 0xa581c8d0, 0xd1e2c2fd,
+0xa5812154, 0xd38ced57, 0xa58087cd, 0xd205ac17, 0xa57ffc3b, 0xd368d0f3, 0xa57f7e9d, 0xd228b18d,
+0xa57f0ef5, 0xd344d011, 0xa57ead41, 0xd24bd34a, 0xa57e5982, 0xd320eac6, 0xa57e13b8, 0xd26f1138,
+0xa57ddbe4, 0xd2fd2129, 0xa57db204, 0xd2926b41, 0xa57d961a, 0xd2d97350, 0xa57d8825, 0xd2b5e151,
+};
+
+const int32_t cos1sin1tab[514] PROGMEM = {
+/* format = Q30 */
+0x40000000, 0x00000000, 0x40323034, 0x003243f1, 0x406438cf, 0x006487c4, 0x409619b2, 0x0096cb58,
+0x40c7d2bd, 0x00c90e90, 0x40f963d3, 0x00fb514b, 0x412accd4, 0x012d936c, 0x415c0da3, 0x015fd4d2,
+0x418d2621, 0x0192155f, 0x41be162f, 0x01c454f5, 0x41eeddaf, 0x01f69373, 0x421f7c84, 0x0228d0bb,
+0x424ff28f, 0x025b0caf, 0x42803fb2, 0x028d472e, 0x42b063d0, 0x02bf801a, 0x42e05ecb, 0x02f1b755,
+0x43103085, 0x0323ecbe, 0x433fd8e1, 0x03562038, 0x436f57c1, 0x038851a2, 0x439ead09, 0x03ba80df,
+0x43cdd89a, 0x03ecadcf, 0x43fcda59, 0x041ed854, 0x442bb227, 0x0451004d, 0x445a5fe8, 0x0483259d,
+0x4488e37f, 0x04b54825, 0x44b73ccf, 0x04e767c5, 0x44e56bbd, 0x0519845e, 0x4513702a, 0x054b9dd3,
+0x454149fc, 0x057db403, 0x456ef916, 0x05afc6d0, 0x459c7d5a, 0x05e1d61b, 0x45c9d6af, 0x0613e1c5,
+0x45f704f7, 0x0645e9af, 0x46240816, 0x0677edbb, 0x4650dff1, 0x06a9edc9, 0x467d8c6d, 0x06dbe9bb,
+0x46aa0d6d, 0x070de172, 0x46d662d6, 0x073fd4cf, 0x47028c8d, 0x0771c3b3, 0x472e8a76, 0x07a3adff,
+0x475a5c77, 0x07d59396, 0x47860275, 0x08077457, 0x47b17c54, 0x08395024, 0x47dcc9f9, 0x086b26de,
+0x4807eb4b, 0x089cf867, 0x4832e02d, 0x08cec4a0, 0x485da887, 0x09008b6a, 0x4888443d, 0x09324ca7,
+0x48b2b335, 0x09640837, 0x48dcf556, 0x0995bdfd, 0x49070a84, 0x09c76dd8, 0x4930f2a6, 0x09f917ac,
+0x495aada2, 0x0a2abb59, 0x49843b5f, 0x0a5c58c0, 0x49ad9bc2, 0x0a8defc3, 0x49d6ceb3, 0x0abf8043,
+0x49ffd417, 0x0af10a22, 0x4a28abd6, 0x0b228d42, 0x4a5155d6, 0x0b540982, 0x4a79d1ff, 0x0b857ec7,
+0x4aa22036, 0x0bb6ecef, 0x4aca4065, 0x0be853de, 0x4af23270, 0x0c19b374, 0x4b19f641, 0x0c4b0b94,
+0x4b418bbe, 0x0c7c5c1e, 0x4b68f2cf, 0x0cada4f5, 0x4b902b5c, 0x0cdee5f9, 0x4bb7354d, 0x0d101f0e,
+0x4bde1089, 0x0d415013, 0x4c04bcf8, 0x0d7278eb, 0x4c2b3a84, 0x0da39978, 0x4c518913, 0x0dd4b19a,
+0x4c77a88e, 0x0e05c135, 0x4c9d98de, 0x0e36c82a, 0x4cc359ec, 0x0e67c65a, 0x4ce8eb9f, 0x0e98bba7,
+0x4d0e4de2, 0x0ec9a7f3, 0x4d33809c, 0x0efa8b20, 0x4d5883b7, 0x0f2b650f, 0x4d7d571c, 0x0f5c35a3,
+0x4da1fab5, 0x0f8cfcbe, 0x4dc66e6a, 0x0fbdba40, 0x4deab226, 0x0fee6e0d, 0x4e0ec5d1, 0x101f1807,
+0x4e32a956, 0x104fb80e, 0x4e565c9f, 0x10804e06, 0x4e79df95, 0x10b0d9d0, 0x4e9d3222, 0x10e15b4e,
+0x4ec05432, 0x1111d263, 0x4ee345ad, 0x11423ef0, 0x4f06067f, 0x1172a0d7, 0x4f289692, 0x11a2f7fc,
+0x4f4af5d1, 0x11d3443f, 0x4f6d2427, 0x12038584, 0x4f8f217e, 0x1233bbac, 0x4fb0edc1, 0x1263e699,
+0x4fd288dc, 0x1294062f, 0x4ff3f2bb, 0x12c41a4f, 0x50152b47, 0x12f422db, 0x5036326e, 0x13241fb6,
+0x50570819, 0x135410c3, 0x5077ac37, 0x1383f5e3, 0x50981eb1, 0x13b3cefa, 0x50b85f74, 0x13e39be9,
+0x50d86e6d, 0x14135c94, 0x50f84b87, 0x144310dd, 0x5117f6ae, 0x1472b8a5, 0x51376fd0, 0x14a253d1,
+0x5156b6d9, 0x14d1e242, 0x5175cbb5, 0x150163dc, 0x5194ae52, 0x1530d881, 0x51b35e9b, 0x15604013,
+0x51d1dc80, 0x158f9a76, 0x51f027eb, 0x15bee78c, 0x520e40cc, 0x15ee2738, 0x522c270f, 0x161d595d,
+0x5249daa2, 0x164c7ddd, 0x52675b72, 0x167b949d, 0x5284a96e, 0x16aa9d7e, 0x52a1c482, 0x16d99864,
+0x52beac9f, 0x17088531, 0x52db61b0, 0x173763c9, 0x52f7e3a6, 0x1766340f, 0x5314326d, 0x1794f5e6,
+0x53304df6, 0x17c3a931, 0x534c362d, 0x17f24dd3, 0x5367eb03, 0x1820e3b0, 0x53836c66, 0x184f6aab,
+0x539eba45, 0x187de2a7, 0x53b9d48f, 0x18ac4b87, 0x53d4bb34, 0x18daa52f, 0x53ef6e23, 0x1908ef82,
+0x5409ed4b, 0x19372a64, 0x5424389d, 0x196555b8, 0x543e5007, 0x19937161, 0x5458337a, 0x19c17d44,
+0x5471e2e6, 0x19ef7944, 0x548b5e3b, 0x1a1d6544, 0x54a4a56a, 0x1a4b4128, 0x54bdb862, 0x1a790cd4,
+0x54d69714, 0x1aa6c82b, 0x54ef4171, 0x1ad47312, 0x5507b76a, 0x1b020d6c, 0x551ff8ef, 0x1b2f971e,
+0x553805f2, 0x1b5d100a, 0x554fde64, 0x1b8a7815, 0x55678236, 0x1bb7cf23, 0x557ef15a, 0x1be51518,
+0x55962bc0, 0x1c1249d8, 0x55ad315b, 0x1c3f6d47, 0x55c4021d, 0x1c6c7f4a, 0x55da9df7, 0x1c997fc4,
+0x55f104dc, 0x1cc66e99, 0x560736bd, 0x1cf34baf, 0x561d338d, 0x1d2016e9, 0x5632fb3f, 0x1d4cd02c,
+0x56488dc5, 0x1d79775c, 0x565deb11, 0x1da60c5d, 0x56731317, 0x1dd28f15, 0x568805c9, 0x1dfeff67,
+0x569cc31b, 0x1e2b5d38, 0x56b14b00, 0x1e57a86d, 0x56c59d6a, 0x1e83e0eb, 0x56d9ba4e, 0x1eb00696,
+0x56eda1a0, 0x1edc1953, 0x57015352, 0x1f081907, 0x5714cf59, 0x1f340596, 0x572815a8, 0x1f5fdee6,
+0x573b2635, 0x1f8ba4dc, 0x574e00f2, 0x1fb7575c, 0x5760a5d5, 0x1fe2f64c, 0x577314d2, 0x200e8190,
+0x57854ddd, 0x2039f90f, 0x579750ec, 0x20655cac, 0x57a91df2, 0x2090ac4d, 0x57bab4e6, 0x20bbe7d8,
+0x57cc15bc, 0x20e70f32, 0x57dd406a, 0x21122240, 0x57ee34e5, 0x213d20e8, 0x57fef323, 0x21680b0f,
+0x580f7b19, 0x2192e09b, 0x581fccbc, 0x21bda171, 0x582fe804, 0x21e84d76, 0x583fcce6, 0x2212e492,
+0x584f7b58, 0x223d66a8, 0x585ef351, 0x2267d3a0, 0x586e34c7, 0x22922b5e, 0x587d3fb0, 0x22bc6dca,
+0x588c1404, 0x22e69ac8, 0x589ab1b9, 0x2310b23e, 0x58a918c6, 0x233ab414, 0x58b74923, 0x2364a02e,
+0x58c542c5, 0x238e7673, 0x58d305a6, 0x23b836ca, 0x58e091bd, 0x23e1e117, 0x58ede700, 0x240b7543,
+0x58fb0568, 0x2434f332, 0x5907eced, 0x245e5acc, 0x59149d87, 0x2487abf7, 0x5921172e, 0x24b0e699,
+0x592d59da, 0x24da0a9a, 0x59396584, 0x250317df, 0x59453a24, 0x252c0e4f, 0x5950d7b3, 0x2554edd1,
+0x595c3e2a, 0x257db64c, 0x59676d82, 0x25a667a7, 0x597265b4, 0x25cf01c8, 0x597d26b8, 0x25f78497,
+0x5987b08a, 0x261feffa, 0x59920321, 0x264843d9, 0x599c1e78, 0x2670801a, 0x59a60288, 0x2698a4a6,
+0x59afaf4c, 0x26c0b162, 0x59b924bc, 0x26e8a637, 0x59c262d5, 0x2710830c, 0x59cb698f, 0x273847c8,
+0x59d438e5, 0x275ff452, 0x59dcd0d3, 0x27878893, 0x59e53151, 0x27af0472, 0x59ed5a5c, 0x27d667d5,
+0x59f54bee, 0x27fdb2a7, 0x59fd0603, 0x2824e4cc, 0x5a048895, 0x284bfe2f, 0x5a0bd3a1, 0x2872feb6,
+0x5a12e720, 0x2899e64a, 0x5a19c310, 0x28c0b4d2, 0x5a20676c, 0x28e76a37, 0x5a26d42f, 0x290e0661,
+0x5a2d0957, 0x29348937, 0x5a3306de, 0x295af2a3, 0x5a38ccc2, 0x2981428c, 0x5a3e5afe, 0x29a778db,
+0x5a43b190, 0x29cd9578, 0x5a48d074, 0x29f3984c, 0x5a4db7a6, 0x2a19813f, 0x5a526725, 0x2a3f503a,
+0x5a56deec, 0x2a650525, 0x5a5b1efa, 0x2a8a9fea, 0x5a5f274b, 0x2ab02071, 0x5a62f7dd, 0x2ad586a3,
+0x5a6690ae, 0x2afad269, 0x5a69f1bb, 0x2b2003ac, 0x5a6d1b03, 0x2b451a55, 0x5a700c84, 0x2b6a164d,
+0x5a72c63b, 0x2b8ef77d, 0x5a754827, 0x2bb3bdce, 0x5a779246, 0x2bd8692b, 0x5a79a498, 0x2bfcf97c,
+0x5a7b7f1a, 0x2c216eaa, 0x5a7d21cc, 0x2c45c8a0, 0x5a7e8cac, 0x2c6a0746, 0x5a7fbfbb, 0x2c8e2a87,
+0x5a80baf6, 0x2cb2324c, 0x5a817e5d, 0x2cd61e7f, 0x5a8209f1, 0x2cf9ef09, 0x5a825db0, 0x2d1da3d5,
+0x5a82799a, 0x2d413ccd,
+};
+
+const uint8_t sinWindowOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 128};
+
+const int32_t sinWindow[128 + 1024] PROGMEM = {
+/* 128 - format = Q31 * 2^0 */
+0x00c90f88, 0x7fff6216, 0x025b26d7, 0x7ffa72d1, 0x03ed26e6, 0x7ff09478, 0x057f0035, 0x7fe1c76b,
+0x0710a345, 0x7fce0c3e, 0x08a2009a, 0x7fb563b3, 0x0a3308bd, 0x7f97cebd, 0x0bc3ac35, 0x7f754e80,
+0x0d53db92, 0x7f4de451, 0x0ee38766, 0x7f2191b4, 0x1072a048, 0x7ef05860, 0x120116d5, 0x7eba3a39,
+0x138edbb1, 0x7e7f3957, 0x151bdf86, 0x7e3f57ff, 0x16a81305, 0x7dfa98a8, 0x183366e9, 0x7db0fdf8,
+0x19bdcbf3, 0x7d628ac6, 0x1b4732ef, 0x7d0f4218, 0x1ccf8cb3, 0x7cb72724, 0x1e56ca1e, 0x7c5a3d50,
+0x1fdcdc1b, 0x7bf88830, 0x2161b3a0, 0x7b920b89, 0x22e541af, 0x7b26cb4f, 0x24677758, 0x7ab6cba4,
+0x25e845b6, 0x7a4210d8, 0x27679df4, 0x79c89f6e, 0x28e5714b, 0x794a7c12, 0x2a61b101, 0x78c7aba2,
+0x2bdc4e6f, 0x78403329, 0x2d553afc, 0x77b417df, 0x2ecc681e, 0x77235f2d, 0x3041c761, 0x768e0ea6,
+0x31b54a5e, 0x75f42c0b, 0x3326e2c3, 0x7555bd4c, 0x34968250, 0x74b2c884, 0x36041ad9, 0x740b53fb,
+0x376f9e46, 0x735f6626, 0x38d8fe93, 0x72af05a7, 0x3a402dd2, 0x71fa3949, 0x3ba51e29, 0x71410805,
+0x3d07c1d6, 0x708378ff, 0x3e680b2c, 0x6fc19385, 0x3fc5ec98, 0x6efb5f12, 0x4121589b, 0x6e30e34a,
+0x427a41d0, 0x6d6227fa, 0x43d09aed, 0x6c8f351c, 0x452456bd, 0x6bb812d1, 0x46756828, 0x6adcc964,
+0x47c3c22f, 0x69fd614a, 0x490f57ee, 0x6919e320, 0x4a581c9e, 0x683257ab, 0x4b9e0390, 0x6746c7d8,
+0x4ce10034, 0x66573cbb, 0x4e210617, 0x6563bf92, 0x4f5e08e3, 0x646c59bf, 0x5097fc5e, 0x637114cc,
+0x51ced46e, 0x6271fa69, 0x53028518, 0x616f146c, 0x5433027d, 0x60686ccf, 0x556040e2, 0x5f5e0db3,
+0x568a34a9, 0x5e50015d, 0x57b0d256, 0x5d3e5237, 0x58d40e8c, 0x5c290acc, 0x59f3de12, 0x5b1035cf,
+/* 1024 - format = Q31 * 2^0 */
+0x001921fb, 0x7ffffd88, 0x004b65ee, 0x7fffe9cb, 0x007da9d4, 0x7fffc251, 0x00afeda8, 0x7fff8719,
+0x00e23160, 0x7fff3824, 0x011474f6, 0x7ffed572, 0x0146b860, 0x7ffe5f03, 0x0178fb99, 0x7ffdd4d7,
+0x01ab3e97, 0x7ffd36ee, 0x01dd8154, 0x7ffc8549, 0x020fc3c6, 0x7ffbbfe6, 0x024205e8, 0x7ffae6c7,
+0x027447b0, 0x7ff9f9ec, 0x02a68917, 0x7ff8f954, 0x02d8ca16, 0x7ff7e500, 0x030b0aa4, 0x7ff6bcf0,
+0x033d4abb, 0x7ff58125, 0x036f8a51, 0x7ff4319d, 0x03a1c960, 0x7ff2ce5b, 0x03d407df, 0x7ff1575d,
+0x040645c7, 0x7fefcca4, 0x04388310, 0x7fee2e30, 0x046abfb3, 0x7fec7c02, 0x049cfba7, 0x7feab61a,
+0x04cf36e5, 0x7fe8dc78, 0x05017165, 0x7fe6ef1c, 0x0533ab20, 0x7fe4ee06, 0x0565e40d, 0x7fe2d938,
+0x05981c26, 0x7fe0b0b1, 0x05ca5361, 0x7fde7471, 0x05fc89b8, 0x7fdc247a, 0x062ebf22, 0x7fd9c0ca,
+0x0660f398, 0x7fd74964, 0x06932713, 0x7fd4be46, 0x06c5598a, 0x7fd21f72, 0x06f78af6, 0x7fcf6ce8,
+0x0729bb4e, 0x7fcca6a7, 0x075bea8c, 0x7fc9ccb2, 0x078e18a7, 0x7fc6df08, 0x07c04598, 0x7fc3dda9,
+0x07f27157, 0x7fc0c896, 0x08249bdd, 0x7fbd9fd0, 0x0856c520, 0x7fba6357, 0x0888ed1b, 0x7fb7132b,
+0x08bb13c5, 0x7fb3af4e, 0x08ed3916, 0x7fb037bf, 0x091f5d06, 0x7facac7f, 0x09517f8f, 0x7fa90d8e,
+0x0983a0a7, 0x7fa55aee, 0x09b5c048, 0x7fa1949e, 0x09e7de6a, 0x7f9dbaa0, 0x0a19fb04, 0x7f99ccf4,
+0x0a4c1610, 0x7f95cb9a, 0x0a7e2f85, 0x7f91b694, 0x0ab0475c, 0x7f8d8de1, 0x0ae25d8d, 0x7f895182,
+0x0b147211, 0x7f850179, 0x0b4684df, 0x7f809dc5, 0x0b7895f0, 0x7f7c2668, 0x0baaa53b, 0x7f779b62,
+0x0bdcb2bb, 0x7f72fcb4, 0x0c0ebe66, 0x7f6e4a5e, 0x0c40c835, 0x7f698461, 0x0c72d020, 0x7f64aabf,
+0x0ca4d620, 0x7f5fbd77, 0x0cd6da2d, 0x7f5abc8a, 0x0d08dc3f, 0x7f55a7fa, 0x0d3adc4e, 0x7f507fc7,
+0x0d6cda53, 0x7f4b43f2, 0x0d9ed646, 0x7f45f47b, 0x0dd0d01f, 0x7f409164, 0x0e02c7d7, 0x7f3b1aad,
+0x0e34bd66, 0x7f359057, 0x0e66b0c3, 0x7f2ff263, 0x0e98a1e9, 0x7f2a40d2, 0x0eca90ce, 0x7f247ba5,
+0x0efc7d6b, 0x7f1ea2dc, 0x0f2e67b8, 0x7f18b679, 0x0f604faf, 0x7f12b67c, 0x0f923546, 0x7f0ca2e7,
+0x0fc41876, 0x7f067bba, 0x0ff5f938, 0x7f0040f6, 0x1027d784, 0x7ef9f29d, 0x1059b352, 0x7ef390ae,
+0x108b8c9b, 0x7eed1b2c, 0x10bd6356, 0x7ee69217, 0x10ef377d, 0x7edff570, 0x11210907, 0x7ed94538,
+0x1152d7ed, 0x7ed28171, 0x1184a427, 0x7ecbaa1a, 0x11b66dad, 0x7ec4bf36, 0x11e83478, 0x7ebdc0c6,
+0x1219f880, 0x7eb6aeca, 0x124bb9be, 0x7eaf8943, 0x127d7829, 0x7ea85033, 0x12af33ba, 0x7ea1039b,
+0x12e0ec6a, 0x7e99a37c, 0x1312a230, 0x7e922fd6, 0x13445505, 0x7e8aa8ac, 0x137604e2, 0x7e830dff,
+0x13a7b1bf, 0x7e7b5fce, 0x13d95b93, 0x7e739e1d, 0x140b0258, 0x7e6bc8eb, 0x143ca605, 0x7e63e03b,
+0x146e4694, 0x7e5be40c, 0x149fe3fc, 0x7e53d462, 0x14d17e36, 0x7e4bb13c, 0x1503153a, 0x7e437a9c,
+0x1534a901, 0x7e3b3083, 0x15663982, 0x7e32d2f4, 0x1597c6b7, 0x7e2a61ed, 0x15c95097, 0x7e21dd73,
+0x15fad71b, 0x7e194584, 0x162c5a3b, 0x7e109a24, 0x165dd9f0, 0x7e07db52, 0x168f5632, 0x7dff0911,
+0x16c0cef9, 0x7df62362, 0x16f2443e, 0x7ded2a47, 0x1723b5f9, 0x7de41dc0, 0x17552422, 0x7ddafdce,
+0x17868eb3, 0x7dd1ca75, 0x17b7f5a3, 0x7dc883b4, 0x17e958ea, 0x7dbf298d, 0x181ab881, 0x7db5bc02,
+0x184c1461, 0x7dac3b15, 0x187d6c82, 0x7da2a6c6, 0x18aec0db, 0x7d98ff17, 0x18e01167, 0x7d8f4409,
+0x19115e1c, 0x7d85759f, 0x1942a6f3, 0x7d7b93da, 0x1973ebe6, 0x7d719eba, 0x19a52ceb, 0x7d679642,
+0x19d669fc, 0x7d5d7a74, 0x1a07a311, 0x7d534b50, 0x1a38d823, 0x7d4908d9, 0x1a6a0929, 0x7d3eb30f,
+0x1a9b361d, 0x7d3449f5, 0x1acc5ef6, 0x7d29cd8c, 0x1afd83ad, 0x7d1f3dd6, 0x1b2ea43a, 0x7d149ad5,
+0x1b5fc097, 0x7d09e489, 0x1b90d8bb, 0x7cff1af5, 0x1bc1ec9e, 0x7cf43e1a, 0x1bf2fc3a, 0x7ce94dfb,
+0x1c240786, 0x7cde4a98, 0x1c550e7c, 0x7cd333f3, 0x1c861113, 0x7cc80a0f, 0x1cb70f43, 0x7cbcccec,
+0x1ce80906, 0x7cb17c8d, 0x1d18fe54, 0x7ca618f3, 0x1d49ef26, 0x7c9aa221, 0x1d7adb73, 0x7c8f1817,
+0x1dabc334, 0x7c837ad8, 0x1ddca662, 0x7c77ca65, 0x1e0d84f5, 0x7c6c06c0, 0x1e3e5ee5, 0x7c602fec,
+0x1e6f342c, 0x7c5445e9, 0x1ea004c1, 0x7c4848ba, 0x1ed0d09d, 0x7c3c3860, 0x1f0197b8, 0x7c3014de,
+0x1f325a0b, 0x7c23de35, 0x1f63178f, 0x7c179467, 0x1f93d03c, 0x7c0b3777, 0x1fc4840a, 0x7bfec765,
+0x1ff532f2, 0x7bf24434, 0x2025dcec, 0x7be5ade6, 0x205681f1, 0x7bd9047c, 0x208721f9, 0x7bcc47fa,
+0x20b7bcfe, 0x7bbf7860, 0x20e852f6, 0x7bb295b0, 0x2118e3dc, 0x7ba59fee, 0x21496fa7, 0x7b989719,
+0x2179f64f, 0x7b8b7b36, 0x21aa77cf, 0x7b7e4c45, 0x21daf41d, 0x7b710a49, 0x220b6b32, 0x7b63b543,
+0x223bdd08, 0x7b564d36, 0x226c4996, 0x7b48d225, 0x229cb0d5, 0x7b3b4410, 0x22cd12bd, 0x7b2da2fa,
+0x22fd6f48, 0x7b1feee5, 0x232dc66d, 0x7b1227d3, 0x235e1826, 0x7b044dc7, 0x238e646a, 0x7af660c2,
+0x23beab33, 0x7ae860c7, 0x23eeec78, 0x7ada4dd8, 0x241f2833, 0x7acc27f7, 0x244f5e5c, 0x7abdef25,
+0x247f8eec, 0x7aafa367, 0x24afb9da, 0x7aa144bc, 0x24dfdf20, 0x7a92d329, 0x250ffeb7, 0x7a844eae,
+0x25401896, 0x7a75b74f, 0x25702cb7, 0x7a670d0d, 0x25a03b11, 0x7a584feb, 0x25d0439f, 0x7a497feb,
+0x26004657, 0x7a3a9d0f, 0x26304333, 0x7a2ba75a, 0x26603a2c, 0x7a1c9ece, 0x26902b39, 0x7a0d836d,
+0x26c01655, 0x79fe5539, 0x26effb76, 0x79ef1436, 0x271fda96, 0x79dfc064, 0x274fb3ae, 0x79d059c8,
+0x277f86b5, 0x79c0e062, 0x27af53a6, 0x79b15435, 0x27df1a77, 0x79a1b545, 0x280edb23, 0x79920392,
+0x283e95a1, 0x79823f20, 0x286e49ea, 0x797267f2, 0x289df7f8, 0x79627e08, 0x28cd9fc1, 0x79528167,
+0x28fd4140, 0x79427210, 0x292cdc6d, 0x79325006, 0x295c7140, 0x79221b4b, 0x298bffb2, 0x7911d3e2,
+0x29bb87bc, 0x790179cd, 0x29eb0957, 0x78f10d0f, 0x2a1a847b, 0x78e08dab, 0x2a49f920, 0x78cffba3,
+0x2a796740, 0x78bf56f9, 0x2aa8ced3, 0x78ae9fb0, 0x2ad82fd2, 0x789dd5cb, 0x2b078a36, 0x788cf94c,
+0x2b36ddf7, 0x787c0a36, 0x2b662b0e, 0x786b088c, 0x2b957173, 0x7859f44f, 0x2bc4b120, 0x7848cd83,
+0x2bf3ea0d, 0x7837942b, 0x2c231c33, 0x78264849, 0x2c52478a, 0x7814e9df, 0x2c816c0c, 0x780378f1,
+0x2cb089b1, 0x77f1f581, 0x2cdfa071, 0x77e05f91, 0x2d0eb046, 0x77ceb725, 0x2d3db928, 0x77bcfc3f,
+0x2d6cbb10, 0x77ab2ee2, 0x2d9bb5f6, 0x77994f11, 0x2dcaa9d5, 0x77875cce, 0x2df996a3, 0x7775581d,
+0x2e287c5a, 0x776340ff, 0x2e575af3, 0x77511778, 0x2e863267, 0x773edb8b, 0x2eb502ae, 0x772c8d3a,
+0x2ee3cbc1, 0x771a2c88, 0x2f128d99, 0x7707b979, 0x2f41482e, 0x76f5340e, 0x2f6ffb7a, 0x76e29c4b,
+0x2f9ea775, 0x76cff232, 0x2fcd4c19, 0x76bd35c7, 0x2ffbe95d, 0x76aa670d, 0x302a7f3a, 0x76978605,
+0x30590dab, 0x768492b4, 0x308794a6, 0x76718d1c, 0x30b61426, 0x765e7540, 0x30e48c22, 0x764b4b23,
+0x3112fc95, 0x76380ec8, 0x31416576, 0x7624c031, 0x316fc6be, 0x76115f63, 0x319e2067, 0x75fdec60,
+0x31cc7269, 0x75ea672a, 0x31fabcbd, 0x75d6cfc5, 0x3228ff5c, 0x75c32634, 0x32573a3f, 0x75af6a7b,
+0x32856d5e, 0x759b9c9b, 0x32b398b3, 0x7587bc98, 0x32e1bc36, 0x7573ca75, 0x330fd7e1, 0x755fc635,
+0x333debab, 0x754bafdc, 0x336bf78f, 0x7537876c, 0x3399fb85, 0x75234ce8, 0x33c7f785, 0x750f0054,
+0x33f5eb89, 0x74faa1b3, 0x3423d78a, 0x74e63108, 0x3451bb81, 0x74d1ae55, 0x347f9766, 0x74bd199f,
+0x34ad6b32, 0x74a872e8, 0x34db36df, 0x7493ba34, 0x3508fa66, 0x747eef85, 0x3536b5be, 0x746a12df,
+0x356468e2, 0x74552446, 0x359213c9, 0x744023bc, 0x35bfb66e, 0x742b1144, 0x35ed50c9, 0x7415ece2,
+0x361ae2d3, 0x7400b69a, 0x36486c86, 0x73eb6e6e, 0x3675edd9, 0x73d61461, 0x36a366c6, 0x73c0a878,
+0x36d0d746, 0x73ab2ab4, 0x36fe3f52, 0x73959b1b, 0x372b9ee3, 0x737ff9ae, 0x3758f5f2, 0x736a4671,
+0x37864477, 0x73548168, 0x37b38a6d, 0x733eaa96, 0x37e0c7cc, 0x7328c1ff, 0x380dfc8d, 0x7312c7a5,
+0x383b28a9, 0x72fcbb8c, 0x38684c19, 0x72e69db7, 0x389566d6, 0x72d06e2b, 0x38c278d9, 0x72ba2cea,
+0x38ef821c, 0x72a3d9f7, 0x391c8297, 0x728d7557, 0x39497a43, 0x7276ff0d, 0x39766919, 0x7260771b,
+0x39a34f13, 0x7249dd86, 0x39d02c2a, 0x72333251, 0x39fd0056, 0x721c7580, 0x3a29cb91, 0x7205a716,
+0x3a568dd4, 0x71eec716, 0x3a834717, 0x71d7d585, 0x3aaff755, 0x71c0d265, 0x3adc9e86, 0x71a9bdba,
+0x3b093ca3, 0x71929789, 0x3b35d1a5, 0x717b5fd3, 0x3b625d86, 0x7164169d, 0x3b8ee03e, 0x714cbbeb,
+0x3bbb59c7, 0x71354fc0, 0x3be7ca1a, 0x711dd220, 0x3c143130, 0x7106430e, 0x3c408f03, 0x70eea28e,
+0x3c6ce38a, 0x70d6f0a4, 0x3c992ec0, 0x70bf2d53, 0x3cc5709e, 0x70a7589f, 0x3cf1a91c, 0x708f728b,
+0x3d1dd835, 0x70777b1c, 0x3d49fde1, 0x705f7255, 0x3d761a19, 0x70475839, 0x3da22cd7, 0x702f2ccd,
+0x3dce3614, 0x7016f014, 0x3dfa35c8, 0x6ffea212, 0x3e262bee, 0x6fe642ca, 0x3e52187f, 0x6fcdd241,
+0x3e7dfb73, 0x6fb5507a, 0x3ea9d4c3, 0x6f9cbd79, 0x3ed5a46b, 0x6f841942, 0x3f016a61, 0x6f6b63d8,
+0x3f2d26a0, 0x6f529d40, 0x3f58d921, 0x6f39c57d, 0x3f8481dd, 0x6f20dc92, 0x3fb020ce, 0x6f07e285,
+0x3fdbb5ec, 0x6eeed758, 0x40074132, 0x6ed5bb10, 0x4032c297, 0x6ebc8db0, 0x405e3a16, 0x6ea34f3d,
+0x4089a7a8, 0x6e89ffb9, 0x40b50b46, 0x6e709f2a, 0x40e064ea, 0x6e572d93, 0x410bb48c, 0x6e3daaf8,
+0x4136fa27, 0x6e24175c, 0x416235b2, 0x6e0a72c5, 0x418d6729, 0x6df0bd35, 0x41b88e84, 0x6dd6f6b1,
+0x41e3abbc, 0x6dbd1f3c, 0x420ebecb, 0x6da336dc, 0x4239c7aa, 0x6d893d93, 0x4264c653, 0x6d6f3365,
+0x428fbabe, 0x6d551858, 0x42baa4e6, 0x6d3aec6e, 0x42e584c3, 0x6d20afac, 0x43105a50, 0x6d066215,
+0x433b2585, 0x6cec03af, 0x4365e65b, 0x6cd1947c, 0x43909ccd, 0x6cb71482, 0x43bb48d4, 0x6c9c83c3,
+0x43e5ea68, 0x6c81e245, 0x44108184, 0x6c67300b, 0x443b0e21, 0x6c4c6d1a, 0x44659039, 0x6c319975,
+0x449007c4, 0x6c16b521, 0x44ba74bd, 0x6bfbc021, 0x44e4d71c, 0x6be0ba7b, 0x450f2edb, 0x6bc5a431,
+0x45397bf4, 0x6baa7d49, 0x4563be60, 0x6b8f45c7, 0x458df619, 0x6b73fdae, 0x45b82318, 0x6b58a503,
+0x45e24556, 0x6b3d3bcb, 0x460c5cce, 0x6b21c208, 0x46366978, 0x6b0637c1, 0x46606b4e, 0x6aea9cf8,
+0x468a624a, 0x6acef1b2, 0x46b44e65, 0x6ab335f4, 0x46de2f99, 0x6a9769c1, 0x470805df, 0x6a7b8d1e,
+0x4731d131, 0x6a5fa010, 0x475b9188, 0x6a43a29a, 0x478546de, 0x6a2794c1, 0x47aef12c, 0x6a0b7689,
+0x47d8906d, 0x69ef47f6, 0x48022499, 0x69d3090e, 0x482badab, 0x69b6b9d3, 0x48552b9b, 0x699a5a4c,
+0x487e9e64, 0x697dea7b, 0x48a805ff, 0x69616a65, 0x48d16265, 0x6944da10, 0x48fab391, 0x6928397e,
+0x4923f97b, 0x690b88b5, 0x494d341e, 0x68eec7b9, 0x49766373, 0x68d1f68f, 0x499f8774, 0x68b5153a,
+0x49c8a01b, 0x689823bf, 0x49f1ad61, 0x687b2224, 0x4a1aaf3f, 0x685e106c, 0x4a43a5b0, 0x6840ee9b,
+0x4a6c90ad, 0x6823bcb7, 0x4a957030, 0x68067ac3, 0x4abe4433, 0x67e928c5, 0x4ae70caf, 0x67cbc6c0,
+0x4b0fc99d, 0x67ae54ba, 0x4b387af9, 0x6790d2b6, 0x4b6120bb, 0x677340ba, 0x4b89badd, 0x67559eca,
+0x4bb24958, 0x6737ecea, 0x4bdacc28, 0x671a2b20, 0x4c034345, 0x66fc596f, 0x4c2baea9, 0x66de77dc,
+0x4c540e4e, 0x66c0866d, 0x4c7c622d, 0x66a28524, 0x4ca4aa41, 0x66847408, 0x4ccce684, 0x6666531d,
+0x4cf516ee, 0x66482267, 0x4d1d3b7a, 0x6629e1ec, 0x4d455422, 0x660b91af, 0x4d6d60df, 0x65ed31b5,
+0x4d9561ac, 0x65cec204, 0x4dbd5682, 0x65b0429f, 0x4de53f5a, 0x6591b38c, 0x4e0d1c30, 0x657314cf,
+0x4e34ecfc, 0x6554666d, 0x4e5cb1b9, 0x6535a86b, 0x4e846a60, 0x6516dacd, 0x4eac16eb, 0x64f7fd98,
+0x4ed3b755, 0x64d910d1, 0x4efb4b96, 0x64ba147d, 0x4f22d3aa, 0x649b08a0, 0x4f4a4f89, 0x647bed3f,
+0x4f71bf2e, 0x645cc260, 0x4f992293, 0x643d8806, 0x4fc079b1, 0x641e3e38, 0x4fe7c483, 0x63fee4f8,
+0x500f0302, 0x63df7c4d, 0x50363529, 0x63c0043b, 0x505d5af1, 0x63a07cc7, 0x50847454, 0x6380e5f6,
+0x50ab814d, 0x63613fcd, 0x50d281d5, 0x63418a50, 0x50f975e6, 0x6321c585, 0x51205d7b, 0x6301f171,
+0x5147388c, 0x62e20e17, 0x516e0715, 0x62c21b7e, 0x5194c910, 0x62a219aa, 0x51bb7e75, 0x628208a1,
+0x51e22740, 0x6261e866, 0x5208c36a, 0x6241b8ff, 0x522f52ee, 0x62217a72, 0x5255d5c5, 0x62012cc2,
+0x527c4bea, 0x61e0cff5, 0x52a2b556, 0x61c06410, 0x52c91204, 0x619fe918, 0x52ef61ee, 0x617f5f12,
+0x5315a50e, 0x615ec603, 0x533bdb5d, 0x613e1df0, 0x536204d7, 0x611d66de, 0x53882175, 0x60fca0d2,
+0x53ae3131, 0x60dbcbd1, 0x53d43406, 0x60bae7e1, 0x53fa29ed, 0x6099f505, 0x542012e1, 0x6078f344,
+0x5445eedb, 0x6057e2a2, 0x546bbdd7, 0x6036c325, 0x54917fce, 0x601594d1, 0x54b734ba, 0x5ff457ad,
+0x54dcdc96, 0x5fd30bbc, 0x5502775c, 0x5fb1b104, 0x55280505, 0x5f90478a, 0x554d858d, 0x5f6ecf53,
+0x5572f8ed, 0x5f4d4865, 0x55985f20, 0x5f2bb2c5, 0x55bdb81f, 0x5f0a0e77, 0x55e303e6, 0x5ee85b82,
+0x5608426e, 0x5ec699e9, 0x562d73b2, 0x5ea4c9b3, 0x565297ab, 0x5e82eae5, 0x5677ae54, 0x5e60fd84,
+0x569cb7a8, 0x5e3f0194, 0x56c1b3a1, 0x5e1cf71c, 0x56e6a239, 0x5dfade20, 0x570b8369, 0x5dd8b6a7,
+0x5730572e, 0x5db680b4, 0x57551d80, 0x5d943c4e, 0x5779d65b, 0x5d71e979, 0x579e81b8, 0x5d4f883b,
+0x57c31f92, 0x5d2d189a, 0x57e7afe4, 0x5d0a9a9a, 0x580c32a7, 0x5ce80e41, 0x5830a7d6, 0x5cc57394,
+0x58550f6c, 0x5ca2ca99, 0x58796962, 0x5c801354, 0x589db5b3, 0x5c5d4dcc, 0x58c1f45b, 0x5c3a7a05,
+0x58e62552, 0x5c179806, 0x590a4893, 0x5bf4a7d2, 0x592e5e19, 0x5bd1a971, 0x595265df, 0x5bae9ce7,
+0x59765fde, 0x5b8b8239, 0x599a4c12, 0x5b68596d, 0x59be2a74, 0x5b452288, 0x59e1faff, 0x5b21dd90,
+0x5a05bdae, 0x5afe8a8b, 0x5a29727b, 0x5adb297d, 0x5a4d1960, 0x5ab7ba6c, 0x5a70b258, 0x5a943d5e,
+};
+
+const int32_t kbdWindowOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 128};
+
+const int32_t kbdWindow[128 + 1024] PROGMEM = {
+/* 128 - format = Q31 * 2^0 */
+0x00016f63, 0x7ffffffe, 0x0003e382, 0x7ffffff1, 0x00078f64, 0x7fffffc7, 0x000cc323, 0x7fffff5d,
+0x0013d9ed, 0x7ffffe76, 0x001d3a9d, 0x7ffffcaa, 0x0029581f, 0x7ffff953, 0x0038b1bd, 0x7ffff372,
+0x004bd34d, 0x7fffe98b, 0x00635538, 0x7fffd975, 0x007fdc64, 0x7fffc024, 0x00a219f1, 0x7fff995b,
+0x00cacad0, 0x7fff5f5b, 0x00fab72d, 0x7fff0a75, 0x0132b1af, 0x7ffe9091, 0x01739689, 0x7ffde49e,
+0x01be4a63, 0x7ffcf5ef, 0x0213b910, 0x7ffbaf84, 0x0274d41e, 0x7ff9f73a, 0x02e2913a, 0x7ff7acf1,
+0x035de86c, 0x7ff4a99a, 0x03e7d233, 0x7ff0be3d, 0x0481457c, 0x7febb2f1, 0x052b357c, 0x7fe545d4,
+0x05e68f77, 0x7fdd2a02, 0x06b4386f, 0x7fd30695, 0x07950acb, 0x7fc675b4, 0x0889d3ef, 0x7fb703be,
+0x099351e0, 0x7fa42e89, 0x0ab230e0, 0x7f8d64d8, 0x0be70923, 0x7f7205f8, 0x0d325c93, 0x7f516195,
+0x0e9494ae, 0x7f2ab7d0, 0x100e0085, 0x7efd3997, 0x119ed2ef, 0x7ec8094a, 0x134720d8, 0x7e8a3ba7,
+0x1506dfdc, 0x7e42d906, 0x16dde50b, 0x7df0dee4, 0x18cbe3f7, 0x7d9341b4, 0x1ad06e07, 0x7d28ef02,
+0x1ceaf215, 0x7cb0cfcc, 0x1f1abc4f, 0x7c29cb20, 0x215ef677, 0x7b92c8eb, 0x23b6a867, 0x7aeab4ec,
+0x2620b8ec, 0x7a3081d0, 0x289beef5, 0x79632c5a, 0x2b26f30b, 0x7881be95, 0x2dc0511f, 0x778b5304,
+0x30667aa2, 0x767f17c0, 0x3317c8dd, 0x755c5178, 0x35d27f98, 0x74225e50, 0x3894cff3, 0x72d0b887,
+0x3b5cdb7b, 0x7166f8e7, 0x3e28b770, 0x6fe4d8e8, 0x40f6702a, 0x6e4a3491, 0x43c40caa, 0x6c970bfc,
+0x468f9231, 0x6acb8483, 0x495707f5, 0x68e7e994, 0x4c187ac7, 0x66ecad1c, 0x4ed200c5, 0x64da6797,
+0x5181bcea, 0x62b1d7b7, 0x5425e28e, 0x6073e1ae, 0x56bcb8c2, 0x5e218e16, 0x59449d76, 0x5bbc0875,
+/* 1024 - format = Q31 * 2^0 */
+0x0009962f, 0x7fffffa4, 0x000e16fb, 0x7fffff39, 0x0011ea65, 0x7ffffebf, 0x0015750e, 0x7ffffe34,
+0x0018dc74, 0x7ffffd96, 0x001c332e, 0x7ffffce5, 0x001f83f5, 0x7ffffc1f, 0x0022d59a, 0x7ffffb43,
+0x00262cc2, 0x7ffffa4f, 0x00298cc4, 0x7ffff942, 0x002cf81f, 0x7ffff81a, 0x003070c4, 0x7ffff6d6,
+0x0033f840, 0x7ffff573, 0x00378fd9, 0x7ffff3f1, 0x003b38a1, 0x7ffff24d, 0x003ef381, 0x7ffff085,
+0x0042c147, 0x7fffee98, 0x0046a2a8, 0x7fffec83, 0x004a9847, 0x7fffea44, 0x004ea2b7, 0x7fffe7d8,
+0x0052c283, 0x7fffe53f, 0x0056f829, 0x7fffe274, 0x005b4422, 0x7fffdf76, 0x005fa6dd, 0x7fffdc43,
+0x006420c8, 0x7fffd8d6, 0x0068b249, 0x7fffd52f, 0x006d5bc4, 0x7fffd149, 0x00721d9a, 0x7fffcd22,
+0x0076f828, 0x7fffc8b6, 0x007bebca, 0x7fffc404, 0x0080f8d9, 0x7fffbf06, 0x00861fae, 0x7fffb9bb,
+0x008b609e, 0x7fffb41e, 0x0090bbff, 0x7fffae2c, 0x00963224, 0x7fffa7e1, 0x009bc362, 0x7fffa13a,
+0x00a17009, 0x7fff9a32, 0x00a7386c, 0x7fff92c5, 0x00ad1cdc, 0x7fff8af0, 0x00b31da8, 0x7fff82ad,
+0x00b93b21, 0x7fff79f9, 0x00bf7596, 0x7fff70cf, 0x00c5cd57, 0x7fff672a, 0x00cc42b1, 0x7fff5d05,
+0x00d2d5f3, 0x7fff525c, 0x00d9876c, 0x7fff4729, 0x00e05769, 0x7fff3b66, 0x00e74638, 0x7fff2f10,
+0x00ee5426, 0x7fff221f, 0x00f58182, 0x7fff148e, 0x00fcce97, 0x7fff0658, 0x01043bb3, 0x7ffef776,
+0x010bc923, 0x7ffee7e2, 0x01137733, 0x7ffed795, 0x011b4631, 0x7ffec68a, 0x01233669, 0x7ffeb4ba,
+0x012b4827, 0x7ffea21d, 0x01337bb8, 0x7ffe8eac, 0x013bd167, 0x7ffe7a61, 0x01444982, 0x7ffe6533,
+0x014ce454, 0x7ffe4f1c, 0x0155a229, 0x7ffe3813, 0x015e834d, 0x7ffe2011, 0x0167880c, 0x7ffe070d,
+0x0170b0b2, 0x7ffdecff, 0x0179fd8b, 0x7ffdd1df, 0x01836ee1, 0x7ffdb5a2, 0x018d0500, 0x7ffd9842,
+0x0196c035, 0x7ffd79b3, 0x01a0a0ca, 0x7ffd59ee, 0x01aaa70a, 0x7ffd38e8, 0x01b4d341, 0x7ffd1697,
+0x01bf25b9, 0x7ffcf2f2, 0x01c99ebd, 0x7ffccdee, 0x01d43e99, 0x7ffca780, 0x01df0597, 0x7ffc7f9e,
+0x01e9f401, 0x7ffc563d, 0x01f50a22, 0x7ffc2b51, 0x02004844, 0x7ffbfecf, 0x020baeb1, 0x7ffbd0ab,
+0x02173db4, 0x7ffba0da, 0x0222f596, 0x7ffb6f4f, 0x022ed6a1, 0x7ffb3bfd, 0x023ae11f, 0x7ffb06d8,
+0x02471558, 0x7ffacfd3, 0x02537397, 0x7ffa96e0, 0x025ffc25, 0x7ffa5bf2, 0x026caf4a, 0x7ffa1efc,
+0x02798d4f, 0x7ff9dfee, 0x0286967c, 0x7ff99ebb, 0x0293cb1b, 0x7ff95b55, 0x02a12b72, 0x7ff915ab,
+0x02aeb7cb, 0x7ff8cdaf, 0x02bc706d, 0x7ff88351, 0x02ca559f, 0x7ff83682, 0x02d867a9, 0x7ff7e731,
+0x02e6a6d2, 0x7ff7954e, 0x02f51361, 0x7ff740c8, 0x0303ad9c, 0x7ff6e98e, 0x031275ca, 0x7ff68f8f,
+0x03216c30, 0x7ff632ba, 0x03309116, 0x7ff5d2fb, 0x033fe4bf, 0x7ff57042, 0x034f6773, 0x7ff50a7a,
+0x035f1975, 0x7ff4a192, 0x036efb0a, 0x7ff43576, 0x037f0c78, 0x7ff3c612, 0x038f4e02, 0x7ff35353,
+0x039fbfeb, 0x7ff2dd24, 0x03b06279, 0x7ff26370, 0x03c135ed, 0x7ff1e623, 0x03d23a8b, 0x7ff16527,
+0x03e37095, 0x7ff0e067, 0x03f4d84e, 0x7ff057cc, 0x040671f7, 0x7fefcb40, 0x04183dd3, 0x7fef3aad,
+0x042a3c22, 0x7feea5fa, 0x043c6d25, 0x7fee0d11, 0x044ed11d, 0x7fed6fda, 0x04616849, 0x7fecce3d,
+0x047432eb, 0x7fec2821, 0x04873140, 0x7feb7d6c, 0x049a6388, 0x7feace07, 0x04adca01, 0x7fea19d6,
+0x04c164ea, 0x7fe960c0, 0x04d53481, 0x7fe8a2aa, 0x04e93902, 0x7fe7df79, 0x04fd72aa, 0x7fe71712,
+0x0511e1b6, 0x7fe6495a, 0x05268663, 0x7fe57634, 0x053b60eb, 0x7fe49d83, 0x05507189, 0x7fe3bf2b,
+0x0565b879, 0x7fe2db0f, 0x057b35f4, 0x7fe1f110, 0x0590ea35, 0x7fe10111, 0x05a6d574, 0x7fe00af3,
+0x05bcf7ea, 0x7fdf0e97, 0x05d351cf, 0x7fde0bdd, 0x05e9e35c, 0x7fdd02a6, 0x0600acc8, 0x7fdbf2d2,
+0x0617ae48, 0x7fdadc40, 0x062ee814, 0x7fd9becf, 0x06465a62, 0x7fd89a5e, 0x065e0565, 0x7fd76eca,
+0x0675e954, 0x7fd63bf1, 0x068e0662, 0x7fd501b0, 0x06a65cc3, 0x7fd3bfe4, 0x06beecaa, 0x7fd2766a,
+0x06d7b648, 0x7fd1251e, 0x06f0b9d1, 0x7fcfcbda, 0x0709f775, 0x7fce6a7a, 0x07236f65, 0x7fcd00d8,
+0x073d21d2, 0x7fcb8ecf, 0x07570eea, 0x7fca1439, 0x077136dd, 0x7fc890ed, 0x078b99da, 0x7fc704c7,
+0x07a6380d, 0x7fc56f9d, 0x07c111a4, 0x7fc3d147, 0x07dc26cc, 0x7fc2299e, 0x07f777b1, 0x7fc07878,
+0x0813047d, 0x7fbebdac, 0x082ecd5b, 0x7fbcf90f, 0x084ad276, 0x7fbb2a78, 0x086713f7, 0x7fb951bc,
+0x08839206, 0x7fb76eaf, 0x08a04ccb, 0x7fb58126, 0x08bd446e, 0x7fb388f4, 0x08da7915, 0x7fb185ee,
+0x08f7eae7, 0x7faf77e5, 0x09159a09, 0x7fad5ead, 0x0933869f, 0x7fab3a17, 0x0951b0cd, 0x7fa909f6,
+0x097018b7, 0x7fa6ce1a, 0x098ebe7f, 0x7fa48653, 0x09ada248, 0x7fa23273, 0x09ccc431, 0x7f9fd249,
+0x09ec245b, 0x7f9d65a4, 0x0a0bc2e7, 0x7f9aec53, 0x0a2b9ff3, 0x7f986625, 0x0a4bbb9e, 0x7f95d2e7,
+0x0a6c1604, 0x7f933267, 0x0a8caf43, 0x7f908472, 0x0aad8776, 0x7f8dc8d5, 0x0ace9eb9, 0x7f8aff5c,
+0x0aeff526, 0x7f8827d3, 0x0b118ad8, 0x7f854204, 0x0b335fe6, 0x7f824dbb, 0x0b557469, 0x7f7f4ac3,
+0x0b77c879, 0x7f7c38e4, 0x0b9a5c2b, 0x7f7917e9, 0x0bbd2f97, 0x7f75e79b, 0x0be042d0, 0x7f72a7c3,
+0x0c0395ec, 0x7f6f5828, 0x0c2728fd, 0x7f6bf892, 0x0c4afc16, 0x7f6888c9, 0x0c6f0f4a, 0x7f650894,
+0x0c9362a8, 0x7f6177b9, 0x0cb7f642, 0x7f5dd5ff, 0x0cdcca26, 0x7f5a232a, 0x0d01de63, 0x7f565f00,
+0x0d273307, 0x7f528947, 0x0d4cc81f, 0x7f4ea1c2, 0x0d729db7, 0x7f4aa835, 0x0d98b3da, 0x7f469c65,
+0x0dbf0a92, 0x7f427e13, 0x0de5a1e9, 0x7f3e4d04, 0x0e0c79e7, 0x7f3a08f9, 0x0e339295, 0x7f35b1b4,
+0x0e5aebfa, 0x7f3146f8, 0x0e82861a, 0x7f2cc884, 0x0eaa60fd, 0x7f28361b, 0x0ed27ca5, 0x7f238f7c,
+0x0efad917, 0x7f1ed467, 0x0f237656, 0x7f1a049d, 0x0f4c5462, 0x7f151fdc, 0x0f75733d, 0x7f1025e3,
+0x0f9ed2e6, 0x7f0b1672, 0x0fc8735e, 0x7f05f146, 0x0ff254a1, 0x7f00b61d, 0x101c76ae, 0x7efb64b4,
+0x1046d981, 0x7ef5fcca, 0x10717d15, 0x7ef07e19, 0x109c6165, 0x7eeae860, 0x10c7866a, 0x7ee53b5b,
+0x10f2ec1e, 0x7edf76c4, 0x111e9279, 0x7ed99a58, 0x114a7971, 0x7ed3a5d1, 0x1176a0fc, 0x7ecd98eb,
+0x11a30910, 0x7ec77360, 0x11cfb1a1, 0x7ec134eb, 0x11fc9aa2, 0x7ebadd44, 0x1229c406, 0x7eb46c27,
+0x12572dbf, 0x7eade14c, 0x1284d7bc, 0x7ea73c6c, 0x12b2c1ed, 0x7ea07d41, 0x12e0ec42, 0x7e99a382,
+0x130f56a8, 0x7e92aee7, 0x133e010b, 0x7e8b9f2a, 0x136ceb59, 0x7e847402, 0x139c157b, 0x7e7d2d25,
+0x13cb7f5d, 0x7e75ca4c, 0x13fb28e6, 0x7e6e4b2d, 0x142b1200, 0x7e66af7f, 0x145b3a92, 0x7e5ef6f8,
+0x148ba281, 0x7e572150, 0x14bc49b4, 0x7e4f2e3b, 0x14ed300f, 0x7e471d70, 0x151e5575, 0x7e3eeea5,
+0x154fb9c9, 0x7e36a18e, 0x15815ced, 0x7e2e35e2, 0x15b33ec1, 0x7e25ab56, 0x15e55f25, 0x7e1d019e,
+0x1617bdf9, 0x7e14386e, 0x164a5b19, 0x7e0b4f7d, 0x167d3662, 0x7e02467e, 0x16b04fb2, 0x7df91d25,
+0x16e3a6e2, 0x7defd327, 0x17173bce, 0x7de66837, 0x174b0e4d, 0x7ddcdc0a, 0x177f1e39, 0x7dd32e53,
+0x17b36b69, 0x7dc95ec6, 0x17e7f5b3, 0x7dbf6d17, 0x181cbcec, 0x7db558f9, 0x1851c0e9, 0x7dab221f,
+0x1887017d, 0x7da0c83c, 0x18bc7e7c, 0x7d964b05, 0x18f237b6, 0x7d8baa2b, 0x19282cfd, 0x7d80e563,
+0x195e5e20, 0x7d75fc5e, 0x1994caee, 0x7d6aeed0, 0x19cb7335, 0x7d5fbc6d, 0x1a0256c2, 0x7d5464e6,
+0x1a397561, 0x7d48e7ef, 0x1a70cede, 0x7d3d453b, 0x1aa86301, 0x7d317c7c, 0x1ae03195, 0x7d258d65,
+0x1b183a63, 0x7d1977aa, 0x1b507d30, 0x7d0d3afc, 0x1b88f9c5, 0x7d00d710, 0x1bc1afe6, 0x7cf44b97,
+0x1bfa9f58, 0x7ce79846, 0x1c33c7e0, 0x7cdabcce, 0x1c6d293f, 0x7ccdb8e4, 0x1ca6c337, 0x7cc08c39,
+0x1ce0958a, 0x7cb33682, 0x1d1a9ff8, 0x7ca5b772, 0x1d54e240, 0x7c980ebd, 0x1d8f5c21, 0x7c8a3c14,
+0x1dca0d56, 0x7c7c3f2e, 0x1e04f59f, 0x7c6e17bc, 0x1e4014b4, 0x7c5fc573, 0x1e7b6a53, 0x7c514807,
+0x1eb6f633, 0x7c429f2c, 0x1ef2b80f, 0x7c33ca96, 0x1f2eaf9e, 0x7c24c9fa, 0x1f6adc98, 0x7c159d0d,
+0x1fa73eb2, 0x7c064383, 0x1fe3d5a3, 0x7bf6bd11, 0x2020a11e, 0x7be7096c, 0x205da0d8, 0x7bd7284a,
+0x209ad483, 0x7bc71960, 0x20d83bd1, 0x7bb6dc65, 0x2115d674, 0x7ba6710d, 0x2153a41b, 0x7b95d710,
+0x2191a476, 0x7b850e24, 0x21cfd734, 0x7b7415ff, 0x220e3c02, 0x7b62ee59, 0x224cd28d, 0x7b5196e9,
+0x228b9a82, 0x7b400f67, 0x22ca938a, 0x7b2e578a, 0x2309bd52, 0x7b1c6f0b, 0x23491783, 0x7b0a55a1,
+0x2388a1c4, 0x7af80b07, 0x23c85bbf, 0x7ae58ef5, 0x2408451a, 0x7ad2e124, 0x24485d7c, 0x7ac0014e,
+0x2488a48a, 0x7aacef2e, 0x24c919e9, 0x7a99aa7e, 0x2509bd3d, 0x7a8632f8, 0x254a8e29, 0x7a728858,
+0x258b8c50, 0x7a5eaa5a, 0x25ccb753, 0x7a4a98b9, 0x260e0ed3, 0x7a365333, 0x264f9271, 0x7a21d983,
+0x269141cb, 0x7a0d2b68, 0x26d31c80, 0x79f8489e, 0x2715222f, 0x79e330e4, 0x27575273, 0x79cde3f8,
+0x2799acea, 0x79b8619a, 0x27dc3130, 0x79a2a989, 0x281ededf, 0x798cbb85, 0x2861b591, 0x7976974e,
+0x28a4b4e0, 0x79603ca5, 0x28e7dc65, 0x7949ab4c, 0x292b2bb8, 0x7932e304, 0x296ea270, 0x791be390,
+0x29b24024, 0x7904acb3, 0x29f6046b, 0x78ed3e30, 0x2a39eed8, 0x78d597cc, 0x2a7dff02, 0x78bdb94a,
+0x2ac2347c, 0x78a5a270, 0x2b068eda, 0x788d5304, 0x2b4b0dae, 0x7874cacb, 0x2b8fb08a, 0x785c098d,
+0x2bd47700, 0x78430f11, 0x2c1960a1, 0x7829db1f, 0x2c5e6cfd, 0x78106d7f, 0x2ca39ba3, 0x77f6c5fb,
+0x2ce8ec23, 0x77dce45c, 0x2d2e5e0b, 0x77c2c86e, 0x2d73f0e8, 0x77a871fa, 0x2db9a449, 0x778de0cd,
+0x2dff77b8, 0x777314b2, 0x2e456ac4, 0x77580d78, 0x2e8b7cf6, 0x773ccaeb, 0x2ed1addb, 0x77214cdb,
+0x2f17fcfb, 0x77059315, 0x2f5e69e2, 0x76e99d69, 0x2fa4f419, 0x76cd6ba9, 0x2feb9b27, 0x76b0fda4,
+0x30325e96, 0x7694532e, 0x30793dee, 0x76776c17, 0x30c038b5, 0x765a4834, 0x31074e72, 0x763ce759,
+0x314e7eab, 0x761f4959, 0x3195c8e6, 0x76016e0b, 0x31dd2ca9, 0x75e35545, 0x3224a979, 0x75c4fedc,
+0x326c3ed8, 0x75a66aab, 0x32b3ec4d, 0x75879887, 0x32fbb159, 0x7568884b, 0x33438d81, 0x754939d1,
+0x338b8045, 0x7529acf4, 0x33d3892a, 0x7509e18e, 0x341ba7b1, 0x74e9d77d, 0x3463db5a, 0x74c98e9e,
+0x34ac23a7, 0x74a906cd, 0x34f48019, 0x74883fec, 0x353cf02f, 0x746739d8, 0x3585736a, 0x7445f472,
+0x35ce0949, 0x74246f9c, 0x3616b14c, 0x7402ab37, 0x365f6af0, 0x73e0a727, 0x36a835b5, 0x73be6350,
+0x36f11118, 0x739bdf95, 0x3739fc98, 0x73791bdd, 0x3782f7b2, 0x7356180e, 0x37cc01e3, 0x7332d410,
+0x38151aa8, 0x730f4fc9, 0x385e417e, 0x72eb8b24, 0x38a775e1, 0x72c7860a, 0x38f0b74d, 0x72a34066,
+0x393a053e, 0x727eba24, 0x39835f30, 0x7259f331, 0x39ccc49e, 0x7234eb79, 0x3a163503, 0x720fa2eb,
+0x3a5fafda, 0x71ea1977, 0x3aa9349e, 0x71c44f0c, 0x3af2c2ca, 0x719e439d, 0x3b3c59d7, 0x7177f71a,
+0x3b85f940, 0x71516978, 0x3bcfa07e, 0x712a9aaa, 0x3c194f0d, 0x71038aa4, 0x3c630464, 0x70dc395e,
+0x3cacbfff, 0x70b4a6cd, 0x3cf68155, 0x708cd2e9, 0x3d4047e1, 0x7064bdab, 0x3d8a131c, 0x703c670d,
+0x3dd3e27e, 0x7013cf0a, 0x3e1db580, 0x6feaf59c, 0x3e678b9b, 0x6fc1dac1, 0x3eb16449, 0x6f987e76,
+0x3efb3f01, 0x6f6ee0b9, 0x3f451b3d, 0x6f45018b, 0x3f8ef874, 0x6f1ae0eb, 0x3fd8d620, 0x6ef07edb,
+0x4022b3b9, 0x6ec5db5d, 0x406c90b7, 0x6e9af675, 0x40b66c93, 0x6e6fd027, 0x410046c5, 0x6e446879,
+0x414a1ec6, 0x6e18bf71, 0x4193f40d, 0x6decd517, 0x41ddc615, 0x6dc0a972, 0x42279455, 0x6d943c8d,
+0x42715e45, 0x6d678e71, 0x42bb235f, 0x6d3a9f2a, 0x4304e31a, 0x6d0d6ec5, 0x434e9cf1, 0x6cdffd4f,
+0x4398505b, 0x6cb24ad6, 0x43e1fcd1, 0x6c84576b, 0x442ba1cd, 0x6c56231c, 0x44753ec7, 0x6c27adfd,
+0x44bed33a, 0x6bf8f81e, 0x45085e9d, 0x6bca0195, 0x4551e06b, 0x6b9aca75, 0x459b581e, 0x6b6b52d5,
+0x45e4c52f, 0x6b3b9ac9, 0x462e2717, 0x6b0ba26b, 0x46777d52, 0x6adb69d3, 0x46c0c75a, 0x6aaaf11b,
+0x470a04a9, 0x6a7a385c, 0x475334b9, 0x6a493fb3, 0x479c5707, 0x6a18073d, 0x47e56b0c, 0x69e68f17,
+0x482e7045, 0x69b4d761, 0x4877662c, 0x6982e039, 0x48c04c3f, 0x6950a9c0, 0x490921f8, 0x691e341a,
+0x4951e6d5, 0x68eb7f67, 0x499a9a51, 0x68b88bcd, 0x49e33beb, 0x68855970, 0x4a2bcb1f, 0x6851e875,
+0x4a74476b, 0x681e3905, 0x4abcb04c, 0x67ea4b47, 0x4b050541, 0x67b61f63, 0x4b4d45c9, 0x6781b585,
+0x4b957162, 0x674d0dd6, 0x4bdd878c, 0x67182883, 0x4c2587c6, 0x66e305b8, 0x4c6d7190, 0x66ada5a5,
+0x4cb5446a, 0x66780878, 0x4cfcffd5, 0x66422e60, 0x4d44a353, 0x660c1790, 0x4d8c2e64, 0x65d5c439,
+0x4dd3a08c, 0x659f348e, 0x4e1af94b, 0x656868c3, 0x4e623825, 0x6531610d, 0x4ea95c9d, 0x64fa1da3,
+0x4ef06637, 0x64c29ebb, 0x4f375477, 0x648ae48d, 0x4f7e26e1, 0x6452ef53, 0x4fc4dcfb, 0x641abf46,
+0x500b7649, 0x63e254a2, 0x5051f253, 0x63a9afa2, 0x5098509f, 0x6370d083, 0x50de90b3, 0x6337b784,
+0x5124b218, 0x62fe64e3, 0x516ab455, 0x62c4d8e0, 0x51b096f3, 0x628b13bc, 0x51f6597b, 0x625115b8,
+0x523bfb78, 0x6216df18, 0x52817c72, 0x61dc701f, 0x52c6dbf5, 0x61a1c912, 0x530c198d, 0x6166ea36,
+0x535134c5, 0x612bd3d2, 0x53962d2a, 0x60f0862d, 0x53db024a, 0x60b50190, 0x541fb3b1, 0x60794644,
+0x546440ef, 0x603d5494, 0x54a8a992, 0x60012cca, 0x54eced2b, 0x5fc4cf33, 0x55310b48, 0x5f883c1c,
+0x5575037c, 0x5f4b73d2, 0x55b8d558, 0x5f0e76a5, 0x55fc806f, 0x5ed144e5, 0x56400452, 0x5e93dee1,
+0x56836096, 0x5e5644ec, 0x56c694cf, 0x5e187757, 0x5709a092, 0x5dda7677, 0x574c8374, 0x5d9c429f,
+0x578f3d0d, 0x5d5ddc24, 0x57d1ccf2, 0x5d1f435d, 0x581432bd, 0x5ce078a0, 0x58566e04, 0x5ca17c45,
+0x58987e63, 0x5c624ea4, 0x58da6372, 0x5c22f016, 0x591c1ccc, 0x5be360f6, 0x595daa0d, 0x5ba3a19f,
+0x599f0ad1, 0x5b63b26c, 0x59e03eb6, 0x5b2393ba, 0x5a214558, 0x5ae345e7, 0x5a621e56, 0x5aa2c951,
+};
+/* bit reverse tables for FFT */
+const uint8_t bitrevtabOffset[NUM_IMDCT_SIZES] PROGMEM = {0, 17};
+const uint8_t bitrevtab[17 + 129] PROGMEM = {
+/* nfft = 64 */
+0x01, 0x08, 0x02, 0x04, 0x03, 0x0c, 0x05, 0x0a, 0x07, 0x0e, 0x0b, 0x0d, 0x00, 0x06, 0x09, 0x0f,
+0x00,
+/* nfft = 512 */
+0x01, 0x40, 0x02, 0x20, 0x03, 0x60, 0x04, 0x10, 0x05, 0x50, 0x06, 0x30, 0x07, 0x70, 0x09, 0x48,
+0x0a, 0x28, 0x0b, 0x68, 0x0c, 0x18, 0x0d, 0x58, 0x0e, 0x38, 0x0f, 0x78, 0x11, 0x44, 0x12, 0x24,
+0x13, 0x64, 0x15, 0x54, 0x16, 0x34, 0x17, 0x74, 0x19, 0x4c, 0x1a, 0x2c, 0x1b, 0x6c, 0x1d, 0x5c,
+0x1e, 0x3c, 0x1f, 0x7c, 0x21, 0x42, 0x23, 0x62, 0x25, 0x52, 0x26, 0x32, 0x27, 0x72, 0x29, 0x4a,
+0x2b, 0x6a, 0x2d, 0x5a, 0x2e, 0x3a, 0x2f, 0x7a, 0x31, 0x46, 0x33, 0x66, 0x35, 0x56, 0x37, 0x76,
+0x39, 0x4e, 0x3b, 0x6e, 0x3d, 0x5e, 0x3f, 0x7e, 0x43, 0x61, 0x45, 0x51, 0x47, 0x71, 0x4b, 0x69,
+0x4d, 0x59, 0x4f, 0x79, 0x53, 0x65, 0x57, 0x75, 0x5b, 0x6d, 0x5f, 0x7d, 0x67, 0x73, 0x6f, 0x7b,
+0x00, 0x08, 0x14, 0x1c, 0x22, 0x2a, 0x36, 0x3e, 0x41, 0x49, 0x55, 0x5d, 0x63, 0x6b, 0x77, 0x7f,
+0x00,
+};
+
+const uint8_t uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f};
+
+const uint32_t twidTabOdd[8*6 + 32*6 + 128*6] PROGMEM = {
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x539eba45, 0xe7821d59,
+ 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59,
+ 0x539eba45, 0xc4df2862, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060,
+ 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x22a2f4f8, 0xc4df2862,
+ 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862,
+ 0xac6145bb, 0x187de2a7, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52,
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x45f704f7, 0xf9ba1651,
+ 0x43103085, 0xfcdc1342, 0x48b2b335, 0xf69bf7c9, 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651,
+ 0x4fd288dc, 0xed6bf9d1, 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, 0x553805f2, 0xe4a2eff6,
+ 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x569cc31b, 0xe1d4a2c8,
+ 0x4da1fab5, 0xf0730342, 0x5a6690ae, 0xd5052d97, 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1,
+ 0x5a12e720, 0xce86ff2a, 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, 0x57cc15bc, 0xc91af976,
+ 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a12e720, 0xce86ff2a,
+ 0x553805f2, 0xe4a2eff6, 0x4da1fab5, 0xc1eb0209, 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8,
+ 0x45f704f7, 0xc04ee4b8, 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, 0x3cc85709, 0xc013bc39,
+ 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x4fd288dc, 0xc2c17d52,
+ 0x5987b08a, 0xd9e01006, 0x26b2a794, 0xc3bdbdf6, 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6,
+ 0x1a4608ab, 0xc78e9a1d, 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, 0x0d47d096, 0xcc983f70,
+ 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x396b3199, 0xc04ee4b8,
+ 0x5a6690ae, 0xd09441bb, 0xf2b82f6a, 0xd9e01006, 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a,
+ 0xe5b9f755, 0xe1d4a2c8, 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, 0xd94d586c, 0xea70658a,
+ 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x1a4608ab, 0xc78e9a1d,
+ 0x57cc15bc, 0xc91af976, 0xc337a8f7, 0xfcdc1342, 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d,
+ 0xba08fb09, 0x0645e9af, 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, 0xb25e054b, 0x0f8cfcbe,
+ 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xf720e574, 0xd76619b6,
+ 0x51d1dc80, 0xc3bdbdf6, 0xa833ea44, 0x20e70f32, 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52,
+ 0xa5ed18e0, 0x2899e64a, 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, 0xa5996f52, 0x2f6bbe45,
+ 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xd5558381, 0xed6bf9d1,
+ 0x48b2b335, 0xc0b15502, 0xaac7fa0e, 0x39daf5e8, 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8,
+ 0xb02d7724, 0x3d3e82ae, 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, 0xb74d4ccb, 0x3f4eaafe,
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x418d2621, 0xfe6deaa1,
+ 0x40c7d2bd, 0xff36f170, 0x424ff28f, 0xfda4f351, 0x43103085, 0xfcdc1342, 0x418d2621, 0xfe6deaa1,
+ 0x4488e37f, 0xfb4ab7db, 0x4488e37f, 0xfb4ab7db, 0x424ff28f, 0xfda4f351, 0x46aa0d6d, 0xf8f21e8e,
+ 0x45f704f7, 0xf9ba1651, 0x43103085, 0xfcdc1342, 0x48b2b335, 0xf69bf7c9, 0x475a5c77, 0xf82a6c6a,
+ 0x43cdd89a, 0xfc135231, 0x4aa22036, 0xf4491311, 0x48b2b335, 0xf69bf7c9, 0x4488e37f, 0xfb4ab7db,
+ 0x4c77a88e, 0xf1fa3ecb, 0x49ffd417, 0xf50ef5de, 0x454149fc, 0xfa824bfd, 0x4e32a956, 0xefb047f2,
+ 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x4c77a88e, 0xf1fa3ecb,
+ 0x46aa0d6d, 0xf8f21e8e, 0x5156b6d9, 0xeb2e1dbe, 0x4da1fab5, 0xf0730342, 0x475a5c77, 0xf82a6c6a,
+ 0x52beac9f, 0xe8f77acf, 0x4ec05432, 0xeeee2d9d, 0x4807eb4b, 0xf7630799, 0x5409ed4b, 0xe6c8d59c,
+ 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9, 0x553805f2, 0xe4a2eff6, 0x50d86e6d, 0xebeca36c,
+ 0x495aada2, 0xf5d544a7, 0x56488dc5, 0xe28688a4, 0x51d1dc80, 0xea70658a, 0x49ffd417, 0xf50ef5de,
+ 0x573b2635, 0xe0745b24, 0x52beac9f, 0xe8f77acf, 0x4aa22036, 0xf4491311, 0x580f7b19, 0xde6d1f65,
+ 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5471e2e6, 0xe61086bc,
+ 0x4bde1089, 0xf2beafed, 0x595c3e2a, 0xda8249b4, 0x553805f2, 0xe4a2eff6, 0x4c77a88e, 0xf1fa3ecb,
+ 0x59d438e5, 0xd8a00bae, 0x55f104dc, 0xe3399167, 0x4d0e4de2, 0xf136580d, 0x5a2d0957, 0xd6cb76c9,
+ 0x569cc31b, 0xe1d4a2c8, 0x4da1fab5, 0xf0730342, 0x5a6690ae, 0xd5052d97, 0x573b2635, 0xe0745b24,
+ 0x4e32a956, 0xefb047f2, 0x5a80baf6, 0xd34dcdb4, 0x57cc15bc, 0xdf18f0ce, 0x4ec05432, 0xeeee2d9d,
+ 0x5a7b7f1a, 0xd1a5ef90, 0x584f7b58, 0xddc29958, 0x4f4af5d1, 0xee2cbbc1, 0x5a56deec, 0xd00e2639,
+ 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, 0x592d59da, 0xdb25f566,
+ 0x50570819, 0xecabef3d, 0x59afaf4c, 0xcd110216, 0x5987b08a, 0xd9e01006, 0x50d86e6d, 0xebeca36c,
+ 0x592d59da, 0xcbacb0bf, 0x59d438e5, 0xd8a00bae, 0x5156b6d9, 0xeb2e1dbe, 0x588c1404, 0xca5a86c4,
+ 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a, 0x57cc15bc, 0xc91af976, 0x5a43b190, 0xd6326a88,
+ 0x5249daa2, 0xe9b38223, 0x56eda1a0, 0xc7ee77b3, 0x5a6690ae, 0xd5052d97, 0x52beac9f, 0xe8f77acf,
+ 0x55f104dc, 0xc6d569be, 0x5a7b7f1a, 0xd3de9156, 0x53304df6, 0xe83c56cf, 0x54d69714, 0xc5d03118,
+ 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a7b7f1a, 0xd1a5ef90,
+ 0x5409ed4b, 0xe6c8d59c, 0x5249daa2, 0xc402a33c, 0x5a6690ae, 0xd09441bb, 0x5471e2e6, 0xe61086bc,
+ 0x50d86e6d, 0xc33aee27, 0x5a43b190, 0xcf89e3e8, 0x54d69714, 0xe55937d5, 0x4f4af5d1, 0xc2884e6e,
+ 0x5a12e720, 0xce86ff2a, 0x553805f2, 0xe4a2eff6, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xcd8bbb6d,
+ 0x55962bc0, 0xe3edb628, 0x4bde1089, 0xc1633f8a, 0x5987b08a, 0xcc983f70, 0x55f104dc, 0xe3399167,
+ 0x49ffd417, 0xc0f1360b, 0x592d59da, 0xcbacb0bf, 0x56488dc5, 0xe28688a4, 0x4807eb4b, 0xc0950d1d,
+ 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x584f7b58, 0xc9edeb50,
+ 0x56eda1a0, 0xe123e6ad, 0x43cdd89a, 0xc01ed535, 0x57cc15bc, 0xc91af976, 0x573b2635, 0xe0745b24,
+ 0x418d2621, 0xc004ef3f, 0x573b2635, 0xc8507ea7, 0x57854ddd, 0xdfc606f1, 0x3f35b59d, 0xc0013bd3,
+ 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce, 0x3cc85709, 0xc013bc39, 0x55f104dc, 0xc6d569be,
+ 0x580f7b19, 0xde6d1f65, 0x3a45e1f7, 0xc03c6a07, 0x553805f2, 0xc6250a18, 0x584f7b58, 0xddc29958,
+ 0x37af354c, 0xc07b371e, 0x5471e2e6, 0xc57d965d, 0x588c1404, 0xdd196538, 0x350536f1, 0xc0d00db6,
+ 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x52beac9f, 0xc449d892,
+ 0x58fb0568, 0xdbcb0cce, 0x2f7afdfc, 0xc1bb5a11, 0x51d1dc80, 0xc3bdbdf6, 0x592d59da, 0xdb25f566,
+ 0x2c9caf6c, 0xc2517e31, 0x50d86e6d, 0xc33aee27, 0x595c3e2a, 0xda8249b4, 0x29aee694, 0xc2fd08a9,
+ 0x4fd288dc, 0xc2c17d52, 0x5987b08a, 0xd9e01006, 0x26b2a794, 0xc3bdbdf6, 0x4ec05432, 0xc2517e31,
+ 0x59afaf4c, 0xd93f4e9e, 0x23a8fb93, 0xc4935b3c, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xd8a00bae,
+ 0x2092f05f, 0xc57d965d, 0x4c77a88e, 0xc18e18a7, 0x59f54bee, 0xd8024d59, 0x1d719810, 0xc67c1e18,
+ 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, 0x49ffd417, 0xc0f1360b,
+ 0x5a2d0957, 0xd6cb76c9, 0x17115bc0, 0xc8b4ab32, 0x48b2b335, 0xc0b15502, 0x5a43b190, 0xd6326a88,
+ 0x13d4ae08, 0xc9edeb50, 0x475a5c77, 0xc07b371e, 0x5a56deec, 0xd59afadb, 0x10911f04, 0xcb39edca,
+ 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97, 0x0d47d096, 0xcc983f70, 0x4488e37f, 0xc02c64a6,
+ 0x5a72c63b, 0xd4710883, 0x09f9e6a1, 0xce0866b8, 0x43103085, 0xc013bc39, 0x5a7b7f1a, 0xd3de9156,
+ 0x06a886a0, 0xcf89e3e8, 0x418d2621, 0xc004ef3f, 0x5a80baf6, 0xd34dcdb4, 0x0354d741, 0xd11c3142,
+ 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3e68fb62, 0xc004ef3f,
+ 0x5a80baf6, 0xd2317756, 0xfcab28bf, 0xd4710883, 0x3cc85709, 0xc013bc39, 0x5a7b7f1a, 0xd1a5ef90,
+ 0xf9577960, 0xd6326a88, 0x3b1e5335, 0xc02c64a6, 0x5a72c63b, 0xd11c3142, 0xf606195f, 0xd8024d59,
+ 0x396b3199, 0xc04ee4b8, 0x5a6690ae, 0xd09441bb, 0xf2b82f6a, 0xd9e01006, 0x37af354c, 0xc07b371e,
+ 0x5a56deec, 0xd00e2639, 0xef6ee0fc, 0xdbcb0cce, 0x35eaa2c7, 0xc0b15502, 0x5a43b190, 0xcf89e3e8,
+ 0xec2b51f8, 0xddc29958, 0x341dbfd3, 0xc0f1360b, 0x5a2d0957, 0xcf077fe1, 0xe8eea440, 0xdfc606f1,
+ 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x306c2624, 0xc18e18a7,
+ 0x59f54bee, 0xce0866b8, 0xe28e67f0, 0xe3edb628, 0x2e88013a, 0xc1eb0209, 0x59d438e5, 0xcd8bbb6d,
+ 0xdf6d0fa1, 0xe61086bc, 0x2c9caf6c, 0xc2517e31, 0x59afaf4c, 0xcd110216, 0xdc57046d, 0xe83c56cf,
+ 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70, 0xd94d586c, 0xea70658a, 0x28b1b544, 0xc33aee27,
+ 0x595c3e2a, 0xcc217822, 0xd651196c, 0xecabef3d, 0x26b2a794, 0xc3bdbdf6, 0x592d59da, 0xcbacb0bf,
+ 0xd3635094, 0xeeee2d9d, 0x24ada23d, 0xc449d892, 0x58fb0568, 0xcb39edca, 0xd0850204, 0xf136580d,
+ 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x2092f05f, 0xc57d965d,
+ 0x588c1404, 0xca5a86c4, 0xcafac90f, 0xf5d544a7, 0x1e7de5df, 0xc6250a18, 0x584f7b58, 0xc9edeb50,
+ 0xc850cab4, 0xf82a6c6a, 0x1c6427a9, 0xc6d569be, 0x580f7b19, 0xc9836582, 0xc5ba1e09, 0xfa824bfd,
+ 0x1a4608ab, 0xc78e9a1d, 0x57cc15bc, 0xc91af976, 0xc337a8f7, 0xfcdc1342, 0x1823dc7d, 0xc8507ea7,
+ 0x57854ddd, 0xc8b4ab32, 0xc0ca4a63, 0xff36f170, 0x15fdf758, 0xc91af976, 0x573b2635, 0xc8507ea7,
+ 0xbe72d9df, 0x0192155f, 0x13d4ae08, 0xc9edeb50, 0x56eda1a0, 0xc7ee77b3, 0xbc322766, 0x03ecadcf,
+ 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, 0x0f7944a7, 0xcbacb0bf,
+ 0x56488dc5, 0xc730e997, 0xb7f814b5, 0x089cf867, 0x0d47d096, 0xcc983f70, 0x55f104dc, 0xc6d569be,
+ 0xb6002be9, 0x0af10a22, 0x0b145041, 0xcd8bbb6d, 0x55962bc0, 0xc67c1e18, 0xb421ef77, 0x0d415013,
+ 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18, 0xb25e054b, 0x0f8cfcbe, 0x06a886a0, 0xcf89e3e8,
+ 0x54d69714, 0xc5d03118, 0xb0b50a2f, 0x11d3443f, 0x0470ebdc, 0xd09441bb, 0x5471e2e6, 0xc57d965d,
+ 0xaf279193, 0x14135c94, 0x0238a1c6, 0xd1a5ef90, 0x5409ed4b, 0xc52d3d18, 0xadb6255e, 0x164c7ddd,
+ 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xfdc75e3a, 0xd3de9156,
+ 0x53304df6, 0xc4935b3c, 0xab2968ec, 0x1aa6c82b, 0xfb8f1424, 0xd5052d97, 0x52beac9f, 0xc449d892,
+ 0xaa0efb24, 0x1cc66e99, 0xf9577960, 0xd6326a88, 0x5249daa2, 0xc402a33c, 0xa9125e60, 0x1edc1953,
+ 0xf720e574, 0xd76619b6, 0x51d1dc80, 0xc3bdbdf6, 0xa833ea44, 0x20e70f32, 0xf4ebafbf, 0xd8a00bae,
+ 0x5156b6d9, 0xc37b2b6a, 0xa773ebfc, 0x22e69ac8, 0xf2b82f6a, 0xd9e01006, 0x50d86e6d, 0xc33aee27,
+ 0xa6d2a626, 0x24da0a9a, 0xf086bb59, 0xdb25f566, 0x50570819, 0xc2fd08a9, 0xa65050b4, 0x26c0b162,
+ 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xec2b51f8, 0xddc29958,
+ 0x4f4af5d1, 0xc2884e6e, 0xa5a92114, 0x2a650525, 0xea0208a8, 0xdf18f0ce, 0x4ec05432, 0xc2517e31,
+ 0xa58480e6, 0x2c216eaa, 0xe7dc2383, 0xe0745b24, 0x4e32a956, 0xc21d0eb8, 0xa57f450a, 0x2dce88aa,
+ 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209, 0xa5996f52, 0x2f6bbe45, 0xe39bd857, 0xe3399167,
+ 0x4d0e4de2, 0xc1bb5a11, 0xa5d2f6a9, 0x30f8801f, 0xe1821a21, 0xe4a2eff6, 0x4c77a88e, 0xc18e18a7,
+ 0xa62bc71b, 0x32744493, 0xdf6d0fa1, 0xe61086bc, 0x4bde1089, 0xc1633f8a, 0xa6a3c1d6, 0x33de87de,
+ 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xdb525dc3, 0xe8f77acf,
+ 0x4aa22036, 0xc114ccb9, 0xa7f084e7, 0x367c9a7e, 0xd94d586c, 0xea70658a, 0x49ffd417, 0xc0f1360b,
+ 0xa8c4d9cb, 0x37af8159, 0xd74e4abc, 0xebeca36c, 0x495aada2, 0xc0d00db6, 0xa9b7723b, 0x38cf1669,
+ 0xd5558381, 0xed6bf9d1, 0x48b2b335, 0xc0b15502, 0xaac7fa0e, 0x39daf5e8, 0xd3635094, 0xeeee2d9d,
+ 0x4807eb4b, 0xc0950d1d, 0xabf612b5, 0x3ad2c2e8, 0xd177fec6, 0xf0730342, 0x475a5c77, 0xc07b371e,
+ 0xad415361, 0x3bb6276e, 0xcf93d9dc, 0xf1fa3ecb, 0x46aa0d6d, 0xc063d405, 0xaea94927, 0x3c84d496,
+ 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, 0xcbe2402d, 0xf50ef5de,
+ 0x454149fc, 0xc03c6a07, 0xb1cd56aa, 0x3de2f148, 0xca155d39, 0xf69bf7c9, 0x4488e37f, 0xc02c64a6,
+ 0xb3885772, 0x3e71e759, 0xc850cab4, 0xf82a6c6a, 0x43cdd89a, 0xc01ed535, 0xb55ddfca, 0x3eeb3347,
+ 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39, 0xb74d4ccb, 0x3f4eaafe, 0xc4e1accb, 0xfb4ab7db,
+ 0x424ff28f, 0xc00b1a20, 0xb955f293, 0x3f9c2bfb, 0xc337a8f7, 0xfcdc1342, 0x418d2621, 0xc004ef3f,
+ 0xbb771c81, 0x3fd39b5a, 0xc197049e, 0xfe6deaa1, 0x40c7d2bd, 0xc0013bd3, 0xbdb00d71, 0x3ff4e5e0,
+};
+
+const uint32_t twidTabEven[4*6 + 16*6 + 64*6] PROGMEM = {
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x5a82799a, 0xd2bec333,
+ 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333,
+ 0x00000000, 0xd2bec333, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7,
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x4b418bbe, 0xf383a3e2,
+ 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2,
+ 0x58c542c5, 0xdc71898d, 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a,
+ 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xcac933ae,
+ 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d,
+ 0x3248d382, 0xc13ad060, 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d,
+ 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3248d382, 0xc13ad060,
+ 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae,
+ 0xcdb72c7e, 0xf383a3e2, 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af,
+ 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xee57aa21, 0xdc71898d,
+ 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060,
+ 0xa73abd3b, 0x3536cc52, 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae,
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x43103085, 0xfcdc1342,
+ 0x418d2621, 0xfe6deaa1, 0x4488e37f, 0xfb4ab7db, 0x45f704f7, 0xf9ba1651, 0x43103085, 0xfcdc1342,
+ 0x48b2b335, 0xf69bf7c9, 0x48b2b335, 0xf69bf7c9, 0x4488e37f, 0xfb4ab7db, 0x4c77a88e, 0xf1fa3ecb,
+ 0x4b418bbe, 0xf383a3e2, 0x45f704f7, 0xf9ba1651, 0x4fd288dc, 0xed6bf9d1, 0x4da1fab5, 0xf0730342,
+ 0x475a5c77, 0xf82a6c6a, 0x52beac9f, 0xe8f77acf, 0x4fd288dc, 0xed6bf9d1, 0x48b2b335, 0xf69bf7c9,
+ 0x553805f2, 0xe4a2eff6, 0x51d1dc80, 0xea70658a, 0x49ffd417, 0xf50ef5de, 0x573b2635, 0xe0745b24,
+ 0x539eba45, 0xe7821d59, 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x553805f2, 0xe4a2eff6,
+ 0x4c77a88e, 0xf1fa3ecb, 0x59d438e5, 0xd8a00bae, 0x569cc31b, 0xe1d4a2c8, 0x4da1fab5, 0xf0730342,
+ 0x5a6690ae, 0xd5052d97, 0x57cc15bc, 0xdf18f0ce, 0x4ec05432, 0xeeee2d9d, 0x5a7b7f1a, 0xd1a5ef90,
+ 0x58c542c5, 0xdc71898d, 0x4fd288dc, 0xed6bf9d1, 0x5a12e720, 0xce86ff2a, 0x5987b08a, 0xd9e01006,
+ 0x50d86e6d, 0xebeca36c, 0x592d59da, 0xcbacb0bf, 0x5a12e720, 0xd76619b6, 0x51d1dc80, 0xea70658a,
+ 0x57cc15bc, 0xc91af976, 0x5a6690ae, 0xd5052d97, 0x52beac9f, 0xe8f77acf, 0x55f104dc, 0xc6d569be,
+ 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59, 0x539eba45, 0xc4df2862, 0x5a6690ae, 0xd09441bb,
+ 0x5471e2e6, 0xe61086bc, 0x50d86e6d, 0xc33aee27, 0x5a12e720, 0xce86ff2a, 0x553805f2, 0xe4a2eff6,
+ 0x4da1fab5, 0xc1eb0209, 0x5987b08a, 0xcc983f70, 0x55f104dc, 0xe3399167, 0x49ffd417, 0xc0f1360b,
+ 0x58c542c5, 0xcac933ae, 0x569cc31b, 0xe1d4a2c8, 0x45f704f7, 0xc04ee4b8, 0x57cc15bc, 0xc91af976,
+ 0x573b2635, 0xe0745b24, 0x418d2621, 0xc004ef3f, 0x569cc31b, 0xc78e9a1d, 0x57cc15bc, 0xdf18f0ce,
+ 0x3cc85709, 0xc013bc39, 0x553805f2, 0xc6250a18, 0x584f7b58, 0xddc29958, 0x37af354c, 0xc07b371e,
+ 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060, 0x51d1dc80, 0xc3bdbdf6,
+ 0x592d59da, 0xdb25f566, 0x2c9caf6c, 0xc2517e31, 0x4fd288dc, 0xc2c17d52, 0x5987b08a, 0xd9e01006,
+ 0x26b2a794, 0xc3bdbdf6, 0x4da1fab5, 0xc1eb0209, 0x59d438e5, 0xd8a00bae, 0x2092f05f, 0xc57d965d,
+ 0x4b418bbe, 0xc13ad060, 0x5a12e720, 0xd76619b6, 0x1a4608ab, 0xc78e9a1d, 0x48b2b335, 0xc0b15502,
+ 0x5a43b190, 0xd6326a88, 0x13d4ae08, 0xc9edeb50, 0x45f704f7, 0xc04ee4b8, 0x5a6690ae, 0xd5052d97,
+ 0x0d47d096, 0xcc983f70, 0x43103085, 0xc013bc39, 0x5a7b7f1a, 0xd3de9156, 0x06a886a0, 0xcf89e3e8,
+ 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x3cc85709, 0xc013bc39,
+ 0x5a7b7f1a, 0xd1a5ef90, 0xf9577960, 0xd6326a88, 0x396b3199, 0xc04ee4b8, 0x5a6690ae, 0xd09441bb,
+ 0xf2b82f6a, 0xd9e01006, 0x35eaa2c7, 0xc0b15502, 0x5a43b190, 0xcf89e3e8, 0xec2b51f8, 0xddc29958,
+ 0x3248d382, 0xc13ad060, 0x5a12e720, 0xce86ff2a, 0xe5b9f755, 0xe1d4a2c8, 0x2e88013a, 0xc1eb0209,
+ 0x59d438e5, 0xcd8bbb6d, 0xdf6d0fa1, 0xe61086bc, 0x2aaa7c7f, 0xc2c17d52, 0x5987b08a, 0xcc983f70,
+ 0xd94d586c, 0xea70658a, 0x26b2a794, 0xc3bdbdf6, 0x592d59da, 0xcbacb0bf, 0xd3635094, 0xeeee2d9d,
+ 0x22a2f4f8, 0xc4df2862, 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x1e7de5df, 0xc6250a18,
+ 0x584f7b58, 0xc9edeb50, 0xc850cab4, 0xf82a6c6a, 0x1a4608ab, 0xc78e9a1d, 0x57cc15bc, 0xc91af976,
+ 0xc337a8f7, 0xfcdc1342, 0x15fdf758, 0xc91af976, 0x573b2635, 0xc8507ea7, 0xbe72d9df, 0x0192155f,
+ 0x11a855df, 0xcac933ae, 0x569cc31b, 0xc78e9a1d, 0xba08fb09, 0x0645e9af, 0x0d47d096, 0xcc983f70,
+ 0x55f104dc, 0xc6d569be, 0xb6002be9, 0x0af10a22, 0x08df1a8c, 0xce86ff2a, 0x553805f2, 0xc6250a18,
+ 0xb25e054b, 0x0f8cfcbe, 0x0470ebdc, 0xd09441bb, 0x5471e2e6, 0xc57d965d, 0xaf279193, 0x14135c94,
+ 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862, 0xac6145bb, 0x187de2a7, 0xfb8f1424, 0xd5052d97,
+ 0x52beac9f, 0xc449d892, 0xaa0efb24, 0x1cc66e99, 0xf720e574, 0xd76619b6, 0x51d1dc80, 0xc3bdbdf6,
+ 0xa833ea44, 0x20e70f32, 0xf2b82f6a, 0xd9e01006, 0x50d86e6d, 0xc33aee27, 0xa6d2a626, 0x24da0a9a,
+ 0xee57aa21, 0xdc71898d, 0x4fd288dc, 0xc2c17d52, 0xa5ed18e0, 0x2899e64a, 0xea0208a8, 0xdf18f0ce,
+ 0x4ec05432, 0xc2517e31, 0xa58480e6, 0x2c216eaa, 0xe5b9f755, 0xe1d4a2c8, 0x4da1fab5, 0xc1eb0209,
+ 0xa5996f52, 0x2f6bbe45, 0xe1821a21, 0xe4a2eff6, 0x4c77a88e, 0xc18e18a7, 0xa62bc71b, 0x32744493,
+ 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52, 0xd94d586c, 0xea70658a,
+ 0x49ffd417, 0xc0f1360b, 0xa8c4d9cb, 0x37af8159, 0xd5558381, 0xed6bf9d1, 0x48b2b335, 0xc0b15502,
+ 0xaac7fa0e, 0x39daf5e8, 0xd177fec6, 0xf0730342, 0x475a5c77, 0xc07b371e, 0xad415361, 0x3bb6276e,
+ 0xcdb72c7e, 0xf383a3e2, 0x45f704f7, 0xc04ee4b8, 0xb02d7724, 0x3d3e82ae, 0xca155d39, 0xf69bf7c9,
+ 0x4488e37f, 0xc02c64a6, 0xb3885772, 0x3e71e759, 0xc694ce67, 0xf9ba1651, 0x43103085, 0xc013bc39,
+ 0xb74d4ccb, 0x3f4eaafe, 0xc337a8f7, 0xfcdc1342, 0x418d2621, 0xc004ef3f, 0xbb771c81, 0x3fd39b5a,
+};
+
+/* log2Tab[x] = floor(log2(x)), format = Q28 */
+const int32_t log2Tab[65] PROGMEM = {
+ 0x00000000, 0x00000000, 0x10000000, 0x195c01a3, 0x20000000, 0x25269e12, 0x295c01a3, 0x2ceaecfe,
+ 0x30000000, 0x32b80347, 0x35269e12, 0x3759d4f8, 0x395c01a3, 0x3b350047, 0x3ceaecfe, 0x3e829fb6,
+ 0x40000000, 0x41663f6f, 0x42b80347, 0x43f782d7, 0x45269e12, 0x4646eea2, 0x4759d4f8, 0x48608280,
+ 0x495c01a3, 0x4a4d3c25, 0x4b350047, 0x4c1404ea, 0x4ceaecfe, 0x4dba4a47, 0x4e829fb6, 0x4f446359,
+ 0x50000000, 0x50b5d69b, 0x51663f6f, 0x52118b11, 0x52b80347, 0x5359ebc5, 0x53f782d7, 0x549101ea,
+ 0x55269e12, 0x55b88873, 0x5646eea2, 0x56d1fafd, 0x5759d4f8, 0x57dea15a, 0x58608280, 0x58df988f,
+ 0x595c01a3, 0x59d5d9fd, 0x5a4d3c25, 0x5ac24113, 0x5b350047, 0x5ba58feb, 0x5c1404ea, 0x5c80730b,
+ 0x5ceaecfe, 0x5d53847a, 0x5dba4a47, 0x5e1f4e51, 0x5e829fb6, 0x5ee44cd5, 0x5f446359, 0x5fa2f045,
+ 0x60000000
+};
+
+const HuffInfo_t huffTabSpecInfo[11] PROGMEM = {
+ /* table 0 not used */
+ {11, { 1, 0, 0, 0, 8, 0, 24, 0, 24, 8, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0},
+ { 9, { 0, 0, 1, 1, 7, 24, 15, 19, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 81},
+ {16, { 1, 0, 0, 4, 2, 6, 3, 5, 15, 15, 8, 9, 3, 3, 5, 2, 0, 0, 0, 0}, 162},
+ {12, { 0, 0, 0, 10, 6, 0, 9, 21, 8, 14, 11, 2, 0, 0, 0, 0, 0, 0, 0, 0}, 243},
+ {13, { 1, 0, 0, 4, 4, 0, 4, 12, 12, 12, 18, 10, 4, 0, 0, 0, 0, 0, 0, 0}, 324},
+ {11, { 0, 0, 0, 9, 0, 16, 13, 8, 23, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 405},
+ {12, { 1, 0, 2, 1, 0, 4, 5, 10, 14, 15, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0}, 486},
+ {10, { 0, 0, 1, 5, 7, 10, 14, 15, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 550},
+ {15, { 1, 0, 2, 1, 0, 4, 3, 8, 11, 20, 31, 38, 32, 14, 4, 0, 0, 0, 0, 0}, 614},
+ {12, { 0, 0, 0, 3, 8, 14, 17, 25, 31, 41, 22, 8, 0, 0, 0, 0, 0, 0, 0, 0}, 783},
+ {12, { 0, 0, 0, 2, 6, 7, 16, 59, 55, 95, 43, 6, 0, 0, 0, 0, 0, 0, 0, 0}, 952},
+};
+
+const int16_t huffTabSpec[1241] PROGMEM = {
+ /* spectrum table 1 [81] (signed) */
+ 0x0000, 0x0200, 0x0e00, 0x0007, 0x0040, 0x0001, 0x0038, 0x0008, 0x01c0, 0x03c0, 0x0e40, 0x0039, 0x0078, 0x01c8, 0x000f, 0x0240,
+ 0x003f, 0x0fc0, 0x01f8, 0x0238, 0x0047, 0x0e08, 0x0009, 0x0208, 0x01c1, 0x0048, 0x0041, 0x0e38, 0x0201, 0x0e07, 0x0207, 0x0e01,
+ 0x01c7, 0x0278, 0x0e78, 0x03c8, 0x004f, 0x0079, 0x01c9, 0x01cf, 0x03f8, 0x0239, 0x007f, 0x0e48, 0x0e0f, 0x0fc8, 0x01f9, 0x03c1,
+ 0x03c7, 0x0e47, 0x0ff8, 0x01ff, 0x0049, 0x020f, 0x0241, 0x0e41, 0x0248, 0x0fc1, 0x0e3f, 0x0247, 0x023f, 0x0e39, 0x0fc7, 0x0e09,
+ 0x0209, 0x03cf, 0x0e79, 0x0e4f, 0x03f9, 0x0249, 0x0fc9, 0x027f, 0x0fcf, 0x0fff, 0x0279, 0x03c9, 0x0e49, 0x0e7f, 0x0ff9, 0x03ff,
+ 0x024f,
+ /* spectrum table 2 [81] (signed) */
+ 0x0000, 0x0200, 0x0e00, 0x0001, 0x0038, 0x0007, 0x01c0, 0x0008, 0x0040, 0x01c8, 0x0e40, 0x0078, 0x000f, 0x0047, 0x0039, 0x0e07,
+ 0x03c0, 0x0238, 0x0fc0, 0x003f, 0x0208, 0x0201, 0x01c1, 0x0e08, 0x0041, 0x01f8, 0x0e01, 0x01c7, 0x0e38, 0x0240, 0x0048, 0x0009,
+ 0x0207, 0x0079, 0x0239, 0x0e78, 0x01cf, 0x03c8, 0x0247, 0x0209, 0x0e48, 0x01f9, 0x0248, 0x0e0f, 0x0ff8, 0x0e39, 0x03f8, 0x0278,
+ 0x03c1, 0x0e47, 0x0fc8, 0x0e09, 0x0fc1, 0x0fc7, 0x01ff, 0x020f, 0x023f, 0x007f, 0x0049, 0x0e41, 0x0e3f, 0x004f, 0x03c7, 0x01c9,
+ 0x0241, 0x03cf, 0x0e79, 0x03f9, 0x0fff, 0x0e4f, 0x0e49, 0x0249, 0x0fcf, 0x03c9, 0x0e7f, 0x0fc9, 0x027f, 0x03ff, 0x0ff9, 0x0279,
+ 0x024f,
+ /* spectrum table 3 [81] (unsigned) */
+ 0x0000, 0x1200, 0x1001, 0x1040, 0x1008, 0x2240, 0x2009, 0x2048, 0x2041, 0x2208, 0x3049, 0x2201, 0x3248, 0x4249, 0x3209, 0x3241,
+ 0x1400, 0x1002, 0x200a, 0x2440, 0x3288, 0x2011, 0x3051, 0x2280, 0x304a, 0x3448, 0x1010, 0x2088, 0x2050, 0x1080, 0x2042, 0x2408,
+ 0x4289, 0x3089, 0x3250, 0x4251, 0x3281, 0x2210, 0x3211, 0x2081, 0x4449, 0x424a, 0x3441, 0x320a, 0x2012, 0x3052, 0x3488, 0x3290,
+ 0x2202, 0x2401, 0x3091, 0x2480, 0x4291, 0x3242, 0x3409, 0x4252, 0x4489, 0x2090, 0x308a, 0x3212, 0x3481, 0x3450, 0x3490, 0x3092,
+ 0x4491, 0x4451, 0x428a, 0x4292, 0x2082, 0x2410, 0x3282, 0x3411, 0x444a, 0x3442, 0x4492, 0x448a, 0x4452, 0x340a, 0x2402, 0x3482,
+ 0x3412,
+ /* spectrum table 4 [81] (unsigned) */
+ 0x4249, 0x3049, 0x3241, 0x3248, 0x3209, 0x1200, 0x2240, 0x0000, 0x2009, 0x2208, 0x2201, 0x2048, 0x1001, 0x2041, 0x1008, 0x1040,
+ 0x4449, 0x4251, 0x4289, 0x424a, 0x3448, 0x3441, 0x3288, 0x3409, 0x3051, 0x304a, 0x3250, 0x3089, 0x320a, 0x3281, 0x3242, 0x3211,
+ 0x2440, 0x2408, 0x2280, 0x2401, 0x2042, 0x2088, 0x200a, 0x2050, 0x2081, 0x2202, 0x2011, 0x2210, 0x1400, 0x1002, 0x1080, 0x1010,
+ 0x4291, 0x4489, 0x4451, 0x4252, 0x428a, 0x444a, 0x3290, 0x3488, 0x3450, 0x3091, 0x3052, 0x3481, 0x308a, 0x3411, 0x3212, 0x4491,
+ 0x3282, 0x340a, 0x3442, 0x4292, 0x4452, 0x448a, 0x2090, 0x2480, 0x2012, 0x2410, 0x2082, 0x2402, 0x4492, 0x3092, 0x3490, 0x3482,
+ 0x3412,
+ /* spectrum table 5 [81] (signed) */
+ 0x0000, 0x03e0, 0x0020, 0x0001, 0x001f, 0x003f, 0x03e1, 0x03ff, 0x0021, 0x03c0, 0x0002, 0x0040, 0x001e, 0x03df, 0x0041, 0x03fe,
+ 0x0022, 0x03c1, 0x005f, 0x03e2, 0x003e, 0x03a0, 0x0060, 0x001d, 0x0003, 0x03bf, 0x0023, 0x0061, 0x03fd, 0x03a1, 0x007f, 0x003d,
+ 0x03e3, 0x03c2, 0x0042, 0x03de, 0x005e, 0x03be, 0x007e, 0x03c3, 0x005d, 0x0062, 0x0043, 0x03a2, 0x03dd, 0x001c, 0x0380, 0x0081,
+ 0x0080, 0x039f, 0x0004, 0x009f, 0x03fc, 0x0024, 0x03e4, 0x0381, 0x003c, 0x007d, 0x03bd, 0x03a3, 0x03c4, 0x039e, 0x0082, 0x005c,
+ 0x0044, 0x0063, 0x0382, 0x03dc, 0x009e, 0x007c, 0x039d, 0x0383, 0x0064, 0x03a4, 0x0083, 0x009d, 0x03bc, 0x009c, 0x0384, 0x0084,
+ 0x039c,
+ /* spectrum table 6 [81] (signed) */
+ 0x0000, 0x0020, 0x001f, 0x0001, 0x03e0, 0x0021, 0x03e1, 0x003f, 0x03ff, 0x005f, 0x0041, 0x03c1, 0x03df, 0x03c0, 0x03e2, 0x0040,
+ 0x003e, 0x0022, 0x001e, 0x03fe, 0x0002, 0x005e, 0x03c2, 0x03de, 0x0042, 0x03a1, 0x0061, 0x007f, 0x03e3, 0x03bf, 0x0023, 0x003d,
+ 0x03fd, 0x0060, 0x03a0, 0x001d, 0x0003, 0x0062, 0x03be, 0x03c3, 0x0043, 0x007e, 0x005d, 0x03dd, 0x03a2, 0x0063, 0x007d, 0x03bd,
+ 0x03a3, 0x003c, 0x03fc, 0x0081, 0x0381, 0x039f, 0x0024, 0x009f, 0x03e4, 0x001c, 0x0382, 0x039e, 0x0044, 0x03dc, 0x0380, 0x0082,
+ 0x009e, 0x03c4, 0x0080, 0x005c, 0x0004, 0x03bc, 0x03a4, 0x007c, 0x009d, 0x0064, 0x0083, 0x0383, 0x039d, 0x0084, 0x0384, 0x039c,
+ 0x009c,
+ /* spectrum table 7 [64] (unsigned) */
+ 0x0000, 0x0420, 0x0401, 0x0821, 0x0841, 0x0822, 0x0440, 0x0402, 0x0861, 0x0823, 0x0842, 0x0460, 0x0403, 0x0843, 0x0862, 0x0824,
+ 0x0881, 0x0825, 0x08a1, 0x0863, 0x0844, 0x0404, 0x0480, 0x0882, 0x0845, 0x08a2, 0x0405, 0x08c1, 0x04a0, 0x0826, 0x0883, 0x0865,
+ 0x0864, 0x08a3, 0x0846, 0x08c2, 0x0827, 0x0866, 0x0406, 0x04c0, 0x0884, 0x08e1, 0x0885, 0x08e2, 0x08a4, 0x08c3, 0x0847, 0x08e3,
+ 0x08c4, 0x08a5, 0x0886, 0x0867, 0x04e0, 0x0407, 0x08c5, 0x08a6, 0x08e4, 0x0887, 0x08a7, 0x08e5, 0x08e6, 0x08c6, 0x08c7, 0x08e7,
+ /* spectrum table 8 [64] (unsigned) */
+ 0x0821, 0x0841, 0x0420, 0x0822, 0x0401, 0x0842, 0x0000, 0x0440, 0x0402, 0x0861, 0x0823, 0x0862, 0x0843, 0x0863, 0x0881, 0x0824,
+ 0x0882, 0x0844, 0x0460, 0x0403, 0x0883, 0x0864, 0x08a2, 0x08a1, 0x0845, 0x0825, 0x08a3, 0x0865, 0x0884, 0x08a4, 0x0404, 0x0885,
+ 0x0480, 0x0846, 0x08c2, 0x08c1, 0x0826, 0x0866, 0x08c3, 0x08a5, 0x04a0, 0x08c4, 0x0405, 0x0886, 0x08e1, 0x08e2, 0x0847, 0x08c5,
+ 0x08e3, 0x0827, 0x08a6, 0x0867, 0x08c6, 0x08e4, 0x04c0, 0x0887, 0x0406, 0x08e5, 0x08e6, 0x08c7, 0x08a7, 0x04e0, 0x0407, 0x08e7,
+ /* spectrum table 9 [169] (unsigned) */
+ 0x0000, 0x0420, 0x0401, 0x0821, 0x0841, 0x0822, 0x0440, 0x0402, 0x0861, 0x0842, 0x0823, 0x0460, 0x0403, 0x0843, 0x0862, 0x0824,
+ 0x0881, 0x0844, 0x0825, 0x0882, 0x0863, 0x0404, 0x0480, 0x08a1, 0x0845, 0x0826, 0x0864, 0x08a2, 0x08c1, 0x0883, 0x0405, 0x0846,
+ 0x04a0, 0x0827, 0x0865, 0x0828, 0x0901, 0x0884, 0x08a3, 0x08c2, 0x08e1, 0x0406, 0x0902, 0x0848, 0x0866, 0x0847, 0x0885, 0x0921,
+ 0x0829, 0x08e2, 0x04c0, 0x08a4, 0x08c3, 0x0903, 0x0407, 0x0922, 0x0868, 0x0886, 0x0867, 0x0408, 0x0941, 0x08c4, 0x0849, 0x08a5,
+ 0x0500, 0x04e0, 0x08e3, 0x0942, 0x0923, 0x0904, 0x082a, 0x08e4, 0x08c5, 0x08a6, 0x0888, 0x0887, 0x0869, 0x0961, 0x08a8, 0x0520,
+ 0x0905, 0x0943, 0x084a, 0x0409, 0x0962, 0x0924, 0x08c6, 0x0981, 0x0889, 0x0906, 0x082b, 0x0925, 0x0944, 0x08a7, 0x08e5, 0x084b,
+ 0x082c, 0x0982, 0x0963, 0x086a, 0x08a9, 0x08c7, 0x0907, 0x0964, 0x040a, 0x08e6, 0x0983, 0x0540, 0x0945, 0x088a, 0x08c8, 0x084c,
+ 0x0926, 0x0927, 0x088b, 0x0560, 0x08c9, 0x086b, 0x08aa, 0x0908, 0x08e8, 0x0985, 0x086c, 0x0965, 0x08e7, 0x0984, 0x0966, 0x0946,
+ 0x088c, 0x08e9, 0x08ab, 0x040b, 0x0986, 0x08ca, 0x0580, 0x0947, 0x08ac, 0x08ea, 0x0928, 0x040c, 0x0967, 0x0909, 0x0929, 0x0948,
+ 0x08eb, 0x0987, 0x08cb, 0x090b, 0x0968, 0x08ec, 0x08cc, 0x090a, 0x0949, 0x090c, 0x092a, 0x092b, 0x092c, 0x094b, 0x0989, 0x094a,
+ 0x0969, 0x0988, 0x096a, 0x098a, 0x098b, 0x094c, 0x096b, 0x096c, 0x098c,
+ /* spectrum table 10 [169] (unsigned) */
+ 0x0821, 0x0822, 0x0841, 0x0842, 0x0420, 0x0401, 0x0823, 0x0862, 0x0861, 0x0843, 0x0863, 0x0440, 0x0402, 0x0844, 0x0882, 0x0824,
+ 0x0881, 0x0000, 0x0883, 0x0864, 0x0460, 0x0403, 0x0884, 0x0845, 0x08a2, 0x0825, 0x08a1, 0x08a3, 0x0865, 0x08a4, 0x0885, 0x08c2,
+ 0x0846, 0x08c3, 0x0480, 0x08c1, 0x0404, 0x0826, 0x0866, 0x08a5, 0x08c4, 0x0886, 0x08c5, 0x08e2, 0x0867, 0x0847, 0x08a6, 0x0902,
+ 0x08e3, 0x04a0, 0x08e1, 0x0405, 0x0901, 0x0827, 0x0903, 0x08e4, 0x0887, 0x0848, 0x08c6, 0x08e5, 0x0828, 0x0868, 0x0904, 0x0888,
+ 0x08a7, 0x0905, 0x08a8, 0x08e6, 0x08c7, 0x0922, 0x04c0, 0x08c8, 0x0923, 0x0869, 0x0921, 0x0849, 0x0406, 0x0906, 0x0924, 0x0889,
+ 0x0942, 0x0829, 0x08e7, 0x0907, 0x0925, 0x08e8, 0x0943, 0x08a9, 0x0944, 0x084a, 0x0941, 0x086a, 0x0926, 0x08c9, 0x0500, 0x088a,
+ 0x04e0, 0x0962, 0x08e9, 0x0963, 0x0946, 0x082a, 0x0961, 0x0927, 0x0407, 0x0908, 0x0945, 0x086b, 0x08aa, 0x0909, 0x0965, 0x0408,
+ 0x0964, 0x084b, 0x08ea, 0x08ca, 0x0947, 0x088b, 0x082b, 0x0982, 0x0928, 0x0983, 0x0966, 0x08ab, 0x0984, 0x0967, 0x0985, 0x086c,
+ 0x08cb, 0x0520, 0x0948, 0x0540, 0x0981, 0x0409, 0x088c, 0x0929, 0x0986, 0x084c, 0x090a, 0x092a, 0x082c, 0x0968, 0x0987, 0x08eb,
+ 0x08ac, 0x08cc, 0x0949, 0x090b, 0x0988, 0x040a, 0x08ec, 0x0560, 0x094a, 0x0969, 0x096a, 0x040b, 0x096b, 0x092b, 0x094b, 0x0580,
+ 0x090c, 0x0989, 0x094c, 0x092c, 0x096c, 0x098b, 0x040c, 0x098a, 0x098c,
+ /* spectrum table 11 [289] (unsigned) */
+ 0x0000, 0x2041, 0x2410, 0x1040, 0x1001, 0x2081, 0x2042, 0x2082, 0x2043, 0x20c1, 0x20c2, 0x1080, 0x2083, 0x1002, 0x20c3, 0x2101,
+ 0x2044, 0x2102, 0x2084, 0x2103, 0x20c4, 0x10c0, 0x1003, 0x2141, 0x2142, 0x2085, 0x2104, 0x2045, 0x2143, 0x20c5, 0x2144, 0x2105,
+ 0x2182, 0x2086, 0x2181, 0x2183, 0x20c6, 0x2046, 0x2110, 0x20d0, 0x2405, 0x2403, 0x2404, 0x2184, 0x2406, 0x1100, 0x2106, 0x1004,
+ 0x2090, 0x2145, 0x2150, 0x2407, 0x2402, 0x2408, 0x2087, 0x21c2, 0x20c7, 0x2185, 0x2146, 0x2190, 0x240a, 0x21c3, 0x21c1, 0x2409,
+ 0x21d0, 0x2050, 0x2047, 0x2107, 0x240b, 0x21c4, 0x240c, 0x2210, 0x2401, 0x2186, 0x2250, 0x2088, 0x2147, 0x2290, 0x240d, 0x2203,
+ 0x2202, 0x20c8, 0x1140, 0x240e, 0x22d0, 0x21c5, 0x2108, 0x2187, 0x21c6, 0x1005, 0x2204, 0x240f, 0x2310, 0x2048, 0x2201, 0x2390,
+ 0x2148, 0x2350, 0x20c9, 0x2205, 0x21c7, 0x2089, 0x2206, 0x2242, 0x2243, 0x23d0, 0x2109, 0x2188, 0x1180, 0x2244, 0x2149, 0x2207,
+ 0x21c8, 0x2049, 0x2283, 0x1006, 0x2282, 0x2241, 0x2245, 0x210a, 0x208a, 0x2246, 0x20ca, 0x2189, 0x2284, 0x2208, 0x2285, 0x2247,
+ 0x22c3, 0x204a, 0x11c0, 0x2286, 0x21c9, 0x20cb, 0x214a, 0x2281, 0x210b, 0x22c2, 0x2342, 0x218a, 0x2343, 0x208b, 0x1400, 0x214b,
+ 0x22c5, 0x22c4, 0x2248, 0x21ca, 0x2209, 0x1010, 0x210d, 0x1007, 0x20cd, 0x22c6, 0x2341, 0x2344, 0x2303, 0x208d, 0x2345, 0x220a,
+ 0x218b, 0x2288, 0x2287, 0x2382, 0x2304, 0x204b, 0x210c, 0x22c1, 0x20cc, 0x204d, 0x2302, 0x21cb, 0x20ce, 0x214c, 0x214d, 0x2384,
+ 0x210e, 0x22c7, 0x2383, 0x2305, 0x2346, 0x2306, 0x1200, 0x22c8, 0x208c, 0x2249, 0x2385, 0x218d, 0x228a, 0x23c2, 0x220b, 0x224a,
+ 0x2386, 0x2289, 0x214e, 0x22c9, 0x2381, 0x208e, 0x218c, 0x204c, 0x2348, 0x1008, 0x2347, 0x21cc, 0x2307, 0x21cd, 0x23c3, 0x2301,
+ 0x218e, 0x208f, 0x23c5, 0x23c4, 0x204e, 0x224b, 0x210f, 0x2387, 0x220d, 0x2349, 0x220c, 0x214f, 0x20cf, 0x228b, 0x22ca, 0x2308,
+ 0x23c6, 0x23c7, 0x220e, 0x23c1, 0x21ce, 0x1240, 0x1009, 0x224d, 0x224c, 0x2309, 0x2388, 0x228d, 0x2389, 0x230a, 0x218f, 0x21cf,
+ 0x224e, 0x23c8, 0x22cb, 0x22ce, 0x204f, 0x228c, 0x228e, 0x234b, 0x234a, 0x22cd, 0x22cc, 0x220f, 0x238b, 0x234c, 0x230d, 0x23c9,
+ 0x238a, 0x1280, 0x230b, 0x224f, 0x100a, 0x230c, 0x12c0, 0x230e, 0x228f, 0x234d, 0x100d, 0x238c, 0x23ca, 0x23cb, 0x22cf, 0x238d,
+ 0x1340, 0x100b, 0x234e, 0x23cc, 0x23cd, 0x230f, 0x1380, 0x238e, 0x234f, 0x1300, 0x238f, 0x100e, 0x100c, 0x23ce, 0x13c0, 0x100f,
+ 0x23cf,
+};
+
+/* coefficient table 4.A.87, format = Q31
+ * reordered as cTab[0], cTab[64], cTab[128], ... cTab[576], cTab[1], cTab[65], cTab[129], ... cTab[639]
+ * keeping full table (not using symmetry) to allow sequential access in synth filter inner loop
+ * format = Q31
+ */
+const uint32_t cTabS[640] PROGMEM = {
+ 0x00000000, 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0x6d474e1d, 0xd1c58ace, 0x09015651, 0xfe4d1be3, 0x0055dba1,
+ 0xffede50e, 0x005b5371, 0x01d78bfc, 0x08d3e41b, 0x2faa221c, 0x6d41d963, 0xd3337b3d, 0x09299ead, 0xfe70b8d1, 0x0050b177,
+ 0xffed978a, 0x006090c4, 0x01fd3ba0, 0x08a24899, 0x311af3a4, 0x6d32730f, 0xd49fd55f, 0x094d7ec2, 0xfe933dc0, 0x004b6c46,
+ 0xffefc9b9, 0x0065fde5, 0x02244a24, 0x086b1eeb, 0x328cc6f0, 0x6d18520e, 0xd60a46e5, 0x096d0e21, 0xfeb48d0d, 0x00465348,
+ 0xfff0065d, 0x006b47fa, 0x024bf7a1, 0x082f552e, 0x33ff670e, 0x6cf4073e, 0xd7722f04, 0x09881dc5, 0xfed4bec3, 0x004103f4,
+ 0xffeff6ca, 0x0070c8a5, 0x0274ba43, 0x07ee507c, 0x3572ec70, 0x6cc59bab, 0xd8d7f21f, 0x099ec3dc, 0xfef3f6ab, 0x003c1fa4,
+ 0xffef7b8b, 0x0075fded, 0x029e35b4, 0x07a8127d, 0x36e69691, 0x6c8c4c7a, 0xda3b176a, 0x09b18a1d, 0xff120d70, 0x003745f9,
+ 0xffeedfa4, 0x007b3875, 0x02c89901, 0x075ca90c, 0x385a49c4, 0x6c492217, 0xdb9b5b12, 0x09c018ce, 0xff2ef725, 0x00329ab6,
+ 0xffee1650, 0x00807994, 0x02f3e48d, 0x070bbf58, 0x39ce0477, 0x6bfbdd98, 0xdcf898fb, 0x09caeb0f, 0xff4aabc8, 0x002d8e42,
+ 0xffed651d, 0x0085c217, 0x03201116, 0x06b559c3, 0x3b415115, 0x6ba4629f, 0xde529086, 0x09d1fa23, 0xff6542d1, 0x00293718,
+ 0xffecc31b, 0x008a7dd7, 0x034d01f0, 0x06593912, 0x3cb41219, 0x6b42a864, 0xdfa93ab5, 0x09d5560b, 0xff7ee3f1, 0x0024dd50,
+ 0xffebe77b, 0x008f4bfc, 0x037ad438, 0x05f7fb90, 0x3e25b17e, 0x6ad73e8d, 0xe0fc421e, 0x09d52709, 0xff975c01, 0x002064f8,
+ 0xffeb50b2, 0x009424c6, 0x03a966bb, 0x0590a67d, 0x3f962fb8, 0x6a619c5e, 0xe24b8f66, 0x09d19ca9, 0xffaea5d6, 0x001c3549,
+ 0xffea9192, 0x0098b855, 0x03d8afe6, 0x05237f9d, 0x41058bc6, 0x69e29784, 0xe396a45d, 0x09cab9f2, 0xffc4e365, 0x0018703f,
+ 0xffe9ca76, 0x009d10bf, 0x04083fec, 0x04b0adcb, 0x4272a385, 0x6959709d, 0xe4de0cb0, 0x09c0e59f, 0xffda17f2, 0x001471f8,
+ 0xffe940f4, 0x00a1039c, 0x043889c6, 0x0437fb0a, 0x43de620a, 0x68c7269b, 0xe620c476, 0x09b3d77f, 0xffee183b, 0x0010bc63,
+ 0xffe88ba8, 0x00a520bb, 0x04694101, 0x03b8f8dc, 0x4547daea, 0x682b39a4, 0xe75f8bb8, 0x09a3e163, 0x0000e790, 0x000d31b5,
+ 0xffe83a07, 0x00a8739d, 0x049aa82f, 0x03343533, 0x46aea856, 0x6785c24d, 0xe89971b7, 0x099140a7, 0x00131c75, 0x0009aa3f,
+ 0xffe79e16, 0x00abe79e, 0x04cc2fcf, 0x02a99097, 0x4812f848, 0x66d76725, 0xe9cea84a, 0x097c1ee8, 0x0023b989, 0x0006b1cf,
+ 0xffe7746e, 0x00af374c, 0x04fe20be, 0x02186a91, 0x4973fef1, 0x661fd6b8, 0xeafee7f1, 0x0963ed46, 0x0033b927, 0x00039609,
+ 0xffe6d466, 0x00b1978d, 0x05303f87, 0x01816e06, 0x4ad237a2, 0x655f63f2, 0xec2a3f5f, 0x0949eaac, 0x00426f36, 0x00007134,
+ 0xffe6afee, 0x00b3d15c, 0x05626209, 0x00e42fa2, 0x4c2ca3df, 0x64964063, 0xed50a31d, 0x092d7970, 0x00504f41, 0xfffdfa25,
+ 0xffe65416, 0x00b5c867, 0x05950122, 0x0040c496, 0x4d83976c, 0x63c45243, 0xee71b2fe, 0x090ec1fc, 0x005d36df, 0xfffb42b0,
+ 0xffe681c6, 0x00b74c37, 0x05c76fed, 0xff96db90, 0x4ed62be3, 0x62ea6474, 0xef8d4d7b, 0x08edfeaa, 0x006928a0, 0xfff91fca,
+ 0xffe66dd0, 0x00b8394b, 0x05f9c051, 0xfee723c6, 0x5024d70e, 0x6207f220, 0xf0a3959f, 0x08cb4e23, 0x007400b8, 0xfff681d6,
+ 0xffe66fac, 0x00b8fe0d, 0x062bf5ec, 0xfe310657, 0x516eefb9, 0x611d58a3, 0xf1b461ab, 0x08a75da4, 0x007e0393, 0xfff48700,
+ 0xffe69423, 0x00b8c6b0, 0x065dd56a, 0xfd7475d8, 0x52b449de, 0x602b0c7f, 0xf2bf6ea4, 0x0880ffdd, 0x00872c63, 0xfff294c3,
+ 0xffe6fed4, 0x00b85f70, 0x068f8b44, 0xfcb1d740, 0x53f495aa, 0x5f30ff5f, 0xf3c4e887, 0x08594887, 0x008f87aa, 0xfff0e7ef,
+ 0xffe75361, 0x00b73ab0, 0x06c0f0c0, 0xfbe8f5bd, 0x552f8ff7, 0x5e2f6367, 0xf4c473c6, 0x08303897, 0x0096dcc2, 0xffef2395,
+ 0xffe80414, 0x00b58c8c, 0x06f1825d, 0xfb19b7bd, 0x56654bdd, 0x5d26be9b, 0xf5be0fa9, 0x08061671, 0x009da526, 0xffedc418,
+ 0xffe85b4b, 0x00b36acd, 0x0721bf22, 0xfa44a069, 0x579505f5, 0x5c16d0ae, 0xf6b1f3c3, 0x07da2b7f, 0x00a3508f, 0xffec8409,
+ 0xffe954d0, 0x00b06b68, 0x075112a2, 0xf96916f5, 0x58befacd, 0x5b001db8, 0xf79fa13a, 0x07ad8c26, 0x00a85e94, 0xffeb3849,
+ 0xffea353a, 0x00acbd2f, 0x077fedb3, 0xf887507c, 0x59e2f69e, 0x59e2f69e, 0xf887507c, 0x077fedb3, 0x00acbd2f, 0xffea353a,
+ 0xffeb3849, 0x00a85e94, 0x07ad8c26, 0xf79fa13a, 0x5b001db8, 0x58befacd, 0xf96916f5, 0x075112a2, 0x00b06b68, 0xffe954d0,
+ 0xffec8409, 0x00a3508f, 0x07da2b7f, 0xf6b1f3c3, 0x5c16d0ae, 0x579505f5, 0xfa44a069, 0x0721bf22, 0x00b36acd, 0xffe85b4b,
+ 0xffedc418, 0x009da526, 0x08061671, 0xf5be0fa9, 0x5d26be9b, 0x56654bdd, 0xfb19b7bd, 0x06f1825d, 0x00b58c8c, 0xffe80414,
+ 0xffef2395, 0x0096dcc2, 0x08303897, 0xf4c473c6, 0x5e2f6367, 0x552f8ff7, 0xfbe8f5bd, 0x06c0f0c0, 0x00b73ab0, 0xffe75361,
+ 0xfff0e7ef, 0x008f87aa, 0x08594887, 0xf3c4e887, 0x5f30ff5f, 0x53f495aa, 0xfcb1d740, 0x068f8b44, 0x00b85f70, 0xffe6fed4,
+ 0xfff294c3, 0x00872c63, 0x0880ffdd, 0xf2bf6ea4, 0x602b0c7f, 0x52b449de, 0xfd7475d8, 0x065dd56a, 0x00b8c6b0, 0xffe69423,
+ 0xfff48700, 0x007e0393, 0x08a75da4, 0xf1b461ab, 0x611d58a3, 0x516eefb9, 0xfe310657, 0x062bf5ec, 0x00b8fe0d, 0xffe66fac,
+ 0xfff681d6, 0x007400b8, 0x08cb4e23, 0xf0a3959f, 0x6207f220, 0x5024d70e, 0xfee723c6, 0x05f9c051, 0x00b8394b, 0xffe66dd0,
+ 0xfff91fca, 0x006928a0, 0x08edfeaa, 0xef8d4d7b, 0x62ea6474, 0x4ed62be3, 0xff96db90, 0x05c76fed, 0x00b74c37, 0xffe681c6,
+ 0xfffb42b0, 0x005d36df, 0x090ec1fc, 0xee71b2fe, 0x63c45243, 0x4d83976c, 0x0040c496, 0x05950122, 0x00b5c867, 0xffe65416,
+ 0xfffdfa25, 0x00504f41, 0x092d7970, 0xed50a31d, 0x64964063, 0x4c2ca3df, 0x00e42fa2, 0x05626209, 0x00b3d15c, 0xffe6afee,
+ 0x00007134, 0x00426f36, 0x0949eaac, 0xec2a3f5f, 0x655f63f2, 0x4ad237a2, 0x01816e06, 0x05303f87, 0x00b1978d, 0xffe6d466,
+ 0x00039609, 0x0033b927, 0x0963ed46, 0xeafee7f1, 0x661fd6b8, 0x4973fef1, 0x02186a91, 0x04fe20be, 0x00af374c, 0xffe7746e,
+ 0x0006b1cf, 0x0023b989, 0x097c1ee8, 0xe9cea84a, 0x66d76725, 0x4812f848, 0x02a99097, 0x04cc2fcf, 0x00abe79e, 0xffe79e16,
+ 0x0009aa3f, 0x00131c75, 0x099140a7, 0xe89971b7, 0x6785c24d, 0x46aea856, 0x03343533, 0x049aa82f, 0x00a8739d, 0xffe83a07,
+ 0x000d31b5, 0x0000e790, 0x09a3e163, 0xe75f8bb8, 0x682b39a4, 0x4547daea, 0x03b8f8dc, 0x04694101, 0x00a520bb, 0xffe88ba8,
+ 0x0010bc63, 0xffee183b, 0x09b3d77f, 0xe620c476, 0x68c7269b, 0x43de620a, 0x0437fb0a, 0x043889c6, 0x00a1039c, 0xffe940f4,
+ 0x001471f8, 0xffda17f2, 0x09c0e59f, 0xe4de0cb0, 0x6959709d, 0x4272a385, 0x04b0adcb, 0x04083fec, 0x009d10bf, 0xffe9ca76,
+ 0x0018703f, 0xffc4e365, 0x09cab9f2, 0xe396a45d, 0x69e29784, 0x41058bc6, 0x05237f9d, 0x03d8afe6, 0x0098b855, 0xffea9192,
+ 0x001c3549, 0xffaea5d6, 0x09d19ca9, 0xe24b8f66, 0x6a619c5e, 0x3f962fb8, 0x0590a67d, 0x03a966bb, 0x009424c6, 0xffeb50b2,
+ 0x002064f8, 0xff975c01, 0x09d52709, 0xe0fc421e, 0x6ad73e8d, 0x3e25b17e, 0x05f7fb90, 0x037ad438, 0x008f4bfc, 0xffebe77b,
+ 0x0024dd50, 0xff7ee3f1, 0x09d5560b, 0xdfa93ab5, 0x6b42a864, 0x3cb41219, 0x06593912, 0x034d01f0, 0x008a7dd7, 0xffecc31b,
+ 0x00293718, 0xff6542d1, 0x09d1fa23, 0xde529086, 0x6ba4629f, 0x3b415115, 0x06b559c3, 0x03201116, 0x0085c217, 0xffed651d,
+ 0x002d8e42, 0xff4aabc8, 0x09caeb0f, 0xdcf898fb, 0x6bfbdd98, 0x39ce0477, 0x070bbf58, 0x02f3e48d, 0x00807994, 0xffee1650,
+ 0x00329ab6, 0xff2ef725, 0x09c018ce, 0xdb9b5b12, 0x6c492217, 0x385a49c4, 0x075ca90c, 0x02c89901, 0x007b3875, 0xffeedfa4,
+ 0x003745f9, 0xff120d70, 0x09b18a1d, 0xda3b176a, 0x6c8c4c7a, 0x36e69691, 0x07a8127d, 0x029e35b4, 0x0075fded, 0xffef7b8b,
+ 0x003c1fa4, 0xfef3f6ab, 0x099ec3dc, 0xd8d7f21f, 0x6cc59bab, 0x3572ec70, 0x07ee507c, 0x0274ba43, 0x0070c8a5, 0xffeff6ca,
+ 0x004103f4, 0xfed4bec3, 0x09881dc5, 0xd7722f04, 0x6cf4073e, 0x33ff670e, 0x082f552e, 0x024bf7a1, 0x006b47fa, 0xfff0065d,
+ 0x00465348, 0xfeb48d0d, 0x096d0e21, 0xd60a46e5, 0x6d18520e, 0x328cc6f0, 0x086b1eeb, 0x02244a24, 0x0065fde5, 0xffefc9b9,
+ 0x004b6c46, 0xfe933dc0, 0x094d7ec2, 0xd49fd55f, 0x6d32730f, 0x311af3a4, 0x08a24899, 0x01fd3ba0, 0x006090c4, 0xffed978a,
+ 0x0050b177, 0xfe70b8d1, 0x09299ead, 0xd3337b3d, 0x6d41d963, 0x2faa221c, 0x08d3e41b, 0x01d78bfc, 0x005b5371, 0xffede50f,
+};
+
+const HuffInfo_t huffTabScaleFactInfo PROGMEM =
+ {19, { 1, 0, 1, 3, 2, 4, 3, 5, 4, 6, 6, 6, 5, 8, 4, 7, 3, 7, 46, 0}, 0};
+
+/* note - includes offset of -60 (4.6.2.3 in spec) */
+const int16_t huffTabScaleFact[121] PROGMEM = { /* scale factor table [121] */
+ 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, 6, -6, 7, -7, 8,
+ -8, 9, -9, 10, -10, -11, 11, 12, -12, 13, -13, 14, -14, 16, 15, 17,
+ 18, -15, -17, -16, 19, -18, -19, 20, -20, 21, -21, 22, -22, 23, -23, -25,
+ 25, -27, -24, -26, 24, -28, 27, 29, -30, -29, 26, -31, -34, -33, -32, -36,
+ 28, -35, -38, -37, 30, -39, -41, -57, -59, -58, -60, 38, 39, 40, 41, 42,
+ 57, 37, 31, 32, 33, 34, 35, 36, 44, 51, 52, 53, 54, 55, 56, 50,
+ 45, 46, 47, 48, 49, 58, -54, -52, -51, -50, -55, 43, 60, 59, -56, -53,
+ -45, -44, -42, -40, -43, -49, -48, -46, -47,
+};
+
+/* noise table 4.A.88, format = Q31 */
+const uint32_t noiseTab[512*2] PROGMEM = {
+ 0x8010fd38, 0xb3dc7948, 0x7c4e2301, 0xa9904192, 0x121622a7, 0x86489625, 0xc3d53d25, 0xd0343fa9,
+ 0x674d6f70, 0x25f4e9fd, 0xce1a8c8b, 0x72a726c5, 0xfea6efc6, 0xaa4adb1a, 0x8b2dd628, 0xf14029e4,
+ 0x46321c1a, 0x604889a0, 0x33363b63, 0x815ed069, 0x802b4315, 0x8f2bf7f3, 0x85b86073, 0x745cfb46,
+ 0xc57886b3, 0xb76731f0, 0xa2a66772, 0x828ca631, 0x60cc145e, 0x1ad1010f, 0x090c83d4, 0x9bd7ba87,
+ 0x5f5aeea2, 0x8b4dbd99, 0x848e7b1e, 0x86bb9fa2, 0x26f18ae5, 0xc0b81194, 0x553407bf, 0x52c17953,
+ 0x755f468d, 0x166b04f8, 0xa5687981, 0x4343248b, 0xa6558d5e, 0xc5f6fab7, 0x80a4fb8c, 0x8cb53cb7,
+ 0x7da68a54, 0x9cd8df8a, 0xba05376c, 0xfcb58ee2, 0xfdd657a4, 0x005e35ca, 0x91c75c55, 0x367651e6,
+ 0x816abf85, 0x8f831c4f, 0x423f9c9c, 0x55aa919e, 0x80779834, 0xb59f4244, 0x800a095c, 0x7de9e0cc,
+ 0x46bda5cb, 0x4c184464, 0x2c438f71, 0x797216b5, 0x5035cee6, 0xa0c3a26e, 0x9d3f95fa, 0xd4a100c0,
+ 0x8ac30dac, 0x04b87397, 0x9e5ac516, 0x8b0b442e, 0x66210ad6, 0x88ba7598, 0x45b9bd33, 0xf0be5087,
+ 0x9261b85e, 0x364f6a31, 0x891c4b50, 0x23ad08ce, 0xf10366a6, 0x80414276, 0x1b562e06, 0x8be21591,
+ 0x9e798195, 0x7fb4045c, 0x7d9506cf, 0x854e691f, 0x9207f092, 0x7a94c9d5, 0x88911536, 0x3f45cc61,
+ 0x27059279, 0xa5b57109, 0x6d2bb67b, 0x3bdc5379, 0x74e662d8, 0x80348f8c, 0xf875e638, 0x5a8caea1,
+ 0x2459ae75, 0x2c54b939, 0x79ee3203, 0xb9bc8683, 0x9b6f630c, 0x9f45b351, 0x8563b2b9, 0xe5dbba41,
+ 0x697c7d0d, 0x7bb7c90e, 0xac900866, 0x8e6b5177, 0x8822dd37, 0x7fd5a91e, 0x7506da05, 0x82302aca,
+ 0xa5e4be04, 0x4b4288eb, 0x00b8bc9f, 0x4f1033e4, 0x7200d612, 0x43900c8c, 0xa815b900, 0x676ed1d4,
+ 0x5c5f23b2, 0xa758ee11, 0xaf73abfa, 0x11714ec0, 0x265239e0, 0xc50de679, 0x8a84e341, 0xa1438354,
+ 0x7f1a341f, 0x343ec96b, 0x696e71b0, 0xa13bde39, 0x81e75094, 0x80091111, 0x853a73bf, 0x80f9c1ee,
+ 0xe4980086, 0x886a8e28, 0xa7e89426, 0xdd93edd7, 0x7592100d, 0x0bfa8123, 0x850a26d4, 0x2e34f395,
+ 0x421b6c00, 0xa4a462e4, 0x4e3f5090, 0x3c189f4c, 0x3c971a56, 0xdd0376d2, 0x747a5367, 0x7bcbc9d7,
+ 0x3966be6a, 0x7efda616, 0x55445e15, 0x7ba2ab3f, 0x5fe684f2, 0x8cf42af9, 0x808c61c3, 0x4390c27b,
+ 0x7cac62ff, 0xea6cab22, 0x5d0902ad, 0xc27b7208, 0x7a27389d, 0x5820a357, 0xa29bbe59, 0x9df0f1fd,
+ 0x92bd67e5, 0x7195b587, 0x97cac65b, 0x8339807e, 0x8f72d832, 0x5fad8685, 0xa462d9d3, 0x81d46214,
+ 0x6ae93e1d, 0x6b23a5b9, 0xc2732874, 0x81795268, 0x7c568cb6, 0x668513ea, 0x428d024e, 0x66b78b3a,
+ 0xfee9ef03, 0x9ddcbb82, 0xa605f07e, 0x46dc55e0, 0x85415054, 0xc89ec271, 0x7c42edfb, 0x0befe59b,
+ 0x89b8f607, 0x6d732a1a, 0xa7081ebd, 0x7e403258, 0x21feeb7b, 0x5dd7a1e7, 0x23e3a31a, 0x129bc896,
+ 0xa11a6b54, 0x7f1e031c, 0xfdc1a4d1, 0x96402e53, 0xb9700f1a, 0x8168ecd6, 0x7d63d3cc, 0x87a70d65,
+ 0x81075a7a, 0x55c8caa7, 0xa95d00b5, 0x102b1652, 0x0bb30215, 0xe5b63237, 0xa446ca44, 0x82d4c333,
+ 0x67b2e094, 0x44c3d661, 0x33fd6036, 0xde1ea2a1, 0xa95e8e47, 0x78f66eb9, 0x6f2aef1e, 0xe8887247,
+ 0x80a3b70e, 0xfca0d9d3, 0x6bf0fd20, 0x0d5226de, 0xf4341c87, 0x5902df05, 0x7ff1a38d, 0xf02e5a5b,
+ 0x99f129af, 0x8ac63d01, 0x7b53f599, 0x7bb32532, 0x99ac59b0, 0x5255a80f, 0xf1320a41, 0x2497aa5c,
+ 0xcce60bd8, 0x787c634b, 0x7ed58c5b, 0x8a28eb3a, 0x24a5e647, 0x8b79a2c1, 0x955f5ce5, 0xa9d12bc4,
+ 0x7a1e20c6, 0x3eeda7ac, 0xf7be823a, 0x042924ce, 0x808b3f03, 0x364248da, 0xac2895e5, 0x69a8b5fa,
+ 0x97fe8b63, 0xbdeac9aa, 0x8073e0ad, 0x6c25dba7, 0x005e51d2, 0x52e74389, 0x59d3988c, 0xe5d1f39c,
+ 0x7b57dc91, 0x341adbe7, 0xa7d42b8d, 0x74e9f335, 0xd35bf7d8, 0x5b7c0a4b, 0x75bc0874, 0x552129bf,
+ 0x8144b70d, 0x6de93bbb, 0x5825f14b, 0x473ec5ca, 0x80a8f37c, 0xe6552d69, 0x7898360b, 0x806379b0,
+ 0xa9b59339, 0x3f6bf60c, 0xc367d731, 0x920ade99, 0x125592f7, 0x877e5ed1, 0xda895d95, 0x075f2ece,
+ 0x380e5f5e, 0x9b006b62, 0xd17a6dd2, 0x530a0e13, 0xf4cc9a14, 0x7d0a0ed4, 0x847c6e3f, 0xbaee4975,
+ 0x47131163, 0x64fb2cac, 0x5e2100a6, 0x7b756a42, 0xd87609f4, 0x98bfe48c, 0x0493745e, 0x836c5784,
+ 0x7e5ccb40, 0x3df6b476, 0x97700d28, 0x8bbd93fd, 0x56de9cdb, 0x680b4e65, 0xebc3d90e, 0x6d286793,
+ 0x6753712e, 0xe05c98a7, 0x3d2b6b85, 0xc4b18ddb, 0x7b59b869, 0x31435688, 0x811888e9, 0xe011ee7a,
+ 0x6a5844f9, 0x86ae35ea, 0xb4cbc10b, 0x01a6f5d6, 0x7a49ed64, 0x927caa49, 0x847ddaed, 0xae0d9bb6,
+ 0x836bdb04, 0x0fd810a6, 0x74fe126b, 0x4a346b5f, 0x80184d36, 0x5afd153c, 0x90cc8102, 0xe606d0e6,
+ 0xde69aa58, 0xa89f1222, 0xe06df715, 0x8fd16144, 0x0317c3e8, 0x22ce92fc, 0x690c3eca, 0x93166f02,
+ 0x71573414, 0x8d43cffb, 0xe8bd0bb6, 0xde86770f, 0x0bf99a41, 0x4633a661, 0xba064108, 0x7adafae3,
+ 0x2f6cde5d, 0xb350a52c, 0xa5ebfb0b, 0x74c57b46, 0xd3b603b5, 0x80b70892, 0xa7f7fa53, 0xd94b566c,
+ 0xdda3fd86, 0x6a635793, 0x3ed005ca, 0xc5f087d8, 0x31e3a746, 0x7a4278f9, 0x82def1f9, 0x06caa2b2,
+ 0xe9d2c349, 0x8940e7f7, 0x7feef8dd, 0x4a9b01f0, 0xacde69f8, 0x57ddc280, 0xf09e4ba4, 0xb6d9f729,
+ 0xb48c18f2, 0xd3654aa9, 0xca7a03c8, 0x14d57545, 0x7fda87a5, 0x0e411366, 0xb77d0df0, 0x8c2aa467,
+ 0x787f2590, 0x2d292db1, 0x9f12682c, 0x44ac364d, 0x1a4b31a6, 0x871f7ded, 0x7ff99167, 0x6630a1d5,
+ 0x25385eb9, 0x2d4dd549, 0xaf8a7004, 0x319ebe0f, 0x379ab730, 0x81dc56a4, 0x822d8523, 0x1ae8554c,
+ 0x18fa0786, 0x875f7de4, 0x85ca350f, 0x7de818dc, 0x7786a38f, 0xa5456355, 0x92e60f88, 0xf5526122,
+ 0x916039bc, 0xc561e2de, 0x31c42042, 0x7c82e290, 0x75d158b2, 0xb015bda1, 0x7220c750, 0x46565441,
+ 0xd0da1fdd, 0x7b777481, 0x782e73c6, 0x8cd72b7b, 0x7f1006aa, 0xfb30e51e, 0x87994818, 0x34e7c7db,
+ 0x7faae06b, 0xea74fbc0, 0xd20c7af4, 0xc44f396b, 0x06b4234e, 0xdf2e2a93, 0x2efb07c8, 0xce861911,
+ 0x7550ea05, 0xd8d90bbb, 0x58522eec, 0x746b3520, 0xce844ce9, 0x7f5cacc3, 0xda8f17e0, 0x2fedf9cb,
+ 0xb2f77ec4, 0x6f13f4c0, 0x834de085, 0x7b7ace4b, 0x713b16ac, 0x499c5ab0, 0x06a7961d, 0x1b39a48a,
+ 0xbb853e6e, 0x7c781cc1, 0xc0baebf5, 0x7dace394, 0x815ceebc, 0xcc7b27d4, 0x8274b181, 0xa2be40a2,
+ 0xdd01d5dc, 0x7fefeb14, 0x0813ec78, 0xba3077cc, 0xe5cf1e1c, 0xedcfacae, 0x54c43a9b, 0x5cd62a42,
+ 0x93806b55, 0x03095c5b, 0x8e076ae3, 0x71bfcd2a, 0x7ac1989b, 0x623bc71a, 0x5e15d4d2, 0xfb341dd1,
+ 0xd75dfbca, 0xd0da32be, 0xd4569063, 0x337869da, 0x3d30606a, 0xcd89cca2, 0x7dd2ae36, 0x028c03cd,
+ 0xd85e052c, 0xe8dc9ec5, 0x7ffd9241, 0xde5bf4c6, 0x88c4b235, 0x8228be2e, 0x7fe6ec64, 0x996abe6a,
+ 0xdeb0666d, 0x9eb86611, 0xd249b922, 0x18b3e26b, 0x80211168, 0x5f8bb99c, 0x6ecb0dd2, 0x4728ff8d,
+ 0x2ac325b8, 0x6e5169d2, 0x7ebbd68d, 0x05e41d17, 0xaaa19f28, 0x8ab238a6, 0x51f105be, 0x140809cc,
+ 0x7f7345d9, 0x3aae5a9d, 0xaecec6e4, 0x1afb3473, 0xf6229ed1, 0x8d55f467, 0x7e32003a, 0x70f30c14,
+ 0x6686f33f, 0xd0d45ed8, 0x644fab57, 0x3a3fbbd3, 0x0b255fc4, 0x679a1701, 0x90e17b6e, 0x325d537b,
+ 0xcd7b9b87, 0xaa7be2a2, 0x7d47c966, 0xa33dbce5, 0x8659c3bb, 0x72a41367, 0x15c446e0, 0x45fe8b0a,
+ 0x9d8ddf26, 0x84d47643, 0x7fabe0da, 0x36a70122, 0x7a28ebfe, 0x7c29b8b8, 0x7f760406, 0xbabe4672,
+ 0x23ea216e, 0x92bcc50a, 0x6d20dba2, 0xad5a7c7e, 0xbf3897f5, 0xabb793e1, 0x8391fc7e, 0xe270291c,
+ 0x7a248d58, 0x80f8fd15, 0x83ef19f3, 0x5e6ece7d, 0x278430c1, 0x35239f4d, 0xe09c073b, 0x50e78cb5,
+ 0xd4b811bd, 0xce834ee0, 0xf88aaa34, 0xf71da5a9, 0xe2b0a1d5, 0x7c3aef31, 0xe84eabca, 0x3ce25964,
+ 0xf29336d3, 0x8fa78b2c, 0xa3fc3415, 0x63e1313d, 0x7fbc74e0, 0x7340bc93, 0x49ae583b, 0x8b79de4b,
+ 0x25011ce9, 0x7b462279, 0x36007db0, 0x3da1599c, 0x77780772, 0xc845c9bb, 0x83ba68be, 0x6ee507d1,
+ 0x2f0159b8, 0x5392c4ed, 0x98336ff6, 0x0b3c7f11, 0xde697aac, 0x893fc8d0, 0x6b83f8f3, 0x47799a0d,
+ 0x801d9dfc, 0x8516a83e, 0x5f8d22ec, 0x0f8ba384, 0xa049dc4b, 0xdd920b05, 0x7a99bc9f, 0x9ad19344,
+ 0x7a345dba, 0xf501a13f, 0x3e58bf19, 0x7fffaf9a, 0x3b4e1511, 0x0e08b991, 0x9e157620, 0x7230a326,
+ 0x4977f9ff, 0x2d2bbae1, 0x607aa7fc, 0x7bc85d5f, 0xb441bbbe, 0x8d8fa5f2, 0x601cce26, 0xda1884f2,
+ 0x81c82d64, 0x200b709c, 0xcbd36abe, 0x8cbdddd3, 0x55ab61d3, 0x7e3ee993, 0x833f18aa, 0xffc1aaea,
+ 0x7362e16a, 0x7fb85db2, 0x904ee04c, 0x7f04dca6, 0x8ad7a046, 0xebe7d8f7, 0xfbc4c687, 0xd0609458,
+ 0x093ed977, 0x8e546085, 0x7f5b8236, 0x7c47e118, 0xa01f2641, 0x7ffb3e48, 0x05de7cda, 0x7fc281b9,
+ 0x8e0278fc, 0xd74e6d07, 0x94c24450, 0x7cf9e641, 0x2ad27871, 0x919fa815, 0x805fd205, 0x7758397f,
+ 0xe2c7e02c, 0x1828e194, 0x5613d6fe, 0xfb55359f, 0xf9699516, 0x8978ee26, 0x7feebad9, 0x77d71d82,
+ 0x55b28b60, 0x7e997600, 0x80821a6b, 0xc6d78af1, 0x691822ab, 0x7f6982a0, 0x7ef56f99, 0x5c307f40,
+ 0xac6f8b76, 0x42cc8ba4, 0x782c61d9, 0xa0224dd0, 0x7bd234d1, 0x74576e3b, 0xe38cfe9a, 0x491e66ef,
+ 0xc78291c5, 0x895bb87f, 0x924f7889, 0x71b89394, 0x757b779d, 0xc4a9c604, 0x5cdf7829, 0x8020e9df,
+ 0x805e8245, 0x4a82c398, 0x6360bd62, 0x78bb60fc, 0x09e0d014, 0x4b0ea180, 0xb841978b, 0x69a0e864,
+ 0x7df35977, 0x3284b0dd, 0x3cdc2efd, 0x57d31f5e, 0x541069cc, 0x1776e92e, 0x04309ea3, 0xa015eb2d,
+ 0xce7bfabc, 0x41b638f8, 0x8365932e, 0x846ab44c, 0xbbcc80cb, 0x8afa6cac, 0x7fc422ea, 0x4e403fc0,
+ 0xbfac9aee, 0x8e4c6709, 0x028e01fb, 0x6d160a9b, 0x7fe93004, 0x790f9cdc, 0x6a1f37a0, 0xf7e7ef30,
+ 0xb4ea0f04, 0x7bf4c8e6, 0xe981701f, 0xc258a9d3, 0x6acbbfba, 0xef5479c7, 0x079c8bd8, 0x1a410f56,
+ 0x6853b799, 0x86cd4f01, 0xc66e23b6, 0x34585565, 0x8d1fe00d, 0x7fcdba1a, 0x32c9717b, 0xa02f9f48,
+ 0xf64940db, 0x5ed7d8f1, 0x61b823b2, 0x356f8918, 0xa0a7151e, 0x793fc969, 0x530beaeb, 0x34e93270,
+ 0x4fc4ddb5, 0x88d58b6c, 0x36094774, 0xf620ac80, 0x03763a72, 0xf910c9a6, 0x6666fb2d, 0x752c8be8,
+ 0x9a6dfdd8, 0xd1a7117d, 0x51c1b1d4, 0x0a67773d, 0x43b32a79, 0x4cdcd085, 0x5f067d30, 0x05bfe92a,
+ 0x7ed7d203, 0xe71a3c85, 0x99127ce2, 0x8eb3cac4, 0xad4bbcea, 0x5c6a0fd0, 0x0eec04af, 0x94e95cd4,
+ 0x8654f921, 0x83eabb5d, 0xb058d7ca, 0x69f12d3c, 0x03d881b2, 0x80558ef7, 0x82938cb3, 0x2ec0e1d6,
+ 0x80044422, 0xd1e47051, 0x720fc6ff, 0x82b20316, 0x0d527b02, 0x63049a15, 0x7ad5b9ad, 0xd2a4641d,
+ 0x41144f86, 0x7b04917a, 0x15c4a2c0, 0x9da07916, 0x211df54a, 0x7fdd09af, 0xfe924f3f, 0x7e132cfe,
+ 0x9a1d18d6, 0x7c56508b, 0x80f0f0af, 0x8095ced6, 0x8037d0d7, 0x026719d1, 0xa55fec43, 0x2b1c7cb7,
+ 0xa5cd5ac1, 0x77639fad, 0x7fcd8b62, 0x81a18c27, 0xaee4912e, 0xeae9eebe, 0xeb3081de, 0x8532aada,
+ 0xc822362e, 0x86a649a9, 0x8031a71d, 0x7b319dc6, 0xea8022e6, 0x814bc5a9, 0x8f62f7a1, 0xa430ea17,
+ 0x388deafb, 0x883b5185, 0x776fe13c, 0x801c683f, 0x87c11b98, 0xb7cbc644, 0x8e9ad3e8, 0x3cf5a10c,
+ 0x7ff6a634, 0x949ef096, 0x9f84aa7c, 0x010af13f, 0x782d1de8, 0xf18e492a, 0x6cf63b01, 0x4301cd81,
+ 0x32d15c9e, 0x68ad8cef, 0xd09bd2d6, 0x908c5c15, 0xd1e36260, 0x2c5bfdd0, 0x88765a99, 0x93deba1e,
+ 0xac6ae342, 0xe865b84c, 0x0f4f2847, 0x7fdf0499, 0x78b1c9b3, 0x6a73261e, 0x601a96f6, 0xd2847933,
+ 0x489aa888, 0xe12e8093, 0x3bfa5a5f, 0xd96ba5f7, 0x7c8f4c8d, 0x80940c6f, 0xcef9dd1a, 0x7e1a055f,
+ 0x3483558b, 0x02b59cc4, 0x0c56333e, 0x05a5b813, 0x92d66287, 0x7516b679, 0x71bfe03f, 0x8056bf68,
+ 0xc24d0724, 0x8416bcf3, 0x234afbdb, 0x4b0d6f9c, 0xaba97333, 0x4b4f42b6, 0x7e8343ab, 0x7ffe2603,
+ 0xe590f73c, 0x45e10c76, 0xb07a6a78, 0xb35609d3, 0x1a027dfd, 0x90cb6e20, 0x82d3fe38, 0x7b409257,
+ 0x0e395afa, 0x1b802093, 0xcb0c6c59, 0x241e17e7, 0x1ee3ea0a, 0x41a82302, 0xab04350a, 0xf570beb7,
+ 0xbb444b9b, 0x83021459, 0x838d65dc, 0x1c439c84, 0x6fdcc454, 0xef9ef325, 0x18626c1c, 0x020d251f,
+ 0xc4aae786, 0x8614cb48, 0xf6f53ca6, 0x8710dbab, 0x89abec0d, 0xf29d41c1, 0x94b50336, 0xfdd49178,
+ 0x604658d1, 0x800e85be, 0xca1bb079, 0x7fa48eeb, 0xa3b7fafe, 0xd330436b, 0x64eb604c, 0x43a658ae,
+ 0x7caa1337, 0xddd445e6, 0x7efbf955, 0xb706ec71, 0x624a6b53, 0x9e0e231f, 0x97097248, 0xa1e1a17a,
+ 0x68dd2e44, 0x7f9d2e14, 0xddcc7074, 0x58324197, 0xc88fc426, 0x6d3640ae, 0x7ef83600, 0x759a0270,
+ 0x98b6d854, 0xd63c9b84, 0x372474a2, 0xe3f18cfd, 0x56ab0bdb, 0x85c9be7e, 0x47dfcfeb, 0xa5830d41,
+ 0x0ddd6283, 0xf4f480ad, 0x74c60e38, 0xab8943c3, 0xc1508fe7, 0x480cdc39, 0x8e097362, 0xa44793be,
+ 0x538b7e18, 0x545f5b41, 0x56529175, 0x9771a97e, 0xc2da7421, 0xea8265f2, 0x805d1163, 0x883c5d28,
+ 0x8ba94c48, 0x4f676e65, 0xf78735b3, 0xe1853671, 0x7f454f53, 0x18147f85, 0x7d09e15d, 0xdb4f3494,
+ 0x795c8973, 0x83310632, 0x85d8061c, 0x9a1a0ebf, 0xc125583c, 0x2a1b1a95, 0x7fd9103f, 0x71e98c72,
+ 0x40932ed7, 0x91ed227a, 0x3c5e560e, 0xe816dee9, 0xb0891b80, 0x600038ba, 0xc7d9a80d, 0x7fff5e09,
+ 0x7e3f4351, 0xbb6b4424, 0xb14448d4, 0x8d6bb7e1, 0xfb153626, 0xa68ad537, 0xd9782006, 0xf62f6991,
+ 0x359ba8c1, 0x02ccff0b, 0x91bf2256, 0x7ea71c4d, 0x560ce5df, 0xeeba289b, 0xa574c4e7, 0x9e04f6ee,
+ 0x7860a5ec, 0x0b8db4a2, 0x968ba3d7, 0x0b6c77df, 0xd6f3157d, 0x402eff1a, 0x49b820b3, 0x8152aebb,
+ 0xd180b0b6, 0x098604d4, 0x7ff92224, 0xede9c996, 0x89c58061, 0x829624c4, 0xc6e71ea7, 0xba94d915,
+ 0x389c3cf6, 0x5b4c5a06, 0x04b335e6, 0x516a8aab, 0x42c8d7d9, 0x92b12af6, 0x86c8549f, 0xfda98acf,
+ 0x819673b6, 0x69545dac, 0x6feaa230, 0x726e6d3f, 0x886ebdfe, 0x34f5730a, 0x7af63ba2, 0x77307bbf,
+ 0x7cd80630, 0x6e45efe0, 0x7f8ad7eb, 0x59d7df99, 0x86c70946, 0xda233629, 0x753f6cbf, 0x825eeb40,
+};
+
+/* sample rates (table 4.5.1) */
+const int32_t sampRateTab[12] PROGMEM = {
+ 96000, 88200, 64000, 48000, 44100, 32000,
+ 24000, 22050, 16000, 12000, 11025, 8000
+};
+
+/* max scalefactor band for prediction (main profile only) */
+const uint8_t predSFBMax[12] PROGMEM = {
+ 33, 33, 38, 40, 40, 40, 41, 41, 37, 37, 37, 34
+};
+
+/* channel mapping (table 1.6.3.4) (-1 = unknown, so need to determine mapping based on rules in 8.5.1) */
+const int8_t channelMapTab[8] PROGMEM = {
+ -1, 1, 2, 3, 4, 5, 6, 8
+};
+
+/* number of channels in each element (SCE, CPE, etc.)
+ * see AACElementID in aaccommon.h
+ */
+const uint8_t elementNumChans[8] PROGMEM = {
+ 1, 2, 0, 1, 0, 0, 0, 0
+};
+
+/* total number of scale factor bands in one window */
+const uint8_t /*char*/ sfBandTotalShort[12] PROGMEM = {
+ 12, 12, 12, 14, 14, 14, 15, 15, 15, 15, 15, 15
+};
+
+const uint8_t /*char*/ sfBandTotalLong[12] PROGMEM = {
+ 41, 41, 47, 49, 49, 51, 47, 47, 43, 43, 43, 40
+};
+
+/* scale factor band tables */
+const uint8_t sfBandTabShortOffset[12] PROGMEM = {0, 0, 0, 13, 13, 13, 28, 28, 44, 44, 44, 60};
+
+const uint16_t sfBandTabShort[76] PROGMEM = {
+ /* short block 64, 88, 96 kHz [13] (tables 4.5.24, 4.5.26) */
+ 0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128,
+
+ /* short block 32, 44, 48 kHz [15] (table 4.5.15) */
+ 0, 4, 8, 12, 16, 20, 28, 36, 44, 56, 68, 80, 96, 112, 128,
+
+ /* short block 22, 24 kHz [16] (table 4.5.22) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 64, 76, 92, 108, 128,
+
+ /* short block 11, 12, 16 kHz [16] (table 4.5.20) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 60, 72, 88, 108, 128,
+
+ /* short block 8 kHz [16] (table 4.5.18) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 36, 44, 52, 60, 72, 88, 108, 128
+};
+
+const uint16_t sfBandTabLongOffset[12] PROGMEM = {0, 0, 42, 90, 90, 140, 192, 192, 240, 240, 240, 284};
+
+const uint16_t sfBandTabLong[325] PROGMEM = {
+ /* long block 88, 96 kHz [42] (table 4.5.25) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52,
+ 56, 64, 72, 80, 88, 96, 108, 120, 132, 144, 156, 172, 188, 212,
+ 240, 276, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024,
+
+ /* long block 64 kHz [48] (table 4.5.13) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64,
+ 72, 80, 88, 100, 112, 124, 140, 156, 172, 192, 216, 240, 268, 304, 344, 384,
+ 424, 464, 504, 544, 584, 624, 664, 704, 744, 784, 824, 864, 904, 944, 984, 1024,
+
+ /* long block 44, 48 kHz [50] (table 4.5.14) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88,
+ 96, 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448,
+ 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 1024,
+
+ /* long block 32 kHz [52] (table 4.5.16) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96,
+ 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512,
+ 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024,
+
+ /* long block 22, 24 kHz [48] (table 4.5.21) */
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 76,
+ 84, 92, 100, 108, 116, 124, 136, 148, 160, 172, 188, 204, 220, 240, 260, 284,
+ 308, 336, 364, 396, 432, 468, 508, 552, 600, 652, 704, 768, 832, 896, 960, 1024,
+
+ /* long block 11, 12, 16 kHz [44] (table 4.5.19) */
+ 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 100, 112, 124,
+ 136, 148, 160, 172, 184, 196, 212, 228, 244, 260, 280, 300, 320, 344, 368,
+ 396, 424, 456, 492, 532, 572, 616, 664, 716, 772, 832, 896, 960, 1024,
+
+ /* long block 8 kHz [41] (table 4.5.17) */
+ 0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156,
+ 172, 188, 204, 220, 236, 252, 268, 288, 308, 328, 348, 372, 396, 420,
+ 448, 476, 508, 544, 580, 620, 664, 712, 764, 820, 880, 944, 1024
+};
+
+/* TNS max bands (table 4.139) and max order (table 4.138) */
+const uint8_t tnsMaxBandsShortOffset[3] PROGMEM = {0, 0, 12};
+
+const uint16_t tnsMaxBandsShort[2*12] PROGMEM = {
+ 9, 9, 10, 14, 14, 14, 14, 14, 14, 14, 14, 14, /* short block, Main/LC */
+ 7, 7, 7, 6, 6, 6, 7, 7, 8, 8, 8, 7 /* short block, SSR */
+};
+
+const uint8_t tnsMaxOrderShort[3] PROGMEM = {7, 7, 7};
+
+const uint8_t tnsMaxBandsLongOffset[3] PROGMEM = {0, 0, 12};
+
+const uint16_t tnsMaxBandsLong[2*12] PROGMEM = {
+ 31, 31, 34, 40, 42, 51, 46, 46, 42, 42, 42, 39, /* long block, Main/LC */
+ 28, 28, 27, 26, 26, 26, 29, 29, 23, 23, 23, 19, /* long block, SSR */
+};
+
+const uint8_t tnsMaxOrderLong[3] PROGMEM = {20, 12, 12};
+
+
+/* k0Tab[sampRateIdx][k] = k0 = startMin + offset(bs_start_freq) for given sample rate (4.6.18.3.2.1)
+ * downsampled (single-rate) SBR not currently supported
+ */
+const uint8_t k0Tab[NUM_SAMPLE_RATES_SBR][16] = {
+ { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 23, 27, 31 }, /* 96 kHz */
+ { 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 23, 27, 31 }, /* 88 kHz */
+ { 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 23, 26, 30 }, /* 64 kHz */
+ { 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 27, 31 }, /* 48 kHz */
+ { 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 23, 25, 28, 32 }, /* 44 kHz */
+ { 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 32 }, /* 32 kHz */
+ { 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 32 }, /* 24 kHz */
+ { 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 30 }, /* 22 kHz */
+ { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }, /* 16 kHz */
+};
+
+
+/* k2Tab[sampRateIdx][k] = stopVector(bs_stop_freq) for given sample rate, bs_stop_freq = [0, 13] (4.6.18.3.2.1)
+ * generated with Matlab script calc_stopvec.m
+ * downsampled (single-rate) SBR not currently supported
+ */
+const uint8_t k2Tab[NUM_SAMPLE_RATES_SBR][14] = {
+ { 13, 15, 17, 19, 21, 24, 27, 31, 35, 39, 44, 50, 57, 64 }, /* 96 kHz */
+ { 15, 17, 19, 21, 23, 26, 29, 33, 37, 41, 46, 51, 57, 64 }, /* 88 kHz */
+ { 20, 22, 24, 26, 28, 31, 34, 37, 41, 45, 49, 54, 59, 64 }, /* 64 kHz */
+ { 21, 23, 25, 27, 29, 32, 35, 38, 41, 45, 49, 54, 59, 64 }, /* 48 kHz */
+ { 23, 25, 27, 29, 31, 34, 37, 40, 43, 47, 51, 55, 59, 64 }, /* 44 kHz */
+ { 32, 34, 36, 38, 40, 42, 44, 46, 49, 52, 55, 58, 61, 64 }, /* 32 kHz */
+ { 32, 34, 36, 38, 40, 42, 44, 46, 49, 52, 55, 58, 61, 64 }, /* 24 kHz */
+ { 35, 36, 38, 40, 42, 44, 46, 48, 50, 52, 55, 58, 61, 64 }, /* 22 kHz */
+ { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64 }, /* 16 kHz */
+};
+
+const HuffInfo_t huffTabSBRInfo[10] PROGMEM = {
+ {19, { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 2, 7, 4, 8, 72, 0}, 0},
+ {20, { 0, 2, 2, 2, 2, 2, 1, 3, 3, 2, 4, 4, 4, 3, 2, 5, 6, 13, 15, 46}, 121},
+ {17, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 1, 25, 10, 0, 0, 0}, 242},
+ {19, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 3, 1, 0, 1, 1, 2, 1, 29, 2, 0}, 291},
+ {19, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 1, 2, 5, 1, 4, 2, 3, 34, 0}, 340},
+ {20, { 1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2, 1, 2, 3, 4, 4, 7, 10, 16}, 403},
+ {14, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 13, 2, 0, 0, 0, 0, 0, 0}, 466},
+ {14, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 6, 8, 0, 0, 0, 0, 0, 0}, 491},
+ {14, { 1, 1, 1, 1, 1, 1, 0, 2, 0, 1, 1, 0, 51, 2, 0, 0, 0, 0, 0, 0}, 516},
+ { 8, { 1, 1, 1, 0, 1, 1, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 579},
+};
+
+/* Huffman tables from appendix 4.A.6.1, includes offset of -LAV[i] for table i */
+const int16_t huffTabSBR[604] PROGMEM = {
+ /* SBR table sbr_tenv15 [121] (signed) */
+ 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8,
+ -9, 8, -10, 9, -11, 10, -12, -13, 11, -14, 12, -15, -16, 13, -19, -18,
+ -17, 14, -24, -20, 16, -26, -21, 15, -23, -25, -22, -60, -59, -58, -57, -56,
+ -55, -54, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40,
+ -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, -27, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60,
+ /* SBR table sbr_fenv15 [121] (signed) */
+ 0, -1, 1, -2, -3, 2, -4, 3, -5, 4, -6, 5, -7, 6, -8, 7,
+ -9, 8, -10, 9, -11, 10, 11, -12, 12, -13, 13, 14, -14, -15, 15, 16,
+ 17, -16, -17, -18, -19, 18, 19, -20, -21, 20, 21, -24, -23, -22, -26, -28,
+ 22, 23, 25, -41, -25, 26, 27, -30, -27, 24, 28, 44, -51, -46, -44, -43,
+ -37, -33, -31, -29, 30, 37, 42, 47, 48, -60, -59, -58, -57, -56, -55, -54,
+ -53, -52, -50, -49, -48, -47, -45, -42, -40, -39, -38, -36, -35, -34, -32, 29,
+ 31, 32, 33, 34, 35, 36, 38, 39, 40, 41, 43, 45, 46, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60,
+ /* SBR table sbr_tenv15b [49] (signed) */
+ 0, 1, -1, 2, -2, 3, -3, 4, -4, -5, 5, -6, 6, 7, -7, 8,
+ -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11, -10, -9,
+ -8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24,
+ /* SBR table sbr_fenv15b [49] (signed) */
+ 0, -1, 1, -2, 2, 3, -3, -4, 4, -5, 5, -6, 6, -7, 7, 8,
+ -9, -8, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12, -11,
+ -10, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24,
+ /* SBR table sbr_tenv30 [63] (signed) */
+ 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, -7, 6, -8, 7,
+ -9, -10, 8, 9, 10, -13, -11, -12, -14, 11, 12, -31, -30, -29, -28, -27,
+ -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ /* SBR table sbr_fenv30 [63] (signed) */
+ 0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5, -6, 6, -7, 7, -8,
+ 8, 9, -9, -10, 10, 11, -11, -12, 12, 13, -13, -15, 14, 15, -14, 18,
+ -18, -24, -19, 16, 17, -22, -21, -16, 20, 21, 22, 25, -23, -20, 24, -31,
+ -30, -29, -28, -27, -26, -25, -17, 19, 23, 26, 27, 28, 29, 30, 31,
+ /* SBR table sbr_tenv30b [25] (signed) */
+ 0, 1, -1, -2, 2, 3, -3, -4, 4, -5, -12, -11, -10, -9, -8, -7,
+ -6, 5, 6, 7, 8, 9, 10, 11, 12,
+ /* SBR table sbr_fenv30b [25] (signed) */
+ 0, -1, 1, -2, 2, 3, -3, -4, 4, -5, 5, 6, -12, -11, -10, -9,
+ -8, -7, -6, 7, 8, 9, 10, 11, 12,
+ /* SBR table sbr_tnoise30 [63] (signed) */
+ 0, 1, -1, -2, 2, -3, 3, -4, 4, -5, 5, 11, -31, -30, -29, -28,
+ -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12,
+ -11, -10, -9, -8, -7, -6, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ /* SBR table sbr_tnoise30b [25] (signed) */
+ 0, -1, 1, -2, 2, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, 3,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12,
+};
+
+/* newBWTab[prev invfMode][curr invfMode], format = Q31 (table 4.158)
+ * sample file which uses all of these: al_sbr_sr_64_2_fsaac32.aac
+ */
+static const int32_t newBWTab[4][4] PROGMEM = {
+ {0x00000000, 0x4ccccccd, 0x73333333, 0x7d70a3d7},
+ {0x4ccccccd, 0x60000000, 0x73333333, 0x7d70a3d7},
+ {0x00000000, 0x60000000, 0x73333333, 0x7d70a3d7},
+ {0x00000000, 0x60000000, 0x73333333, 0x7d70a3d7},
+};
+
+/* NINT(2.048E6 / Fs) (figure 4.47)
+ * downsampled (single-rate) SBR not currently supported
+ */
+const uint8_t goalSBTab[NUM_SAMPLE_RATES_SBR] = {
+ 21, 23, 32, 43, 46, 64, 85, 93, 128
+};
+
+/* twiddle table for radix 4 pass, format = Q31 */
+static const uint32_t twidTabOdd32[8*6] = {
+ 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x40000000, 0x00000000, 0x539eba45, 0xe7821d59,
+ 0x4b418bbe, 0xf383a3e2, 0x58c542c5, 0xdc71898d, 0x5a82799a, 0xd2bec333, 0x539eba45, 0xe7821d59,
+ 0x539eba45, 0xc4df2862, 0x539eba45, 0xc4df2862, 0x58c542c5, 0xdc71898d, 0x3248d382, 0xc13ad060,
+ 0x40000000, 0xc0000000, 0x5a82799a, 0xd2bec333, 0x00000000, 0xd2bec333, 0x22a2f4f8, 0xc4df2862,
+ 0x58c542c5, 0xcac933ae, 0xcdb72c7e, 0xf383a3e2, 0x00000000, 0xd2bec333, 0x539eba45, 0xc4df2862,
+ 0xac6145bb, 0x187de2a7, 0xdd5d0b08, 0xe7821d59, 0x4b418bbe, 0xc13ad060, 0xa73abd3b, 0x3536cc52,
+};
+
+/* PostMultiply64() table
+ * format = Q30
+ * reordered for sequential access
+ *
+ * for (i = 0; i <= (32/2); i++) {
+ * angle = i * M_PI / 64;
+ * x = (cos(angle) + sin(angle));
+ * x = sin(angle);
+ * }
+ */
+static const int32_t cos1sin1tab64[34] PROGMEM = {
+ 0x40000000, 0x00000000, 0x43103085, 0x0323ecbe, 0x45f704f7, 0x0645e9af, 0x48b2b335, 0x09640837,
+ 0x4b418bbe, 0x0c7c5c1e, 0x4da1fab5, 0x0f8cfcbe, 0x4fd288dc, 0x1294062f, 0x51d1dc80, 0x158f9a76,
+ 0x539eba45, 0x187de2a7, 0x553805f2, 0x1b5d100a, 0x569cc31b, 0x1e2b5d38, 0x57cc15bc, 0x20e70f32,
+ 0x58c542c5, 0x238e7673, 0x5987b08a, 0x261feffa, 0x5a12e720, 0x2899e64a, 0x5a6690ae, 0x2afad269,
+ 0x5a82799a, 0x2d413ccd,
+};
+
+/* coefficient table 4.A.87, format = Q31
+ * reordered as:
+ * cTab[0], cTab[64], cTab[128], cTab[192], cTab[256],
+ * cTab[2], cTab[66], cTab[130], cTab[194], cTab[258],
+ * ...
+ * cTab[64], cTab[128], cTab[192], cTab[256], cTab[320]
+ *
+ * NOTE: cTab[1, 2, ... , 318, 319] = cTab[639, 638, ... 322, 321]
+ * except cTab[384] = -cTab[256], cTab[512] = -cTab[128]
+ */
+const uint32_t cTabA[165] PROGMEM = {
+ 0x00000000, 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0xffed978a, 0x006090c4, 0x01fd3ba0, 0x08a24899, 0x311af3a4,
+ 0xfff0065d, 0x006b47fa, 0x024bf7a1, 0x082f552e, 0x33ff670e, 0xffef7b8b, 0x0075fded, 0x029e35b4, 0x07a8127d, 0x36e69691,
+ 0xffee1650, 0x00807994, 0x02f3e48d, 0x070bbf58, 0x39ce0477, 0xffecc31b, 0x008a7dd7, 0x034d01f0, 0x06593912, 0x3cb41219,
+ 0xffeb50b2, 0x009424c6, 0x03a966bb, 0x0590a67d, 0x3f962fb8, 0xffe9ca76, 0x009d10bf, 0x04083fec, 0x04b0adcb, 0x4272a385,
+ 0xffe88ba8, 0x00a520bb, 0x04694101, 0x03b8f8dc, 0x4547daea, 0xffe79e16, 0x00abe79e, 0x04cc2fcf, 0x02a99097, 0x4812f848,
+ 0xffe6d466, 0x00b1978d, 0x05303f87, 0x01816e06, 0x4ad237a2, 0xffe65416, 0x00b5c867, 0x05950122, 0x0040c496, 0x4d83976c,
+ 0xffe66dd0, 0x00b8394b, 0x05f9c051, 0xfee723c6, 0x5024d70e, 0xffe69423, 0x00b8c6b0, 0x065dd56a, 0xfd7475d8, 0x52b449de,
+ 0xffe75361, 0x00b73ab0, 0x06c0f0c0, 0xfbe8f5bd, 0x552f8ff7, 0xffe85b4b, 0x00b36acd, 0x0721bf22, 0xfa44a069, 0x579505f5,
+ 0xffea353a, 0x00acbd2f, 0x077fedb3, 0xf887507c, 0x59e2f69e, 0xffec8409, 0x00a3508f, 0x07da2b7f, 0xf6b1f3c3, 0x5c16d0ae,
+ 0xffef2395, 0x0096dcc2, 0x08303897, 0xf4c473c6, 0x5e2f6367, 0xfff294c3, 0x00872c63, 0x0880ffdd, 0xf2bf6ea4, 0x602b0c7f,
+ 0xfff681d6, 0x007400b8, 0x08cb4e23, 0xf0a3959f, 0x6207f220, 0xfffb42b0, 0x005d36df, 0x090ec1fc, 0xee71b2fe, 0x63c45243,
+ 0x00007134, 0x00426f36, 0x0949eaac, 0xec2a3f5f, 0x655f63f2, 0x0006b1cf, 0x0023b989, 0x097c1ee8, 0xe9cea84a, 0x66d76725,
+ 0x000d31b5, 0x0000e790, 0x09a3e163, 0xe75f8bb8, 0x682b39a4, 0x001471f8, 0xffda17f2, 0x09c0e59f, 0xe4de0cb0, 0x6959709d,
+ 0x001c3549, 0xffaea5d6, 0x09d19ca9, 0xe24b8f66, 0x6a619c5e, 0x0024dd50, 0xff7ee3f1, 0x09d5560b, 0xdfa93ab5, 0x6b42a864,
+ 0x002d8e42, 0xff4aabc8, 0x09caeb0f, 0xdcf898fb, 0x6bfbdd98, 0x003745f9, 0xff120d70, 0x09b18a1d, 0xda3b176a, 0x6c8c4c7a,
+ 0x004103f4, 0xfed4bec3, 0x09881dc5, 0xd7722f04, 0x6cf4073e, 0x004b6c46, 0xfe933dc0, 0x094d7ec2, 0xd49fd55f, 0x6d32730f,
+ 0x0055dba1, 0x01b2e41d, 0x09015651, 0x2e3a7532, 0x6d474e1d,
+};
+
+/* PreMultiply64() table
+ * format = Q30
+ * reordered for sequential access
+ *
+ * for (i = 0; i < 64/4; i++) {
+ * angle = (i + 0.25) * M_PI / nmdct;
+ * x = (cos(angle) + sin(angle));
+ * x = sin(angle);
+ *
+ * angle = (nmdct/2 - 1 - i + 0.25) * M_PI / nmdct;
+ * x = (cos(angle) + sin(angle));
+ * x = sin(angle);
+ * }
+ */
+static const int32_t cos4sin4tab64[64] PROGMEM = {
+ 0x40c7d2bd, 0x00c90e90, 0x424ff28f, 0x3ff4e5e0, 0x43cdd89a, 0x03ecadcf, 0x454149fc, 0x3fc395f9,
+ 0x46aa0d6d, 0x070de172, 0x4807eb4b, 0x3f6af2e3, 0x495aada2, 0x0a2abb59, 0x4aa22036, 0x3eeb3347,
+ 0x4bde1089, 0x0d415013, 0x4d0e4de2, 0x3e44a5ef, 0x4e32a956, 0x104fb80e, 0x4f4af5d1, 0x3d77b192,
+ 0x50570819, 0x135410c3, 0x5156b6d9, 0x3c84d496, 0x5249daa2, 0x164c7ddd, 0x53304df6, 0x3b6ca4c4,
+ 0x5409ed4b, 0x19372a64, 0x54d69714, 0x3a2fcee8, 0x55962bc0, 0x1c1249d8, 0x56488dc5, 0x38cf1669,
+ 0x56eda1a0, 0x1edc1953, 0x57854ddd, 0x374b54ce, 0x580f7b19, 0x2192e09b, 0x588c1404, 0x35a5793c,
+ 0x58fb0568, 0x2434f332, 0x595c3e2a, 0x33de87de, 0x59afaf4c, 0x26c0b162, 0x59f54bee, 0x31f79948,
+ 0x5a2d0957, 0x29348937, 0x5a56deec, 0x2ff1d9c7, 0x5a72c63b, 0x2b8ef77d, 0x5a80baf6, 0x2dce88aa,
+};
+
+/* invBandTab[i] = 1.0 / (i + 1), Q31 */
+static const int32_t invBandTab[64] PROGMEM = {
+ 0x7fffffff, 0x40000000, 0x2aaaaaab, 0x20000000, 0x1999999a, 0x15555555, 0x12492492, 0x10000000,
+ 0x0e38e38e, 0x0ccccccd, 0x0ba2e8ba, 0x0aaaaaab, 0x09d89d8a, 0x09249249, 0x08888889, 0x08000000,
+ 0x07878788, 0x071c71c7, 0x06bca1af, 0x06666666, 0x06186186, 0x05d1745d, 0x0590b216, 0x05555555,
+ 0x051eb852, 0x04ec4ec5, 0x04bda12f, 0x04924925, 0x0469ee58, 0x04444444, 0x04210842, 0x04000000,
+ 0x03e0f83e, 0x03c3c3c4, 0x03a83a84, 0x038e38e4, 0x03759f23, 0x035e50d8, 0x03483483, 0x03333333,
+ 0x031f3832, 0x030c30c3, 0x02fa0be8, 0x02e8ba2f, 0x02d82d83, 0x02c8590b, 0x02b93105, 0x02aaaaab,
+ 0x029cbc15, 0x028f5c29, 0x02828283, 0x02762762, 0x026a439f, 0x025ed098, 0x0253c825, 0x02492492,
+ 0x023ee090, 0x0234f72c, 0x022b63cc, 0x02222222, 0x02192e2a, 0x02108421, 0x02082082, 0x02000000,
+};
+
+static const uint32_t poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 };
+static const uint32_t poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 };
+
+/* pow2exp[i] = pow(2, i*4/3) exponent */
+static const uint16_t pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 };
+
+/* pow2exp[i] = pow(2, i*4/3) fraction */
+static const int32_t pow2frac[8] PROGMEM = {
+ 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94,
+ 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6
+};
+
+/* pow(2, i/4.0) for i = [0,1,2,3], format = Q30 */
+static const int32_t pow14[4] PROGMEM = {
+ 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65
+};
+
+/* pow(2, i/4.0) * pow(j, 4.0/3.0) for i = [0,1,2,3], j = [0,1,2,...,15]
+ * format = Q28 for j = [0-3], Q25 for j = [4-15]
+ */
+static const uint32_t pow43_14[4][16] PROGMEM = {
+ {
+ 0x00000000, 0x10000000, 0x285145f3, 0x453a5cdb, /* Q28 */
+ 0x0cb2ff53, 0x111989d6, 0x15ce31c8, 0x1ac7f203, /* Q25 */
+ 0x20000000, 0x257106b9, 0x2b16b4a3, 0x30ed74b4, /* Q25 */
+ 0x36f23fa5, 0x3d227bd3, 0x437be656, 0x49fc823c, /* Q25 */
+ },
+ {
+ 0x00000000, 0x1306fe0a, 0x2ff221af, 0x52538f52,
+ 0x0f1a1bf4, 0x1455ccc2, 0x19ee62a8, 0x1fd92396,
+ 0x260dfc14, 0x2c8694d8, 0x333dcb29, 0x3a2f5c7a,
+ 0x4157aed5, 0x48b3aaa3, 0x50409f76, 0x57fc3010,
+ },
+ {
+ 0x00000000, 0x16a09e66, 0x39047c0f, 0x61e734aa,
+ 0x11f59ac4, 0x182ec633, 0x1ed66a45, 0x25dfc55a,
+ 0x2d413ccd, 0x34f3462d, 0x3cefc603, 0x4531ab69,
+ 0x4db4adf8, 0x56752054, 0x5f6fcfcd, 0x68a1eca1,
+ },
+ {
+ 0x00000000, 0x1ae89f99, 0x43ce3e4b, 0x746d57b2,
+ 0x155b8109, 0x1cc21cdc, 0x24ac1839, 0x2d0a479e,
+ 0x35d13f33, 0x3ef80748, 0x48775c93, 0x524938cd,
+ 0x5c68841d, 0x66d0df0a, 0x717e7bfe, 0x7c6e0305,
+ },
+};
+
+/* pow(j, 4.0 / 3.0) for j = [16,17,18,...,63], format = Q23 */
+static const int32_t pow43[48] PROGMEM = {
+ 0x1428a2fa, 0x15db1bd6, 0x1796302c, 0x19598d85,
+ 0x1b24e8bb, 0x1cf7fcfa, 0x1ed28af2, 0x20b4582a,
+ 0x229d2e6e, 0x248cdb55, 0x26832fda, 0x28800000,
+ 0x2a832287, 0x2c8c70a8, 0x2e9bc5d8, 0x30b0ff99,
+ 0x32cbfd4a, 0x34eca001, 0x3712ca62, 0x393e6088,
+ 0x3b6f47e0, 0x3da56717, 0x3fe0a5fc, 0x4220ed72,
+ 0x44662758, 0x46b03e7c, 0x48ff1e87, 0x4b52b3f3,
+ 0x4daaebfd, 0x5007b497, 0x5268fc62, 0x54ceb29c,
+ 0x5738c721, 0x59a72a59, 0x5c19cd35, 0x5e90a129,
+ 0x610b9821, 0x638aa47f, 0x660db90f, 0x6894c90b,
+ 0x6b1fc80c, 0x6daeaa0d, 0x70416360, 0x72d7e8b0,
+ 0x75722ef9, 0x78102b85, 0x7ab1d3ec, 0x7d571e09,
+};
+
+/* invTab[x] = 1/(x+1), format = Q30 */
+static const int32_t invTab[5] PROGMEM = {0x40000000, 0x20000000, 0x15555555, 0x10000000, 0x0ccccccd};
+
+/* inverse quantization tables for TNS filter coefficients, format = Q31
+ * see bottom of file for table generation
+ * negative (vs. spec) since we use MADD for filter kernel
+ */
+static const uint32_t invQuant3[16] PROGMEM = {
+ 0x00000000, 0xc8767f65, 0x9becf22c, 0x83358feb, 0x83358feb, 0x9becf22c, 0xc8767f65, 0x00000000,
+ 0x2bc750e9, 0x5246dd49, 0x6ed9eba1, 0x7e0e2e32, 0x7e0e2e32, 0x6ed9eba1, 0x5246dd49, 0x2bc750e9,
+};
+
+static const uint32_t invQuant4[16] PROGMEM = {
+ 0x00000000, 0xe5632654, 0xcbf00dbe, 0xb4c373ee, 0xa0e0a15f, 0x9126145f, 0x8643c7b3, 0x80b381ac,
+ 0x7f7437ad, 0x7b1d1a49, 0x7294b5f2, 0x66256db2, 0x563ba8aa, 0x4362210e, 0x2e3d2abb, 0x17851aad,
+};
+
+static const int8_t sgnMask[3] = {0x02, 0x04, 0x08};
+static const int8_t negMask[3] = {~0x03, ~0x07, ~0x0f};
+
+/***********************************************************************************************************************
+ * Function: AACDecoder_AllocateBuffers
+ *
+ * Description: allocate all the memory needed for the AAC decoder
+ * try heap first, because it's faster
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: false if not enough memory, otherwise true
+ *
+ **********************************************************************************************************************/
+
+#ifdef CONFIG_IDF_TARGET_ESP32S3
+ // ESP32-S3: If there is PSRAM, prefer it
+ #define __malloc_heap_psram(size) \
+ heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL)
+#else
+ // ESP32, PSRAM is too slow, prefer SRAM
+ #define __malloc_heap_psram(size) \
+ heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM)
+#endif
+
+bool AACDecoder_AllocateBuffers(void){
+
+ /* here, sizes are: AACDecInfo_t:96 PSInfoBase_t:27364 ProgConfigElement_t*16:1312 PSInfoSBR_t:50788 */
+#ifdef AAC_ENABLE_SBR
+ if(!m_PSInfoSBR) {m_PSInfoSBR = (PSInfoSBR_t*)__malloc_heap_psram(sizeof(PSInfoSBR_t));}
+
+ if(!m_PSInfoSBR) {
+ log_e("OOM in SBR, can't allocate %d bytes\n", sizeof(PSInfoSBR_t));
+ return false; // ERR_AAC_SBR_INIT;
+ }
+ else {
+ log_d("AAC Spectral Band Replication enabled, %d additional bytes allocated", sizeof(PSInfoSBR_t));
+ }
+#endif
+
+ /* these could fall back to PSRAM if not enough heap available */
+ if(!m_AACDecInfo) {m_AACDecInfo = (AACDecInfo_t*) __malloc_heap_psram(sizeof(AACDecInfo_t));}
+ if(!m_PSInfoBase) {m_PSInfoBase = (PSInfoBase_t*) __malloc_heap_psram(sizeof(PSInfoBase_t));}
+ if(!m_pce[0]) {m_pce[0] = (ProgConfigElement_t*) __malloc_heap_psram(sizeof(ProgConfigElement_t)*16);}
+
+ if(!m_AACDecInfo || !m_PSInfoBase || !m_pce[0]) {
+ log_e("not enough memory to allocate aacdecoder buffers");
+ AACDecoder_FreeBuffers();
+ return false;
+ }
+
+ // Clear Buffer
+ memset( m_AACDecInfo, 0, sizeof(AACDecInfo_t)); //Clear AACDecInfo
+ memset( m_PSInfoBase, 0, sizeof(PSInfoBase_t)); //Clear PSInfoBase
+ memset(&m_AACFrameInfo, 0, sizeof(AACFrameInfo_t)); //Clear AACFrameInfo
+ memset(&m_fhADTS, 0, sizeof(ADTSHeader_t)); //Clear fhADTS
+ memset(&m_fhADIF, 0, sizeof(ADIFHeader_t)); //Clear fhADIS
+ memset( m_pce[0], 0, sizeof(ProgConfigElement_t) * 16); //Clear ProgConfigElement
+ memset(&m_pulseInfo[0], 0, sizeof(PulseInfo_t) *2); //Clear PulseInfo
+ memset(&m_aac_BitStreamInfo, 0, sizeof(aac_BitStreamInfo_t)); //Clear aac_BitStreamInfo
+#ifdef AAC_ENABLE_SBR
+ memset( m_PSInfoSBR, 0, sizeof(PSInfoSBR_t)); //Clear PSInfoSBR
+ InitSBRState();
+#endif
+
+ m_AACDecInfo->prevBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currInstTag = -1;
+ for(int32_t ch = 0; ch < MAX_NCHANS_ELEM; ch++)
+ m_AACDecInfo->sbDeinterleaveReqd[ch] = 0;
+ m_AACDecInfo->adtsBlocksLeft = 0;
+ m_AACDecInfo->tnsUsed = 0;
+ m_AACDecInfo->pnsUsed = 0;
+
+ return true;
+}
+
+/**************************************************************************************
+ * Function: AACFlushCodec
+ *
+ * Description: flush internal codec state (after seeking, for example)
+ *
+ * Inputs: valid AAC decoder instance pointer (HAACDecoder)
+ *
+ * Outputs: updated state variables in aacDecInfo
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ **************************************************************************************/
+int32_t AACFlushCodec()
+{
+ int32_t ch;
+
+ if (!m_AACDecInfo)
+ return ERR_AAC_NULL_POINTER;
+
+ /* reset common state variables which change per-frame
+ * don't touch state variables which are (usually) constant for entire clip
+ * (nChans, sampRate, profile, format, sbrEnabled)
+ */
+ m_AACDecInfo->prevBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currInstTag = -1;
+ for (ch = 0; ch < MAX_NCHANS_ELEM; ch++)
+ m_AACDecInfo->sbDeinterleaveReqd[ch] = 0;
+ m_AACDecInfo->adtsBlocksLeft = 0;
+ m_AACDecInfo->tnsUsed = 0;
+ m_AACDecInfo->pnsUsed = 0;
+
+ /* reset internal codec state (flush overlap buffers, etc.) */
+ memset(m_PSInfoBase->overlap, 0, AAC_MAX_NCHANS * AAC_MAX_NSAMPS * sizeof(int32_t));
+ memset(m_PSInfoBase->prevWinShape, 0, AAC_MAX_NCHANS * sizeof(int32_t));
+
+ return ERR_AAC_NONE;
+}
+/***********************************************************************************************************************
+ * Function: AACDecoder_FreeBuffers
+ *
+ * Description: allocate all the memory needed for the AAC decoder
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: none
+
+ **********************************************************************************************************************/
+void AACDecoder_FreeBuffers(void) {
+
+// uint32_t i = ESP.getFreeHeap();
+
+ if(m_AACDecInfo) {free(m_AACDecInfo); m_AACDecInfo=NULL;}
+ if(m_PSInfoBase) {free(m_PSInfoBase); m_PSInfoBase=NULL;}
+ if(m_pce[0]) {free(m_pce[0]); m_pce[0]=NULL;}
+
+#ifdef AAC_ENABLE_SBR
+ if(m_PSInfoSBR) {free(m_PSInfoSBR); m_PSInfoSBR=NULL;} //Clear AACDecInfo
+#endif
+
+// log_i("AACDecoder: %lu bytes memory was freed", ESP.getFreeHeap() - i);
+}
+
+/***********************************************************************************************************************
+ * Function: AACDecoder_IsInit
+ *
+ * Description: returns AAC decoder initialization status
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: true if buffers allocated, otherwise false
+
+ **********************************************************************************************************************/
+bool AACDecoder_IsInit(void) {
+ if(m_AACDecInfo && m_PSInfoBase && m_pce[0]){
+ return true;
+ }
+ return false;
+}
+
+/***********************************************************************************************************************
+ * Function: AACDecoder_FreeBuffers
+ *
+ * Description: allocate all the memory needed for the AAC decoder
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: none
+
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: AACFindSyncWord
+ *
+ * Description: locate the next byte-alinged sync word in the raw AAC stream
+ *
+ * Inputs: buffer to search for sync word
+ * max number of bytes to search in buffer
+ *
+ * Outputs: none
+ *
+ * Return: offset to first sync word (bytes from start of buf)
+ * -1 if sync not found after searching nBytes
+ **********************************************************************************************************************/
+int32_t AACFindSyncWord(uint8_t *buf, int32_t nBytes)
+{
+ int32_t i;
+
+ /* find byte-aligned syncword (12 bits = 0xFFF) */
+ for (i = 0; i < nBytes - 1; i++) {
+ if ( (buf[i+0] & SYNCWORDH) == SYNCWORDH && (buf[i+1] & SYNCWORDL) == SYNCWORDL )
+ return i;
+ }
+
+ return -1;
+}
+//**************************************************************************************
+int32_t AACGetSampRate(){return m_AACDecInfo->sampRate * (m_AACDecInfo->sbrEnabled ? 2 : 1);}
+int32_t AACGetChannels(){return m_AACDecInfo->nChans;}
+int32_t AACGetBitsPerSample(){return 16;}
+int32_t AACGetID() {return m_AACDecInfo->id;} // 0-MPEG4, 1-MPEG2
+uint8_t AACGetProfile() {return (uint8_t)m_AACDecInfo->profile;} // 0-Main, 1-LC, 2-SSR, 3-reserved
+uint8_t AACGetFormat() {return (uint8_t)m_AACDecInfo->format;} // 0-unknown 1-ADTS 2-ADIF, 3-RAW
+int32_t AACGetOutputSamps(){return m_AACDecInfo->nChans * AAC_MAX_NSAMPS * (m_AACDecInfo->sbrEnabled ? 2 : 1);}
+int32_t AACGetBitrate() {
+ uint32_t br = AACGetBitsPerSample() * AACGetChannels() * AACGetSampRate();
+ return (br / m_AACDecInfo->compressionRatio);
+}
+/**************************************************************************************
+ * Function: AACSetRawBlockParams
+ *
+ * Description: set internal state variables for decoding a stream of raw data blocks
+ *
+ * Inputs: flag indicating source of parameters
+ * nChans, sampRate,
+ * and profile 0 = main, 1 = LC, 2 = SSR, 3 = reserved
+ * optionally filled-in
+ *
+ * Outputs: updated codec state
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ *
+ * Notes: if copyLast == 1, then the codec sets up its internal state (for
+ * decoding raw blocks) based on previously-decoded ADTS header info
+ * if copyLast == 0, then the codec uses the values passed in
+ * aacFrameInfo to configure its internal state (useful when the
+ * source is MP4 format, for example)
+ **************************************************************************************/
+int32_t AACSetRawBlockParams(int32_t nChans, int32_t sampRateCore, int32_t profile, int32_t copyLast)
+{
+ if (!m_AACDecInfo)
+ return ERR_AAC_NULL_POINTER;
+
+ m_AACDecInfo->format = AAC_FF_RAW;
+ if (copyLast)
+ return SetRawBlockParams(1, 0, 0, 0);
+ else
+ return SetRawBlockParams(0, nChans, sampRateCore, profile);
+}
+
+/***********************************************************************************************************************
+ * Function: AACDecode
+ *
+ * Description: decode AAC frame
+ *
+ * Inputs: double pointer to buffer of AAC data
+ * pointer to number of valid bytes remaining in inbuf
+ * pointer to outbuf, big enough to hold one frame of decoded PCM samples
+ *
+ * Outputs: PCM data in outbuf, interleaved LRLRLR... if stereo
+ * number of output samples = 1024 per channel
+ * updated inbuf pointer
+ * updated bytesLeft
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ *
+ * Notes: inbuf pointer and bytesLeft are not updated until whole frame is
+ * successfully decoded, so if ERR_AAC_INDATA_UNDERFLOW is returned
+ * just call AACDecode again with more data in inbuf
+ **********************************************************************************************************************/
+int32_t AACDecode(uint8_t *inbuf, int32_t *bytesLeft, int16_t *outbuf)
+{
+ int32_t err, offset, bitOffset, bitsAvail;
+ int32_t ch, baseChan, elementChans;
+ uint8_t *inptr;
+
+#ifdef AAC_ENABLE_SBR
+ int32_t baseChanSBR, elementChansSBR;
+#endif
+
+ /* make local copies (see "Notes" above) */
+ inptr = inbuf;
+ bitOffset = 0;
+ bitsAvail = (*bytesLeft) << 3;
+
+ /* first time through figure out what the file format is */
+ if (m_AACDecInfo->format == AAC_FF_Unknown) {
+ if (bitsAvail < 32)
+ return ERR_AAC_INDATA_UNDERFLOW;
+
+ if ((inptr)[0] == 'A' && (inptr)[1] == 'D' && (inptr)[2] == 'I' && (inptr)[3] == 'F') {
+ /* unpack ADIF header */
+ m_AACDecInfo->format = AAC_FF_ADIF;
+ err = UnpackADIFHeader(&inptr, &bitOffset, &bitsAvail);
+ if (err)
+ return err;
+ } else {
+ /* assume ADTS by default */
+ m_AACDecInfo->format = AAC_FF_ADTS;
+ }
+ }
+ /* if ADTS, search for start of next frame */
+ if (m_AACDecInfo->format == AAC_FF_ADTS) {
+ /* can have 1-4 raw data blocks per ADTS frame (header only present for first one) */
+ if (m_AACDecInfo->adtsBlocksLeft == 0) {
+ offset = AACFindSyncWord(inptr, bitsAvail >> 3);
+ if (offset < 0)
+ return ERR_AAC_INDATA_UNDERFLOW;
+ inptr += offset;
+ bitsAvail -= (offset << 3);
+
+ err = UnpackADTSHeader(&inptr, &bitOffset, &bitsAvail);
+ if (err)
+ return err;
+
+ if (m_AACDecInfo->nChans == -1) {
+ /* figure out implicit channel mapping if necessary */
+ err = GetADTSChannelMapping(inptr, bitOffset, bitsAvail);
+ if (err)
+ return err;
+ }
+ }
+ m_AACDecInfo->adtsBlocksLeft--;
+ } else if (m_AACDecInfo->format == AAC_FF_RAW) {
+ err = PrepareRawBlock();
+ if (err)
+ return err;
+ }
+
+ /* check for valid number of channels */
+ if (m_AACDecInfo->nChans > AAC_MAX_NCHANS || m_AACDecInfo->nChans <= 0)
+ return ERR_AAC_NCHANS_TOO_HIGH;
+
+ /* will be set later if active in this frame */
+ m_AACDecInfo->tnsUsed = 0;
+ m_AACDecInfo->pnsUsed = 0;
+
+ bitOffset = 0;
+ baseChan = 0;
+
+#ifdef AAC_ENABLE_SBR
+ baseChanSBR = 0;
+#endif
+
+ do {
+ /* parse next syntactic element */
+ if(bitsAvail < 0) return ERR_AAC_INDATA_UNDERFLOW;
+ err = DecodeNextElement(&inptr, &bitOffset, &bitsAvail);
+ if (err)
+ return err;
+
+ elementChans = elementNumChans[m_AACDecInfo->currBlockID];
+ if (baseChan + elementChans > AAC_MAX_NCHANS)
+ return ERR_AAC_NCHANS_TOO_HIGH;
+
+ /* noiseless decoder and dequantizer */
+ for (ch = 0; ch < elementChans; ch++) {
+ err = DecodeNoiselessData(&inptr, &bitOffset, &bitsAvail, ch);
+
+ if (err)
+ return err;
+
+ if (AACDequantize(ch))
+ return ERR_AAC_DEQUANT;
+ }
+
+ /* mid-side and intensity stereo */
+ if (m_AACDecInfo->currBlockID == AAC_ID_CPE) {
+ if (StereoProcess())
+ return ERR_AAC_STEREO_PROCESS;
+ }
+
+ /* PNS, TNS, inverse transform */
+ for (ch = 0; ch < elementChans; ch++) {
+
+ if (PNS(ch))
+ return ERR_AAC_PNS;
+
+ if (m_AACDecInfo->sbDeinterleaveReqd[ch]) {
+ /* deinterleave short blocks, if required */
+ if (DeinterleaveShortBlocks(ch))
+ return ERR_AAC_SHORT_BLOCK_DEINT;
+ m_AACDecInfo->sbDeinterleaveReqd[ch] = 0;
+ }
+
+ if (TNSFilter(ch))
+ return ERR_AAC_TNS;
+
+ if (IMDCT(ch, baseChan + ch, outbuf))
+ return ERR_AAC_IMDCT;
+ }
+
+#ifdef AAC_ENABLE_SBR
+ if (m_AACDecInfo->sbrEnabled && (m_AACDecInfo->currBlockID == AAC_ID_FIL ||
+ m_AACDecInfo->currBlockID == AAC_ID_LFE)) {
+ if (m_AACDecInfo->currBlockID == AAC_ID_LFE)
+ elementChansSBR = elementNumChans[AAC_ID_LFE];
+ else if (m_AACDecInfo->currBlockID == AAC_ID_FIL && (m_AACDecInfo->prevBlockID == AAC_ID_SCE ||
+ m_AACDecInfo->prevBlockID == AAC_ID_CPE))
+ elementChansSBR = elementNumChans[m_AACDecInfo->prevBlockID];
+ else
+ elementChansSBR = 0;
+
+ if (baseChanSBR + elementChansSBR > AAC_MAX_NCHANS)
+ return ERR_AAC_SBR_NCHANS_TOO_HIGH;
+
+ /* parse SBR extension data if present (contained in a fill element) */
+ if (DecodeSBRBitstream(baseChanSBR))
+ return ERR_AAC_SBR_BITSTREAM;
+
+ /* apply SBR */
+ if (DecodeSBRData(baseChanSBR, outbuf))
+ return ERR_AAC_SBR_DATA;
+
+ baseChanSBR += elementChansSBR;
+ }
+#endif
+
+ baseChan += elementChans;
+ } while (m_AACDecInfo->currBlockID != AAC_ID_END);
+
+ /* byte align after each raw_data_block */
+ if (bitOffset) {
+ inptr++;
+ bitsAvail -= (8-bitOffset);
+ bitOffset = 0;
+ if (bitsAvail < 0)
+ return ERR_AAC_INDATA_UNDERFLOW;
+ }
+
+ m_AACDecInfo->compressionRatio = (float)(AACGetOutputSamps()) * 2 / (inptr - inbuf);
+
+ /* update pointers */
+ m_AACDecInfo->frameCount++;
+ *bytesLeft -= (inptr - inbuf);
+ inbuf = inptr;
+
+ return ERR_AAC_NONE;
+}
+/***********************************************************************************************************************
+ * Function: DecodeLPCCoefs
+ *
+ * Description: decode LPC coefficients for TNS
+ *
+ * Inputs: order of TNS filter
+ * resolution of coefficients (3 or 4 bits)
+ * coefficients unpacked from bitstream
+ * scratch buffer (b) of size >= order
+ *
+ * Outputs: LPC coefficients in Q(FBITS_LPC_COEFS), in 'a'
+ *
+ * Return: none
+ *
+ * Notes: assumes no guard bits in input transform coefficients
+ * a[i] = Q(FBITS_LPC_COEFS), don't store a0 = 1.0
+ * (so a[0] = first delay tap, etc.)
+ * max abs(a[i]) < log2(order), so for max order = 20 a[i] < 4.4
+ * (up to 3 bits of gain) so a[i] has at least 31 - FBITS_LPC_COEFS - 3
+ * guard bits
+ * to ensure no intermediate overflow in all-pole filter, set
+ * FBITS_LPC_COEFS such that number of guard bits >= log2(max order)
+ **********************************************************************************************************************/
+void DecodeLPCCoefs(int32_t order, int32_t res, int8_t *filtCoef, int32_t *a, int32_t *b)
+{
+ int32_t i, m, t;
+ const uint32_t *invQuantTab;
+
+ if (res == 3) invQuantTab = invQuant3;
+ else if (res == 4) invQuantTab = invQuant4;
+ else return;
+
+ for (m = 0; m < order; m++) {
+ t = invQuantTab[filtCoef[m] & 0x0f]; /* t = Q31 */
+ for (i = 0; i < m; i++)
+ b[i] = a[i] - (MULSHIFT32(t, a[m-i-1]) << 1);
+ for (i = 0; i < m; i++)
+ a[i] = b[i];
+ a[m] = t >> (31 - FBITS_LPC_COEFS);
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: FilterRegion
+ *
+ * Description: apply LPC filter to one region of coefficients
+ *
+ * Inputs: number of transform coefficients in this region
+ * direction flag (forward = 1, backward = -1)
+ * order of filter
+ * 'size' transform coefficients
+ * 'order' LPC coefficients in Q(FBITS_LPC_COEFS)
+ * scratch buffer for history (must be >= order samples long)
+ *
+ * Outputs: filtered transform coefficients
+ *
+ * Return: guard bit mask (OR of abs value of all filtered transform coefs)
+ *
+ * Notes: assumes no guard bits in input transform coefficients
+ * gains 0 int32_t bits
+ * history buffer does not need to be preserved between regions
+ **********************************************************************************************************************/
+int32_t FilterRegion(int32_t size, int32_t dir, int32_t order, int32_t *audioCoef, int32_t *a, int32_t *hist)
+{
+ int32_t i, j, y, hi32, inc, gbMask;
+ U64 sum64;
+
+ /* init history to 0 every time */
+ for (i = 0; i < order; i++)
+ hist[i] = 0;
+
+ sum64.w64 = 0; /* avoid warning */
+ gbMask = 0;
+ inc = (dir ? -1 : 1);
+ do {
+ /* sum64 = a0*y[n] = 1.0*y[n] */
+ y = *audioCoef;
+ sum64.r.hi32 = y >> (32 - FBITS_LPC_COEFS);
+ sum64.r.lo32 = y << FBITS_LPC_COEFS;
+
+ /* sum64 += (a1*y[n-1] + a2*y[n-2] + ... + a[order-1]*y[n-(order-1)]) */
+ for (j = order - 1; j > 0; j--) {
+ sum64.w64 = MADD64(sum64.w64, hist[j], a[j]);
+ hist[j] = hist[j-1];
+ }
+ sum64.w64 = MADD64(sum64.w64, hist[0], a[0]);
+ y = (sum64.r.hi32 << (32 - FBITS_LPC_COEFS)) | (sum64.r.lo32 >> FBITS_LPC_COEFS);
+
+ /* clip output (rare) */
+ hi32 = sum64.r.hi32;
+ if ((hi32 >> 31) != (hi32 >> (FBITS_LPC_COEFS-1)))
+ y = (hi32 >> 31) ^ 0x7fffffff;
+
+ hist[0] = y;
+ *audioCoef = y;
+ audioCoef += inc;
+ gbMask |= FASTABS(y);
+ } while (--size);
+
+ return gbMask;
+}
+
+/***********************************************************************************************************************
+ * Function: TNSFilter
+ *
+ * Description: apply temporal noise shaping, if enabled
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: updated transform coefficients
+ * updated minimum guard bit count for this channel
+ *
+ * Return: 0 if successful, -1 if error
+ **********************************************************************************************************************/
+int32_t TNSFilter(int32_t ch)
+{
+ int32_t win, winLen, nWindows, nSFB, filt, bottom, top, order, maxOrder, dir;
+ int32_t start, end, size, tnsMaxBand, numFilt, gbMask;
+ int32_t *audioCoef;
+ uint8_t *filtLength, *filtOrder, *filtRes, *filtDir;
+ int8_t *filtCoef;
+ const uint16_t *tnsMaxBandTab;
+ const uint16_t *sfbTab;
+ ICSInfo_t *icsInfo;
+ TNSInfo_t *ti;
+
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+ ti = &m_PSInfoBase->tnsInfo[ch];
+
+ if (!ti->tnsDataPresent)
+ return 0;
+
+ if (icsInfo->winSequence == 2) {
+ nWindows = NWINDOWS_SHORT;
+ winLen = NSAMPS_SHORT;
+ nSFB = sfBandTotalShort[m_PSInfoBase->sampRateIdx];
+ maxOrder = tnsMaxOrderShort[m_AACDecInfo->profile];
+ sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx];
+ tnsMaxBandTab = tnsMaxBandsShort + tnsMaxBandsShortOffset[m_AACDecInfo->profile];
+ tnsMaxBand = tnsMaxBandTab[m_PSInfoBase->sampRateIdx];
+ } else {
+ nWindows = NWINDOWS_LONG;
+ winLen = NSAMPS_LONG;
+ nSFB = sfBandTotalLong[m_PSInfoBase->sampRateIdx];
+ maxOrder = tnsMaxOrderLong[m_AACDecInfo->profile];
+ sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx];
+ tnsMaxBandTab = tnsMaxBandsLong + tnsMaxBandsLongOffset[m_AACDecInfo->profile];
+ tnsMaxBand = tnsMaxBandTab[m_PSInfoBase->sampRateIdx];
+ }
+
+ if (tnsMaxBand > icsInfo->maxSFB)
+ tnsMaxBand = icsInfo->maxSFB;
+
+ filtRes = ti->coefRes;
+ filtLength = ti->length;
+ filtOrder = ti->order;
+ filtDir = ti->dir;
+ filtCoef = ti->coef;
+
+ gbMask = 0;
+ audioCoef = m_PSInfoBase->coef[ch];
+ for (win = 0; win < nWindows; win++) {
+ bottom = nSFB;
+ numFilt = ti->numFilt[win];
+ for (filt = 0; filt < numFilt; filt++) {
+ top = bottom;
+ bottom = top - *filtLength++;
+ bottom = MAX(bottom, (int32_t)0);
+ order = *filtOrder++;
+ order = MIN(order, maxOrder);
+
+ if (order) {
+ start = sfbTab[MIN(bottom, tnsMaxBand)];
+ end = sfbTab[MIN(top, tnsMaxBand)];
+ size = end - start;
+ if (size > 0) {
+ dir = *filtDir++;
+ if (dir)
+ start = end - 1;
+
+ DecodeLPCCoefs(order, filtRes[win], filtCoef, m_PSInfoBase->tnsLPCBuf, m_PSInfoBase->tnsWorkBuf);
+ gbMask |= FilterRegion(size, dir, order, audioCoef + start, m_PSInfoBase->tnsLPCBuf,
+ m_PSInfoBase->tnsWorkBuf);
+ }
+ filtCoef += order;
+ }
+ }
+ audioCoef += winLen;
+ }
+
+ /* update guard bit count if necessary */
+ size = CLZ(gbMask) - 1;
+ if (m_PSInfoBase->gbCurrent[ch] > size)
+ m_PSInfoBase->gbCurrent[ch] = size;
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeSingleChannelElement
+ *
+ * Description: decode one SCE
+ *
+ * Inputs: none
+ *
+ * Outputs: updated element instance tag
+ *
+ * Return: 0 if successful, -1 if error
+ *
+ * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData)
+ **********************************************************************************************************************/
+int32_t DecodeSingleChannelElement()
+{
+ /* read instance tag */
+ m_AACDecInfo->currInstTag = GetBits(NUM_INST_TAG_BITS);
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeChannelPairElement
+ *
+ * Description: decode one CPE
+ *
+ * Inputs: none
+ *
+ * Outputs: updated element instance tag
+ * updated commonWin
+ * updated ICS info, if commonWin == 1
+ * updated mid-side stereo info, if commonWin == 1
+ *
+ * Return: 0 if successful, -1 if error
+ *
+ * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData)
+ **********************************************************************************************************************/
+int32_t DecodeChannelPairElement()
+{
+ int32_t sfb, gp, maskOffset;
+ uint8_t currBit, *maskPtr;
+ ICSInfo_t *icsInfo;
+
+
+ icsInfo = m_PSInfoBase->icsInfo;
+
+ /* read instance tag */
+ m_AACDecInfo->currInstTag = GetBits(NUM_INST_TAG_BITS);
+
+ /* read common window flag and mid-side info (if present)
+ * store msMask bits in m_PSInfoBase->msMaskBits[] as follows:
+ * long blocks - pack bits for each SFB in range [0, maxSFB) starting with lsb of msMaskBits[0]
+ * short blocks - pack bits for each SFB in range [0, maxSFB), for each group [0, 7]
+ * msMaskPresent = 0 means no M/S coding
+ * = 1 means m_PSInfoBase->msMaskBits contains 1 bit per SFB to toggle M/S coding
+ * = 2 means all SFB's are M/S coded (so m_PSInfoBase->msMaskBits is not needed)
+ */
+ m_PSInfoBase->commonWin = GetBits(1);
+ if (m_PSInfoBase->commonWin) {
+ DecodeICSInfo(icsInfo, m_PSInfoBase->sampRateIdx);
+ m_PSInfoBase->msMaskPresent = GetBits(2);
+ if (m_PSInfoBase->msMaskPresent == 1) {
+ maskPtr = m_PSInfoBase->msMaskBits;
+ *maskPtr = 0;
+ maskOffset = 0;
+ for (gp = 0; gp < icsInfo->numWinGroup; gp++) {
+ for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) {
+ currBit = (uint8_t)GetBits(1);
+ *maskPtr |= currBit << maskOffset;
+ if (++maskOffset == 8) {
+ maskPtr++;
+ *maskPtr = 0;
+ maskOffset = 0;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeLFEChannelElement
+ *
+ * Description: decode one LFE
+ *
+ * Inputs: none
+ *
+ * Outputs: updated element instance tag
+ *
+ * Return: 0 if successful, -1 if error
+ *
+ * Notes: doesn't decode individual channel stream (part of DecodeNoiselessData)
+ **********************************************************************************************************************/
+int32_t DecodeLFEChannelElement()
+{
+ /* read instance tag */
+ m_AACDecInfo->currInstTag = GetBits( NUM_INST_TAG_BITS);
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeDataStreamElement
+ *
+ * Description: decode one DSE
+ *
+ * Inputs: none
+ *
+ * Outputs: updated element instance tag
+ * filled in data stream buffer
+ *
+ * Return: 0 if successful, -1 if error
+ **********************************************************************************************************************/
+int32_t DecodeDataStreamElement()
+{
+ uint32_t byteAlign, dataCount;
+ uint8_t *dataBuf;
+
+ m_AACDecInfo->currInstTag = GetBits( NUM_INST_TAG_BITS);
+ byteAlign = GetBits(1);
+ dataCount = GetBits(8);
+ if (dataCount == 255)
+ dataCount += GetBits(8);
+
+ if (byteAlign)
+ ByteAlignBitstream();
+
+ m_PSInfoBase->dataCount = dataCount;
+ dataBuf = m_PSInfoBase->dataBuf;
+ while (dataCount--)
+ *dataBuf++ = GetBits(8);
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeProgramConfigElement
+ *
+ * Description: decode one PCE
+ *
+ * Inputs: none
+ *
+ * Outputs: filled-in ProgConfigElement_t struct
+ * updated aac_BitStreamInfo_t struct
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ *
+ * Notes: #define KEEP_PCE_COMMENTS to save the comment field of the PCE
+ * (otherwise we just skip it in the bitstream, to save memory)
+ **********************************************************************************************************************/
+int32_t DecodeProgramConfigElement(uint8_t idx)
+{
+ int32_t i;
+
+ m_pce[idx]->elemInstTag = GetBits(4);
+ m_pce[idx]->profile = GetBits(2);
+ m_pce[idx]->sampRateIdx = GetBits(4);
+ m_pce[idx]->numFCE = GetBits(4);
+ m_pce[idx]->numSCE = GetBits(4);
+ m_pce[idx]->numBCE = GetBits(4);
+ m_pce[idx]->numLCE = GetBits(2);
+ m_pce[idx]->numADE = GetBits(3);
+ m_pce[idx]->numCCE = GetBits(4);
+
+ m_pce[idx]->monoMixdown = GetBits(1) << 4; /* present flag */
+ if (m_pce[idx]->monoMixdown)
+ m_pce[idx]->monoMixdown |= GetBits(4); /* element number */
+
+ m_pce[idx]->stereoMixdown = GetBits(1) << 4; /* present flag */
+ if (m_pce[idx]->stereoMixdown)
+ m_pce[idx]->stereoMixdown |= GetBits(4); /* element number */
+
+ m_pce[idx]->matrixMixdown = GetBits(1) << 4; /* present flag */
+ if (m_pce[idx]->matrixMixdown) {
+ m_pce[idx]->matrixMixdown |= GetBits(2) << 1; /* index */
+ m_pce[idx]->matrixMixdown |= GetBits(1); /* pseudo-surround enable */
+ }
+
+ for (i = 0; i < m_pce[idx]->numFCE; i++) {
+ m_pce[idx]->fce[i] = GetBits(1) << 4; /* is_cpe flag */
+ m_pce[idx]->fce[i] |= GetBits(4); /* tag select */
+ }
+
+ for (i = 0; i < m_pce[idx]->numSCE; i++) {
+ m_pce[idx]->sce[i] = GetBits(1) << 4; /* is_cpe flag */
+ m_pce[idx]->sce[i] |= GetBits(4); /* tag select */
+ }
+
+ for (i = 0; i < m_pce[idx]->numBCE; i++) {
+ m_pce[idx]->bce[i] = GetBits(1) << 4; /* is_cpe flag */
+ m_pce[idx]->bce[i] |= GetBits(4); /* tag select */
+ }
+
+ for (i = 0; i < m_pce[idx]->numLCE; i++)
+ m_pce[idx]->lce[i] = GetBits(4); /* tag select */
+
+ for (i = 0; i < m_pce[idx]->numADE; i++)
+ m_pce[idx]->ade[i] = GetBits(4); /* tag select */
+
+ for (i = 0; i < m_pce[idx]->numCCE; i++) {
+ m_pce[idx]->cce[i] = GetBits(1) << 4; /* independent/dependent flag */
+ m_pce[idx]->cce[i] |= GetBits(4); /* tag select */
+ }
+
+ ByteAlignBitstream();
+ /* eat comment bytes and throw away */
+ i = GetBits(8);
+ while (i--)
+ GetBits(8);
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeFillElement
+ *
+ * Description: decode one fill element
+ *
+ * Inputs: none
+ * (14496-3, table 4.4.11)
+ *
+ * Outputs: updated element instance tag
+ * unpacked extension payload
+ *
+ * Return: 0 if successful, -1 if error
+ **********************************************************************************************************************/
+int32_t DecodeFillElement()
+{
+ uint32_t fillCount;
+ uint8_t *fillBuf;
+
+ fillCount = GetBits(4);
+ if (fillCount == 15)
+ fillCount += (GetBits(8) - 1);
+
+ m_PSInfoBase->fillCount = fillCount;
+ fillBuf = m_PSInfoBase->fillBuf;
+ while (fillCount--)
+ *fillBuf++ = GetBits(8);
+
+ m_AACDecInfo->currInstTag = -1; /* fill elements don't have instance tag */
+ m_AACDecInfo->fillExtType = 0;
+
+#ifdef AAC_ENABLE_SBR
+ /* check for SBR
+ * aacDecInfo->sbrEnabled is sticky (reset each raw_data_block), so for multichannel
+ * need to verify that all SCE/CPE/ICCE have valid SBR fill element following, and
+ * must upsample by 2 for LFE
+ */
+ if (m_PSInfoBase->fillCount > 0) {
+ m_AACDecInfo->fillExtType = (int32_t)((m_PSInfoBase->fillBuf[0] >> 4) & 0x0f);
+ if (m_AACDecInfo->fillExtType == EXT_SBR_DATA || m_AACDecInfo->fillExtType == EXT_SBR_DATA_CRC)
+ m_AACDecInfo->sbrEnabled = 1;
+ }
+#endif
+
+
+ m_AACDecInfo->fillBuf = m_PSInfoBase->fillBuf;
+ m_AACDecInfo->fillCount = m_PSInfoBase->fillCount;
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeNextElement
+ *
+ * Description: decode next syntactic element in AAC frame
+ *
+ * Inputs: double pointer to buffer containing next element
+ * pointer to bit offset
+ * pointer to number of valid bits remaining in buf
+ *
+ * Outputs: type of element decoded (aacDecInfo->currBlockID)
+ * type of element decoded last time (aacDecInfo->prevBlockID)
+ * updated aacDecInfo state, depending on which element was decoded
+ * updated buffer pointer
+ * updated bit offset
+ * updated number of available bits
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ **********************************************************************************************************************/
+int32_t DecodeNextElement(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail)
+{
+ int32_t err, bitsUsed;
+
+ /* init bitstream reader */
+ SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf);
+ GetBits(*bitOffset);
+
+ m_AACDecInfo->prevBlockID = m_AACDecInfo->currBlockID;
+ m_AACDecInfo->currBlockID = GetBits(NUM_SYN_ID_BITS);
+
+ /* set defaults (could be overwritten by DecodeXXXElement(), depending on currBlockID) */
+ m_PSInfoBase->commonWin = 0;
+
+ err = 0;
+ switch (m_AACDecInfo->currBlockID) {
+ case AAC_ID_SCE:
+ err = DecodeSingleChannelElement();
+ break;
+ case AAC_ID_CPE:
+ err = DecodeChannelPairElement();
+ break;
+ case AAC_ID_CCE:
+ break;
+ case AAC_ID_LFE:
+ err = DecodeLFEChannelElement();
+ break;
+ case AAC_ID_DSE:
+ err = DecodeDataStreamElement();
+ break;
+ case AAC_ID_PCE:
+ err = DecodeProgramConfigElement(0);
+ break;
+ case AAC_ID_FIL:
+ err = DecodeFillElement();
+ break;
+ case AAC_ID_END:
+ break;
+ }
+ if (err)
+ return ERR_AAC_SYNTAX_ELEMENT;
+
+ /* update bitstream reader */
+ bitsUsed = CalcBitsUsed(*buf, *bitOffset);
+ *buf += (bitsUsed + *bitOffset) >> 3;
+ *bitOffset = (bitsUsed + *bitOffset) & 0x07;
+ *bitsAvail -= bitsUsed;
+
+ if (*bitsAvail < 0)
+ return ERR_AAC_INDATA_UNDERFLOW;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+ * Function: PreMultiply
+ *
+ * Description: pre-twiddle stage of DCT4
+ *
+ * Inputs: table index (for transform size)
+ * buffer of nmdct samples
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: minimum 1 GB in, 2 GB out, gains 5 (short) or 8 (long) frac bits
+ * i.e. gains 2-7= -5 int32_t bits (short) or 2-10 = -8 int32_t bits (long)
+ * normalization by -1/N is rolled into tables here (see trigtabs.c)
+ * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add
+ **********************************************************************************************************************/
+void PreMultiply(int32_t tabidx, int32_t *zbuf1)
+{
+ int32_t i, nmdct, ar1, ai1, ar2, ai2, z1, z2;
+ int32_t t, cms2, cps2a, sin2a, cps2b, sin2b;
+ int32_t *zbuf2;
+ const uint32_t *csptr;
+
+ nmdct = nmdctTab[tabidx];
+ zbuf2 = zbuf1 + nmdct - 1;
+ csptr = cos4sin4tab + cos4sin4tabOffset[tabidx];
+
+ /* whole thing should fit in registers - verify that compiler does this */
+ for (i = nmdct >> 2; i != 0; i--) {
+ /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */
+ cps2a = *csptr++;
+ sin2a = *csptr++;
+ cps2b = *csptr++;
+ sin2b = *csptr++;
+
+ ar1 = *(zbuf1 + 0);
+ ai2 = *(zbuf1 + 1);
+ ai1 = *(zbuf2 + 0);
+ ar2 = *(zbuf2 - 1);
+
+ /* gain 2 ints bit from MULSHIFT32 by Q30, but drop 7 or 10 int32_t bits from table scaling of 1/M
+ * max per-sample gain (ignoring implicit scaling) = MAX(sin(angle)+cos(angle)) = 1.414
+ * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from
+ * extra sign bits, and eat one in adding
+ */
+ t = MULSHIFT32(sin2a, ar1 + ai1);
+ z2 = MULSHIFT32(cps2a, ai1) - t;
+ cms2 = cps2a - 2*sin2a;
+ z1 = MULSHIFT32(cms2, ar1) + t;
+ *zbuf1++ = z1; /* cos*ar1 + sin*ai1 */
+ *zbuf1++ = z2; /* cos*ai1 - sin*ar1 */
+
+ t = MULSHIFT32(sin2b, ar2 + ai2);
+ z2 = MULSHIFT32(cps2b, ai2) - t;
+ cms2 = cps2b - 2*sin2b;
+ z1 = MULSHIFT32(cms2, ar2) + t;
+ *zbuf2-- = z2; /* cos*ai2 - sin*ar2 */
+ *zbuf2-- = z1; /* cos*ar2 + sin*ai2 */
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: PostMultiply
+ *
+ * Description: post-twiddle stage of DCT4
+ *
+ * Inputs: table index (for transform size)
+ * buffer of nmdct samples
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: minimum 1 GB in, 2 GB out - gains 2 int32_t bits
+ * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add
+ **********************************************************************************************************************/
+void PostMultiply(int32_t tabidx, int32_t *fft1)
+{
+ int32_t i, nmdct, ar1, ai1, ar2, ai2, skipFactor;
+ int32_t t, cms2, cps2, sin2;
+ int32_t *fft2;
+ const int32_t *csptr;
+
+ nmdct = nmdctTab[tabidx];
+ csptr = cos1sin1tab;
+ skipFactor = postSkip[tabidx];
+ fft2 = fft1 + nmdct - 1;
+
+ /* load coeffs for first pass
+ * cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin)
+ */
+ cps2 = *csptr++;
+ sin2 = *csptr;
+ csptr += skipFactor;
+ cms2 = cps2 - 2*sin2;
+
+ for (i = nmdct >> 2; i != 0; i--) {
+ ar1 = *(fft1 + 0);
+ ai1 = *(fft1 + 1);
+ ar2 = *(fft2 - 1);
+ ai2 = *(fft2 + 0);
+
+ /* gain 2 ints bit from MULSHIFT32 by Q30
+ * max per-sample gain = MAX(sin(angle)+cos(angle)) = 1.414
+ * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from
+ * extra sign bits, and eat one in adding
+ */
+ t = MULSHIFT32(sin2, ar1 + ai1);
+ *fft2-- = t - MULSHIFT32(cps2, ai1); /* sin*ar1 - cos*ai1 */
+ *fft1++ = t + MULSHIFT32(cms2, ar1); /* cos*ar1 + sin*ai1 */
+ cps2 = *csptr++;
+ sin2 = *csptr;
+ csptr += skipFactor;
+
+ ai2 = -ai2;
+ t = MULSHIFT32(sin2, ar2 + ai2);
+ *fft2-- = t - MULSHIFT32(cps2, ai2); /* sin*ar1 - cos*ai1 */
+ cms2 = cps2 - 2*sin2;
+ *fft1++ = t + MULSHIFT32(cms2, ar2); /* cos*ar1 + sin*ai1 */
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: PreMultiplyRescale
+ *
+ * Description: pre-twiddle stage of DCT4, with rescaling for extra guard bits
+ *
+ * Inputs: table index (for transform size)
+ * buffer of nmdct samples
+ * number of guard bits to add to input before processing
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: see notes on PreMultiply(), above
+ **********************************************************************************************************************/
+void PreMultiplyRescale(int32_t tabidx, int32_t *zbuf1, int32_t es)
+{
+ int32_t i, nmdct, ar1, ai1, ar2, ai2, z1, z2;
+ int32_t t, cms2, cps2a, sin2a, cps2b, sin2b;
+ int32_t *zbuf2;
+ const uint32_t *csptr;
+
+ nmdct = nmdctTab[tabidx];
+ zbuf2 = zbuf1 + nmdct - 1;
+ csptr = cos4sin4tab + cos4sin4tabOffset[tabidx];
+
+ /* whole thing should fit in registers - verify that compiler does this */
+ for (i = nmdct >> 2; i != 0; i--) {
+ /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */
+ cps2a = *csptr++;
+ sin2a = *csptr++;
+ cps2b = *csptr++;
+ sin2b = *csptr++;
+
+ ar1 = *(zbuf1 + 0) >> es;
+ ai1 = *(zbuf2 + 0) >> es;
+ ai2 = *(zbuf1 + 1) >> es;
+
+ t = MULSHIFT32(sin2a, ar1 + ai1);
+ z2 = MULSHIFT32(cps2a, ai1) - t;
+ cms2 = cps2a - 2*sin2a;
+ z1 = MULSHIFT32(cms2, ar1) + t;
+ *zbuf1++ = z1;
+ *zbuf1++ = z2;
+
+ ar2 = *(zbuf2 - 1) >> es; /* do here to free up register used for es */
+
+ t = MULSHIFT32(sin2b, ar2 + ai2);
+ z2 = MULSHIFT32(cps2b, ai2) - t;
+ cms2 = cps2b - 2*sin2b;
+ z1 = MULSHIFT32(cms2, ar2) + t;
+ *zbuf2-- = z2;
+ *zbuf2-- = z1;
+
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: PostMultiplyRescale
+ *
+ * Description: post-twiddle stage of DCT4, with rescaling for extra guard bits
+ *
+ * Inputs: table index (for transform size)
+ * buffer of nmdct samples
+ * number of guard bits to remove from output
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: clips output to [-2^30, 2^30 - 1], guaranteeing at least 1 guard bit
+ * see notes on PostMultiply(), above
+ **********************************************************************************************************************/
+void PostMultiplyRescale(int32_t tabidx, int32_t *fft1, int32_t es)
+{
+ int32_t i, nmdct, ar1, ai1, ar2, ai2, skipFactor, z;
+ int32_t t, cs2, sin2;
+ int32_t *fft2;
+ const int32_t *csptr;
+
+ nmdct = nmdctTab[tabidx];
+ csptr = cos1sin1tab;
+ skipFactor = postSkip[tabidx];
+ fft2 = fft1 + nmdct - 1;
+
+ /* load coeffs for first pass
+ * cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin)
+ */
+ cs2 = *csptr++;
+ sin2 = *csptr;
+ csptr += skipFactor;
+
+ for (i = nmdct >> 2; i != 0; i--) {
+ ar1 = *(fft1 + 0);
+ ai1 = *(fft1 + 1);
+ ai2 = *(fft2 + 0);
+
+ t = MULSHIFT32(sin2, ar1 + ai1);
+ z = t - MULSHIFT32(cs2, ai1);
+ {int32_t sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}}
+ *fft2-- = z;
+ cs2 -= 2*sin2;
+ z = t + MULSHIFT32(cs2, ar1);
+ {int32_t sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}}
+ *fft1++ = z;
+
+ cs2 = *csptr++;
+ sin2 = *csptr;
+ csptr += skipFactor;
+
+ ar2 = *fft2;
+ ai2 = -ai2;
+ t = MULSHIFT32(sin2, ar2 + ai2);
+ z = t - MULSHIFT32(cs2, ai2);
+ {int32_t sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}}
+ *fft2-- = z;
+ cs2 -= 2*sin2;
+ z = t + MULSHIFT32(cs2, ar2);
+ {int32_t sign = (z) >> 31; if (sign != (z) >> (30 - (es))) {(z) = sign ^ (0x3fffffff);} else {(z) = (z) << (es);}}
+ *fft1++ = z;
+ cs2 += 2*sin2;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DCT4
+ *
+ * Description: type-IV DCT
+ *
+ * Inputs: table index (for transform size)
+ * buffer of nmdct samples
+ * number of guard bits in the input buffer
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: operates in-place
+ * if number of guard bits in input is < GBITS_IN_DCT4, the input is
+ * scaled (>>) before the DCT4 and rescaled (<<, with clipping) after
+ * the DCT4 (rare)
+ * the output has FBITS_LOST_DCT4 fewer fraction bits than the input
+ * the output will always have at least 1 guard bit (GBITS_IN_DCT4 >= 4)
+ * int32_t bits gained per stage (PreMul + FFT + PostMul)
+ * short blocks = (-5 + 4 + 2) = 1 total
+ * long blocks = (-8 + 7 + 2) = 1 total
+ **********************************************************************************************************************/
+void DCT4(int32_t tabidx, int32_t *coef, int32_t gb)
+{
+ int32_t es;
+
+ /* fast in-place DCT-IV - adds guard bits if necessary */
+ if (gb < GBITS_IN_DCT4) {
+ es = GBITS_IN_DCT4 - gb;
+ PreMultiplyRescale(tabidx, coef, es);
+ R4FFT(tabidx, coef);
+ PostMultiplyRescale(tabidx, coef, es);
+ } else {
+ PreMultiply(tabidx, coef);
+ R4FFT(tabidx, coef);
+ PostMultiply(tabidx, coef);
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: BitReverse
+ *
+ * Description: Ken's fast in-place bit reverse, using super-small table
+ *
+ * Inputs: buffer of samples
+ * table index (for transform size)
+ *
+ * Outputs: bit-reversed samples in same buffer
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void BitReverse(int32_t *inout, int32_t tabidx)
+{
+ int32_t *part0, *part1;
+ int32_t a,b, t;
+ const uint8_t* tab = bitrevtab + bitrevtabOffset[tabidx];
+ int32_t nbits = nfftlog2Tab[tabidx];
+
+ part0 = inout;
+ part1 = inout + (1 << nbits);
+
+ while ((a = pgm_read_byte(tab++)) != 0) {
+ b = pgm_read_byte(tab++);
+
+ t=part0[4*a+0]; part0[4*a+0]=part0[4*b+0]; part0[4*b+0]=t; /* 0xxx0 <-> 0yyy0 */
+ t=part0[4*a+1]; part0[4*a+1]=part0[4*b+1]; part0[4*b+1]=t;
+
+ t=part0[4*a+2]; part0[4*a+2]=part1[4*b+0]; part1[4*b+0]=t; /* 0xxx0 <-> 0yyy0 */
+ t=part0[4*a+3]; part0[4*a+3]=part1[4*b+1]; part1[4*b+1]=t;
+
+ t=part1[4*a+0]; part1[4*a+0]=part0[4*b+2]; part0[4*b+2]=t; /* 1xxx0 <-> 0yyy1 */
+ t=part1[4*a+1]; part1[4*a+1]=part0[4*b+3]; part0[4*b+3]=t;
+
+ t=part1[4*a+2]; part1[4*a+2]=part1[4*b+2]; part1[4*b+2]=t; /* 1xxx1 <-> 1yyy1 */
+ t=part1[4*a+3]; part1[4*a+3]=part1[4*b+3]; part1[4*b+3]=t;
+ }
+
+ do {
+ t=part0[4*a+2]; part0[4*a+2]=part1[4*a+0]; part1[4*a+0]=t; /* 0xxx1 <-> 1xxx0 */
+ t=part0[4*a+3]; part0[4*a+3]=part1[4*a+1]; part1[4*a+1]=t;
+ } while ((a = pgm_read_byte(tab++)) != 0);
+
+
+}
+
+/***********************************************************************************************************************
+ * Function: R4FirstPass
+ *
+ * Description: radix-4 trivial pass for decimation-in-time FFT
+ *
+ * Inputs: buffer of (bit-reversed) samples
+ * number of R4 butterflies per group (i.e. nfft / 4)
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: assumes 2 guard bits, gains no integer bits,
+ * guard bits out = guard bits in - 2
+ **********************************************************************************************************************/
+void R4FirstPass(int32_t *x, int32_t bg)
+{
+ int32_t ar, ai, br, bi, cr, ci, dr, di;
+
+ for (; bg != 0; bg--) {
+
+ ar = x[0] + x[2];
+ br = x[0] - x[2];
+ ai = x[1] + x[3];
+ bi = x[1] - x[3];
+ cr = x[4] + x[6];
+ dr = x[4] - x[6];
+ ci = x[5] + x[7];
+ di = x[5] - x[7];
+
+ /* max per-sample gain = 4.0 (adding 4 inputs together) */
+ x[0] = ar + cr;
+ x[4] = ar - cr;
+ x[1] = ai + ci;
+ x[5] = ai - ci;
+ x[2] = br + di;
+ x[6] = br - di;
+ x[3] = bi - dr;
+ x[7] = bi + dr;
+
+ x += 8;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: R8FirstPass
+ *
+ * Description: radix-8 trivial pass for decimation-in-time FFT
+ *
+ * Inputs: buffer of (bit-reversed) samples
+ * number of R8 butterflies per group (i.e. nfft / 8)
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: assumes 3 guard bits, gains 1 integer bit
+ * guard bits out = guard bits in - 3 (if inputs are full scale)
+ * or guard bits in - 2 (if inputs bounded to +/- sqrt(2)/2)
+ * see scaling comments in code
+ **********************************************************************************************************************/
+void R8FirstPass(int32_t *x, int32_t bg)
+{
+ int32_t ar, ai, br, bi, cr, ci, dr, di;
+ int32_t sr, si, tr, ti, ur, ui, vr, vi;
+ int32_t wr, wi, xr, xi, yr, yi, zr, zi;
+
+ for (; bg != 0; bg--) {
+
+ ar = x[0] + x[2];
+ br = x[0] - x[2];
+ ai = x[1] + x[3];
+ bi = x[1] - x[3];
+ cr = x[4] + x[6];
+ dr = x[4] - x[6];
+ ci = x[5] + x[7];
+ di = x[5] - x[7];
+
+ sr = ar + cr;
+ ur = ar - cr;
+ si = ai + ci;
+ ui = ai - ci;
+ tr = br - di;
+ vr = br + di;
+ ti = bi + dr;
+ vi = bi - dr;
+
+ ar = x[ 8] + x[10];
+ br = x[ 8] - x[10];
+ ai = x[ 9] + x[11];
+ bi = x[ 9] - x[11];
+ cr = x[12] + x[14];
+ dr = x[12] - x[14];
+ ci = x[13] + x[15];
+ di = x[13] - x[15];
+
+ /* max gain of wr/wi/yr/yi vs input = 2
+ * (sum of 4 samples >> 1)
+ */
+ wr = (ar + cr) >> 1;
+ yr = (ar - cr) >> 1;
+ wi = (ai + ci) >> 1;
+ yi = (ai - ci) >> 1;
+
+ /* max gain of output vs input = 4
+ * (sum of 4 samples >> 1 + sum of 4 samples >> 1)
+ */
+ x[ 0] = (sr >> 1) + wr;
+ x[ 8] = (sr >> 1) - wr;
+ x[ 1] = (si >> 1) + wi;
+ x[ 9] = (si >> 1) - wi;
+ x[ 4] = (ur >> 1) + yi;
+ x[12] = (ur >> 1) - yi;
+ x[ 5] = (ui >> 1) - yr;
+ x[13] = (ui >> 1) + yr;
+
+ ar = br - di;
+ cr = br + di;
+ ai = bi + dr;
+ ci = bi - dr;
+
+ /* max gain of xr/xi/zr/zi vs input = 4*sqrt(2)/2 = 2*sqrt(2)
+ * (sum of 8 samples, multiply by sqrt(2)/2, implicit >> 1 from Q31)
+ */
+ xr = MULSHIFT32(SQRTHALF, ar - ai);
+ xi = MULSHIFT32(SQRTHALF, ar + ai);
+ zr = MULSHIFT32(SQRTHALF, cr - ci);
+ zi = MULSHIFT32(SQRTHALF, cr + ci);
+
+ /* max gain of output vs input = (2 + 2*sqrt(2) ~= 4.83)
+ * (sum of 4 samples >> 1, plus xr/xi/zr/zi with gain of 2*sqrt(2))
+ * in absolute terms, we have max gain of appx 9.656 (4 + 0.707*8)
+ * but we also gain 1 int32_t bit (from MULSHIFT32 or from explicit >> 1)
+ */
+ x[ 6] = (tr >> 1) - xr;
+ x[14] = (tr >> 1) + xr;
+ x[ 7] = (ti >> 1) - xi;
+ x[15] = (ti >> 1) + xi;
+ x[ 2] = (vr >> 1) + zi;
+ x[10] = (vr >> 1) - zi;
+ x[ 3] = (vi >> 1) - zr;
+ x[11] = (vi >> 1) + zr;
+
+ x += 16;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: R4Core
+ *
+ * Description: radix-4 pass for decimation-in-time FFT
+ *
+ * Inputs: buffer of samples
+ * number of R4 butterflies per group
+ * number of R4 groups per pass
+ * pointer to twiddle factors tables
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: gain 2 integer bits per pass (see scaling comments in code)
+ * min 1 GB in
+ * gbOut = gbIn - 1 (short block) or gbIn - 2 (long block)
+ * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add
+ **********************************************************************************************************************/
+void R4Core(int32_t *x, int32_t bg, int32_t gp, int32_t *wtab)
+{
+ int32_t ar, ai, br, bi, cr, ci, dr, di, tr, ti;
+ int32_t wd, ws, wi;
+ int32_t i, j, step;
+ int32_t *xptr, *wptr;
+
+ for (; bg != 0; gp <<= 2, bg >>= 2) {
+
+ step = 2*gp;
+ xptr = x;
+
+ /* max per-sample gain, per group < 1 + 3*sqrt(2) ~= 5.25 if inputs x are full-scale
+ * do 3 groups for long block, 2 groups for short block (gain 2 int32_t bits per group)
+ *
+ * very conservative scaling:
+ * group 1: max gain = 5.25, int32_t bits gained = 2, gb used = 1 (2^3 = 8)
+ * group 2: max gain = 5.25^2 = 27.6, int32_t bits gained = 4, gb used = 1 (2^5 = 32)
+ * group 3: max gain = 5.25^3 = 144.7, int32_t bits gained = 6, gb used = 2 (2^8 = 256)
+ */
+ for (i = bg; i != 0; i--) {
+
+ wptr = wtab;
+
+ for (j = gp; j != 0; j--) {
+
+ ar = xptr[0];
+ ai = xptr[1];
+ xptr += step;
+
+ /* gain 2 int32_t bits for br/bi, cr/ci, dr/di (MULSHIFT32 by Q30)
+ * gain 1 net GB
+ */
+ ws = wptr[0];
+ wi = wptr[1];
+ br = xptr[0];
+ bi = xptr[1];
+ wd = ws + 2*wi;
+ tr = MULSHIFT32(wi, br + bi);
+ br = MULSHIFT32(wd, br) - tr; /* cos*br + sin*bi */
+ bi = MULSHIFT32(ws, bi) + tr; /* cos*bi - sin*br */
+ xptr += step;
+
+ ws = wptr[2];
+ wi = wptr[3];
+ cr = xptr[0];
+ ci = xptr[1];
+ wd = ws + 2*wi;
+ tr = MULSHIFT32(wi, cr + ci);
+ cr = MULSHIFT32(wd, cr) - tr;
+ ci = MULSHIFT32(ws, ci) + tr;
+ xptr += step;
+
+ ws = wptr[4];
+ wi = wptr[5];
+ dr = xptr[0];
+ di = xptr[1];
+ wd = ws + 2*wi;
+ tr = MULSHIFT32(wi, dr + di);
+ dr = MULSHIFT32(wd, dr) - tr;
+ di = MULSHIFT32(ws, di) + tr;
+ wptr += 6;
+
+ tr = ar;
+ ti = ai;
+ ar = (tr >> 2) - br;
+ ai = (ti >> 2) - bi;
+ br = (tr >> 2) + br;
+ bi = (ti >> 2) + bi;
+
+ tr = cr;
+ ti = ci;
+ cr = tr + dr;
+ ci = di - ti;
+ dr = tr - dr;
+ di = di + ti;
+
+ xptr[0] = ar + ci;
+ xptr[1] = ai + dr;
+ xptr -= step;
+ xptr[0] = br - cr;
+ xptr[1] = bi - di;
+ xptr -= step;
+ xptr[0] = ar - ci;
+ xptr[1] = ai - dr;
+ xptr -= step;
+ xptr[0] = br + cr;
+ xptr[1] = bi + di;
+ xptr += 2;
+ }
+ xptr += 3*step;
+ }
+ wtab += 3*step;
+ }
+}
+
+
+/***********************************************************************************************************************
+ * Function: R4FFT
+ *
+ * Description: Ken's very fast in-place radix-4 decimation-in-time FFT
+ *
+ * Inputs: table index (for transform size)
+ * buffer of samples (non bit-reversed)
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: assumes 5 guard bits in for nfft <= 512
+ * gbOut = gbIn - 4 (assuming input is from PreMultiply)
+ * gains log2(nfft) - 2 int32_t bits total
+ * so gain 7 int32_t bits (LONG), 4 int32_t bits (SHORT)
+ **********************************************************************************************************************/
+void R4FFT(int32_t tabidx, int32_t *x)
+{
+ int32_t order = nfftlog2Tab[tabidx];
+ int32_t nfft = nfftTab[tabidx];
+
+ /* decimation in time */
+ BitReverse(x, tabidx);
+
+ if (order & 0x1) {
+ /* long block: order = 9, nfft = 512 */
+ R8FirstPass(x, nfft >> 3); /* gain 1 int32_t bit, lose 2 GB */
+ R4Core(x, nfft >> 5, 8, (int32_t *)twidTabOdd); /* gain 6 int32_t bits, lose 2 GB */
+ } else {
+ /* short block: order = 6, nfft = 64 */
+ R4FirstPass(x, nfft >> 2); /* gain 0 int32_t bits, lose 2 GB */
+ R4Core(x, nfft >> 4, 4, (int32_t *)twidTabEven); /* gain 4 int32_t bits, lose 1 GB */
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: UnpackZeros
+ *
+ * Description: fill a section of coefficients with zeros
+ *
+ * Inputs: number of coefficients
+ *
+ * Outputs: nVals zeros, starting at coef
+ *
+ * Return: none
+ *
+ * Notes: assumes nVals is always a multiple of 4 because all scalefactor bands
+ * are a multiple of 4 coefficients long
+ **********************************************************************************************************************/
+void UnpackZeros(int32_t nVals, int32_t *coef)
+{
+ while (nVals > 0) {
+ *coef++ = 0;
+ *coef++ = 0;
+ *coef++ = 0;
+ *coef++ = 0;
+ nVals -= 4;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: UnpackQuads
+ *
+ * Description: decode a section of 4-way vector Huffman coded coefficients
+ *
+ * Inputs index of Huffman codebook
+ * number of coefficients
+ *
+ * Outputs: nVals coefficients, starting at coef
+ *
+ * Return: none
+ *
+ * Notes: assumes nVals is always a multiple of 4 because all scalefactor bands
+ * are a multiple of 4 coefficients long
+ **********************************************************************************************************************/
+void UnpackQuads(int32_t cb, int32_t nVals, int32_t *coef)
+{
+ int32_t w, x, y, z, maxBits, nCodeBits, nSignBits, val;
+ uint32_t bitBuf;
+
+ maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 4;
+ while (nVals > 0) {
+ /* decode quad */
+ bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits);
+ nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET], bitBuf, &val);
+
+ w = (((int32_t)(val) << 20) >> 29); /* bits 11-9, sign-extend */
+ x = (((int32_t)(val) << 23) >> 29); /* bits 8-6, sign-extend */
+ y = (((int32_t)(val) << 26) >> 29); /* bits 5-3, sign-extend */
+ z = (((int32_t)(val) << 29) >> 29); /* bits 2-0, sign-extend */
+
+ bitBuf <<= nCodeBits;
+ nSignBits = (int32_t)(((uint32_t)(val) << 17) >> 29); /* bits 14-12, unsigned */
+
+ AdvanceBitstream(nCodeBits + nSignBits);
+ if (nSignBits) {
+ if (w) {w ^= ((int32_t)bitBuf >> 31); w -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ if (x) {x ^= ((int32_t)bitBuf >> 31); x -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ }
+ *coef++ = w; *coef++ = x; *coef++ = y; *coef++ = z;
+ nVals -= 4;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: UnpackPairsNoEsc
+ *
+ * Description: decode a section of 2-way vector Huffman coded coefficients,
+ * using non-esc tables (5 through 10)
+ *
+ * Inputs index of Huffman codebook (must not be the escape codebook)
+ * number of coefficients
+ *
+ * Outputs: nVals coefficients, starting at coef
+ *
+ * Return: none
+ *
+ * Notes: assumes nVals is always a multiple of 2 because all scalefactor bands
+ * are a multiple of 4 coefficients long
+ **********************************************************************************************************************/
+void UnpackPairsNoEsc(int32_t cb, int32_t nVals, int32_t *coef)
+{
+ int32_t y, z, maxBits, nCodeBits, nSignBits, val;
+ uint32_t bitBuf;
+
+ maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 2;
+ while (nVals > 0) {
+ /* decode pair */
+ bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits);
+ nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb-HUFFTAB_SPEC_OFFSET], bitBuf, &val);
+
+ y = (((int32_t)(val) << 22) >> 27); /* bits 9-5, sign-extend */
+ z = (((int32_t)(val) << 27) >> 27); /* bits 4-0, sign-extend */
+
+ bitBuf <<= nCodeBits;
+ nSignBits = (((uint32_t)(val) << 20) >> 30); /* bits 11-10, unsigned */
+ AdvanceBitstream(nCodeBits + nSignBits);
+ if (nSignBits) {
+ if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ }
+ *coef++ = y; *coef++ = z;
+ nVals -= 2;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: UnpackPairsEsc
+ *
+ * Description: decode a section of 2-way vector Huffman coded coefficients,
+ * using esc table (11)
+ *
+ * Inputs index of Huffman codebook (must be the escape codebook)
+ * number of coefficients
+ *
+ * Outputs: nVals coefficients, starting at coef
+ *
+ * Return: none
+ *
+ * Notes: assumes nVals is always a multiple of 2 because all scalefactor bands
+ * are a multiple of 4 coefficients long
+ **********************************************************************************************************************/
+void UnpackPairsEsc(int32_t cb, int32_t nVals, int32_t *coef)
+{
+ int32_t y, z, maxBits, nCodeBits, nSignBits, n, val;
+ uint32_t bitBuf;
+
+ maxBits = huffTabSpecInfo[cb - HUFFTAB_SPEC_OFFSET].maxBits + 2;
+ while (nVals > 0) {
+ /* decode pair with escape value */
+ bitBuf = GetBitsNoAdvance(maxBits) << (32 - maxBits);
+ nCodeBits = DecodeHuffmanScalar(huffTabSpec, &huffTabSpecInfo[cb-HUFFTAB_SPEC_OFFSET], bitBuf, &val);
+
+ y = (((int32_t)(val) << 20) >> 26); /* bits 11-6, sign-extend */
+ z = (((int32_t)(val) << 26) >> 26); /* bits 5-0, sign-extend */
+
+ bitBuf <<= nCodeBits;
+ nSignBits = (((uint32_t)(val) << 18) >> 30); /* bits 13-12, unsigned */
+ AdvanceBitstream(nCodeBits + nSignBits);
+
+ if (y == 16) {
+ n = 4;
+ while (GetBits(1) == 1)
+ n++;
+ y = (1 << n) + GetBits(n);
+ }
+ if (z == 16) {
+ n = 4;
+ while (GetBits(1) == 1)
+ n++;
+ z = (1 << n) + GetBits(n);
+ }
+
+ if (nSignBits) {
+ if (y) {y ^= ((int32_t)bitBuf >> 31); y -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ if (z) {z ^= ((int32_t)bitBuf >> 31); z -= ((int32_t)bitBuf >> 31); bitBuf <<= 1;}
+ }
+
+ *coef++ = y; *coef++ = z;
+ nVals -= 2;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeSpectrumLong
+ *
+ * Description: decode transform coefficients for frame with one long block
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: decoded, quantized coefficients for this channel
+ *
+ * Return: none
+ *
+ * Notes: adds in pulse data if present
+ * fills coefficient buffer with zeros in any region not coded with
+ * codebook in range [1, 11] (including sfb's above sfbMax)
+ **********************************************************************************************************************/
+void DecodeSpectrumLong(int32_t ch)
+{
+ int32_t i, sfb, cb, nVals, offset;
+ const uint16_t *sfbTab;
+ uint8_t *sfbCodeBook;
+ int32_t *coef;
+ ICSInfo_t *icsInfo;
+
+ coef = m_PSInfoBase->coef[ch];
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+
+ /* decode long block */
+ sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx];
+ sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch];
+ for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) {
+ cb = *sfbCodeBook++;
+ nVals = sfbTab[sfb+1] - sfbTab[sfb];
+
+ if (cb == 0)
+ UnpackZeros(nVals, coef);
+ else if (cb <= 4)
+ UnpackQuads(cb, nVals, coef);
+ else if (cb <= 10)
+ UnpackPairsNoEsc(cb, nVals, coef);
+ else if (cb == 11)
+ UnpackPairsEsc(cb, nVals, coef);
+ else
+ UnpackZeros(nVals, coef);
+
+ coef += nVals;
+ }
+
+ /* fill with zeros above maxSFB */
+ nVals = NSAMPS_LONG - sfbTab[sfb];
+ UnpackZeros(nVals, coef);
+
+ /* add pulse data, if present */
+ if (m_pulseInfo[ch].pulseDataPresent) {
+ coef = m_PSInfoBase->coef[ch];
+ offset = sfbTab[m_pulseInfo[ch].startSFB];
+ for (i = 0; i < m_pulseInfo[ch].numPulse; i++) {
+ offset += m_pulseInfo[ch].offset[i];
+ if (coef[offset] > 0)
+ coef[offset] += m_pulseInfo[ch].amp[i];
+ else
+ coef[offset] -= m_pulseInfo[ch].amp[i];
+ }
+ ASSERT(offset < NSAMPS_LONG);
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeSpectrumShort
+ *
+ * Description: decode transform coefficients for frame with eight short blocks
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: decoded, quantized coefficients for this channel
+ *
+ * Return: none
+ *
+ * Notes: fills coefficient buffer with zeros in any region not coded with
+ * codebook in range [1, 11] (including sfb's above sfbMax)
+ * deinterleaves window groups into 8 windows
+ **********************************************************************************************************************/
+void DecodeSpectrumShort(int32_t ch)
+{
+ int32_t gp, cb, nVals=0, win, offset, sfb;
+ const uint16_t *sfbTab;
+ uint8_t *sfbCodeBook;
+ int32_t *coef;
+ ICSInfo_t *icsInfo;
+
+ coef = m_PSInfoBase->coef[ch];
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+
+ /* decode short blocks, deinterleaving in-place */
+ sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx];
+ sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch];
+ for (gp = 0; gp < icsInfo->numWinGroup; gp++) {
+ for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) {
+ nVals = sfbTab[sfb+1] - sfbTab[sfb];
+ cb = *sfbCodeBook++;
+
+ for (win = 0; win < icsInfo->winGroupLen[gp]; win++) {
+ offset = win*NSAMPS_SHORT;
+ if (cb == 0)
+ UnpackZeros(nVals, coef + offset);
+ else if (cb <= 4)
+ UnpackQuads(cb, nVals, coef + offset);
+ else if (cb <= 10)
+ UnpackPairsNoEsc(cb, nVals, coef + offset);
+ else if (cb == 11)
+ UnpackPairsEsc(cb, nVals, coef + offset);
+ else
+ UnpackZeros(nVals, coef + offset);
+ }
+ coef += nVals;
+ }
+
+ /* fill with zeros above maxSFB */
+ for (win = 0; win < icsInfo->winGroupLen[gp]; win++) {
+ offset = win*NSAMPS_SHORT;
+ nVals = NSAMPS_SHORT - sfbTab[sfb];
+ UnpackZeros(nVals, coef + offset);
+ }
+ coef += nVals;
+ coef += (icsInfo->winGroupLen[gp] - 1)*NSAMPS_SHORT;
+ }
+
+ ASSERT(coef == m_PSInfoBase->coef[ch] + NSAMPS_LONG);
+}
+
+#ifndef AAC_ENABLE_SBR
+/***********************************************************************************************************************
+ * Function: DecWindowOverlap
+ *
+ * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM,
+ * for winSequence LONG-LONG
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * number of channels
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans
+ *
+ * Return: none
+ *
+ * Notes: this processes one channel at a time, but skips every other sample in
+ * the output buffer (pcm) for stereo interleaving
+ * this should fit in registers on ARM
+ *
+ **********************************************************************************************************************/
+void DecWindowOverlap(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev)
+{
+ int32_t in, w0, w1, f0, f1;
+ int32_t *buf1, *over1;
+ int16_t *pcm1;
+ const int32_t *wndCurr, *wndPrev;
+
+ buf0 += (1024 >> 1);
+ buf1 = buf0 - 1;
+ pcm1 = pcm0 + (1024 - 1) * nChans;
+ over1 = over0 + 1024 - 1;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+ if (winTypeCurr == winTypePrev) {
+ /* cut window loads in half since current and overlap sections use same symmetric window */
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ } else {
+ /* different windows for current and overlap parts - should still fit in registers on ARM w/o stack spill */
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapLongStart
+ *
+ * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM,
+ * for winSequence LONG-START
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * number of channels
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans
+ *
+ * Return: none
+ *
+ * Notes: this processes one channel at a time, but skips every other sample in
+ * the output buffer (pcm) for stereo interleaving
+ * this should fit in registers on ARM
+ **********************************************************************************************************************/
+void DecWindowOverlapLongStart(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev)
+{
+ int32_t i, in, w0, w1, f0, f1;
+ int32_t *buf1, *over1;
+ int16_t *pcm1;
+ const int32_t *wndPrev, *wndCurr;
+
+ buf0 += (1024 >> 1);
+ buf1 = buf0 - 1;
+ pcm1 = pcm0 + (1024 - 1) * nChans;
+ over1 = over0 + 1024 - 1;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+ i = 448; /* 2 outputs, 2 overlaps per loop */
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ in = *buf1--;
+
+ *over1-- = 0; /* Wn = 0 for n = (2047, 2046, ... 1600) */
+ *over0++ = in >> 1; /* Wn = 1 for n = (1024, 1025, ... 1471) */
+ } while (--i);
+
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+
+ /* do 64 more loops - 2 outputs, 2 overlaps per loop */
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ w0 = *wndCurr++; /* W[0], W[1], ... --> W[255], W[254], ... */
+ w1 = *wndCurr++; /* W[127], W[126], ... --> W[128], W[129], ... */
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in); /* Wn = short window for n = (1599, 1598, ... , 1536) */
+ *over0++ = MULSHIFT32(w1, in); /* Wn = short window for n = (1472, 1473, ... , 1535) */
+ } while (over0 < over1);
+}
+
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapLongStop
+ *
+ * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM,
+ * for winSequence LONG-STOP
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * number of channels
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans
+ *
+ * Return: none
+ *
+ * Notes: this processes one channel at a time, but skips every other sample in
+ * the output buffer (pcm) for stereo interleaving
+ * this should fit in registers on ARM
+ **********************************************************************************************************************/
+void DecWindowOverlapLongStop(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev)
+{
+ int32_t i, in, w0, w1, f0, f1;
+ int32_t *buf1, *over1;
+ int16_t *pcm1;
+ const int32_t *wndPrev, *wndCurr;
+
+ buf0 += (1024 >> 1);
+ buf1 = buf0 - 1;
+ pcm1 = pcm0 + (1024 - 1) * nChans;
+ over1 = over0 + 1024 - 1;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+
+ i = 448; /* 2 outputs, 2 overlaps per loop */
+ do {
+ /* Wn = 0 for n = (0, 1, ... 447) */
+ /* Wn = 1 for n = (576, 577, ... 1023) */
+ in = *buf0++;
+ f1 = in >> 1; /* scale since skipping multiply by Q31 */
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (--i);
+
+ /* do 64 more loops - 2 outputs, 2 overlaps per loop */
+ do {
+ w0 = *wndPrev++; /* W[0], W[1], ...W[63] */
+ w1 = *wndPrev++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+}
+
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapShort
+ *
+ * Description: apply synthesis window, do overlap-add, clip to 16-bit PCM,
+ * for winSequence EIGHT-SHORT (does all 8 short blocks)
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * number of channels
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 16-bit PCM, interleaved by nChans
+ *
+ * Return: none
+ *
+ * Notes: this processes one channel at a time, but skips every other sample in
+ * the output buffer (pcm) for stereo interleaving
+ * this should fit in registers on ARM
+ **********************************************************************************************************************/
+void DecWindowOverlapShort(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev)
+{
+ int32_t i, in, w0, w1, f0, f1;
+ int32_t *buf1, *over1;
+ int16_t *pcm1;
+ const int32_t *wndPrev, *wndCurr;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+
+ /* pcm[0-447] = 0 + overlap[0-447] */
+ i = 448;
+ do {
+ f0 = *over0++;
+ f1 = *over0++;
+ *pcm0 = CLIPTOSHORT( (f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); pcm0 += nChans;
+ *pcm0 = CLIPTOSHORT( (f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT ); pcm0 += nChans;
+ i -= 2;
+ } while (i);
+
+ /* pcm[448-575] = Wp[0-127] * block0[0-127] + overlap[448-575] */
+ pcm1 = pcm0 + (128 - 1) * nChans;
+ over1 = over0 + 128 - 1;
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ do {
+ w0 = *wndPrev++; /* W[0], W[1], ...W[63] */
+ w1 = *wndPrev++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *over1;
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ /* save over0/over1 for next short block, in the slots just vacated */
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+
+ /* pcm[576-703] = Wc[128-255] * block0[128-255] + Wc[0-127] * block1[0-127] + overlap[576-703]
+ * pcm[704-831] = Wc[128-255] * block1[128-255] + Wc[0-127] * block2[0-127] + overlap[704-831]
+ * pcm[832-959] = Wc[128-255] * block2[128-255] + Wc[0-127] * block3[0-127] + overlap[832-959]
+ */
+ for (i = 0; i < 3; i++) {
+ pcm0 += 64 * nChans;
+ pcm1 = pcm0 + (128 - 1) * nChans;
+ over0 += 64;
+ over1 = over0 + 128 - 1;
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ wndCurr -= 128;
+
+ do {
+ w0 = *wndCurr++; /* W[0], W[1], ...W[63] */
+ w1 = *wndCurr++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *(over0 - 128); /* from last short block */
+ in += *(over0 + 0); /* from last full frame */
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *(over1 - 128); /* from last short block */
+ in += *(over1 + 0); /* from last full frame */
+ *pcm1 = CLIPTOSHORT( (in + f1 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm1 -= nChans;
+
+ /* save over0/over1 for next short block, in the slots just vacated */
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ }
+
+ /* pcm[960-1023] = Wc[128-191] * block3[128-191] + Wc[0-63] * block4[0-63] + overlap[960-1023]
+ * over[0-63] = Wc[192-255] * block3[192-255] + Wc[64-127] * block4[64-127]
+ */
+ pcm0 += 64 * nChans;
+ over0 -= 832; /* points at overlap[64] */
+ over1 = over0 + 128 - 1; /* points at overlap[191] */
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ wndCurr -= 128;
+ do {
+ w0 = *wndCurr++; /* W[0], W[1], ...W[63] */
+ w1 = *wndCurr++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *(over0 + 768); /* from last short block */
+ in += *(over0 + 896); /* from last full frame */
+ *pcm0 = CLIPTOSHORT( (in - f0 + (1 << (FBITS_OUT_IMDCT-1))) >> FBITS_OUT_IMDCT );
+ pcm0 += nChans;
+
+ in = *(over1 + 768); /* from last short block */
+ *(over1 - 128) = in + f1;
+
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in); /* save in overlap[128-191] */
+ *over0++ = MULSHIFT32(w1, in); /* save in overlap[64-127] */
+ } while (over0 < over1);
+
+ /* over0 now points at overlap[128] */
+
+ /* over[64-191] = Wc[128-255] * block4[128-255] + Wc[0-127] * block5[0-127]
+ * over[192-319] = Wc[128-255] * block5[128-255] + Wc[0-127] * block6[0-127]
+ * over[320-447] = Wc[128-255] * block6[128-255] + Wc[0-127] * block7[0-127]
+ * over[448-576] = Wc[128-255] * block7[128-255]
+ */
+ for (i = 0; i < 3; i++) {
+ over0 += 64;
+ over1 = over0 + 128 - 1;
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ wndCurr -= 128;
+ do {
+ w0 = *wndCurr++; /* W[0], W[1], ...W[63] */
+ w1 = *wndCurr++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ /* from last short block */
+ *(over0 - 128) -= f0;
+ *(over1 - 128)+= f1;
+
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ }
+
+ /* over[576-1024] = 0 */
+ i = 448;
+ over0 += 64;
+ do {
+ *over0++ = 0;
+ *over0++ = 0;
+ *over0++ = 0;
+ *over0++ = 0;
+ i -= 4;
+ } while (i);
+}
+
+#endif /* !AAC_ENABLE_SBR */
+
+/***********************************************************************************************************************
+ * Function: IMDCT
+ *
+ * Description: inverse transform and convert to 16-bit PCM
+ *
+ * Inputs: index of current channel (0 for SCE/LFE, 0 or 1 for CPE)
+ * output channel (range = [0, nChans-1])
+ *
+ * Outputs: complete frame of decoded PCM, after inverse transform
+ *
+ * Return: 0 if successful, -1 if error
+ *
+ * Notes: If AAC_ENABLE_SBR is defined at compile time then window + overlap
+ * does NOT clip to 16-bit PCM and does NOT interleave channels
+ * If AAC_ENABLE_SBR is NOT defined at compile time, then window + overlap
+ * does clip to 16-bit PCM and interleaves channels
+ * If SBR is enabled at compile time, but we don't know whether it is
+ * actually used for this frame (e.g. the first frame of a stream),
+ * we need to produce both clipped 16-bit PCM in outbuf AND
+ * unclipped 32-bit PCM in the SBR input buffer. In this case we make
+ * a separate pass over the 32-bit PCM to produce 16-bit PCM output.
+ * This inflicts a slight performance hit when decoding non-SBR files.
+ **********************************************************************************************************************/
+int32_t IMDCT(int32_t ch, int32_t chOut, int16_t *outbuf)
+{
+ int32_t i;
+ ICSInfo_t *icsInfo;
+
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+ outbuf += chOut;
+
+ /* optimized type-IV DCT (operates inplace) */
+ if (icsInfo->winSequence == 2) {
+ /* 8 short blocks */
+ for (i = 0; i < 8; i++)
+ DCT4(0, m_PSInfoBase->coef[ch] + i*128, m_PSInfoBase->gbCurrent[ch]);
+ } else {
+ /* 1 long block */
+ DCT4(1, m_PSInfoBase->coef[ch], m_PSInfoBase->gbCurrent[ch]);
+ }
+
+#ifdef AAC_ENABLE_SBR
+ /* window, overlap-add, don't clip to short (send to SBR decoder)
+ * store the decoded 32-bit samples in top half (second AAC_MAX_NSAMPS samples) of coef buffer
+ */
+ if (icsInfo->winSequence == 0)
+ DecWindowOverlapNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut],
+ m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+ else if (icsInfo->winSequence == 1)
+ DecWindowOverlapLongStartNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut],
+ m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+ else if (icsInfo->winSequence == 2)
+ DecWindowOverlapShortNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut],
+ m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+ else if (icsInfo->winSequence == 3)
+ DecWindowOverlapLongStopNoClip(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut],
+ m_PSInfoBase->sbrWorkBuf[ch], icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+
+ if (!m_AACDecInfo->sbrEnabled) {
+ for (i = 0; i < AAC_MAX_NSAMPS; i++) {
+ *outbuf = CLIPTOSHORT((m_PSInfoBase->sbrWorkBuf[ch][i] + RND_VAL) >> FBITS_OUT_IMDCT);
+ outbuf += m_AACDecInfo->nChans;
+ }
+ }
+
+ m_AACDecInfo->rawSampleBuf[ch] = m_PSInfoBase->sbrWorkBuf[ch];
+ m_AACDecInfo->rawSampleBytes = sizeof(int32_t);
+ m_AACDecInfo->rawSampleFBits = FBITS_OUT_IMDCT;
+#else
+ /* window, overlap-add, round to PCM - optimized for each window sequence */
+ if (icsInfo->winSequence == 0)
+ DecWindowOverlap(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans,
+ icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+ else if (icsInfo->winSequence == 1)
+ DecWindowOverlapLongStart(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans,
+ icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+ else if (icsInfo->winSequence == 2)
+ DecWindowOverlapShort(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans,
+ icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+ else if (icsInfo->winSequence == 3)
+ DecWindowOverlapLongStop(m_PSInfoBase->coef[ch], m_PSInfoBase->overlap[chOut], outbuf, m_AACDecInfo->nChans,
+ icsInfo->winShape, m_PSInfoBase->prevWinShape[chOut]);
+
+ m_AACDecInfo->rawSampleBuf[ch] = 0;
+ m_AACDecInfo->rawSampleBytes = 0;
+ m_AACDecInfo->rawSampleFBits = 0;
+#endif
+
+ m_PSInfoBase->prevWinShape[chOut] = icsInfo->winShape;
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeICSInfo
+ *
+ * Description: decode individual channel stream info
+ *
+ * Inputs: sample rate index
+ *
+ * Outputs: updated icsInfo struct
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodeICSInfo(ICSInfo_t *icsInfo, int32_t sampRateIdx)
+{
+ int32_t sfb, g, mask;
+
+ icsInfo->icsResBit = GetBits(1);
+ icsInfo->winSequence = GetBits(2);
+ icsInfo->winShape = GetBits(1);
+ if (icsInfo->winSequence == 2) {
+ /* short block */
+ icsInfo->maxSFB = GetBits(4);
+ icsInfo->sfGroup = GetBits(7);
+ icsInfo->numWinGroup = 1;
+ icsInfo->winGroupLen[0] = 1;
+ mask = 0x40; /* start with bit 6 */
+ for (g = 0; g < 7; g++) {
+ if (icsInfo->sfGroup & mask) {
+ icsInfo->winGroupLen[icsInfo->numWinGroup - 1]++;
+ } else {
+ icsInfo->numWinGroup++;
+ icsInfo->winGroupLen[icsInfo->numWinGroup - 1] = 1;
+ }
+ mask >>= 1;
+ }
+ } else {
+ /* long block */
+ icsInfo->maxSFB = GetBits(6);
+ icsInfo->predictorDataPresent = GetBits(1);
+ if (icsInfo->predictorDataPresent) {
+ icsInfo->predictorReset = GetBits(1);
+ if (icsInfo->predictorReset)
+ icsInfo->predictorResetGroupNum = GetBits(5);
+ for (sfb = 0; sfb < MIN(icsInfo->maxSFB, predSFBMax[sampRateIdx]); sfb++)
+ icsInfo->predictionUsed[sfb] = GetBits(1);
+ }
+ icsInfo->numWinGroup = 1;
+ icsInfo->winGroupLen[0] = 1;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeSectionData
+ *
+ * Description: decode section data (scale factor band groupings and
+ * associated Huffman codebooks)
+ *
+ * Inputs: window sequence (short or long blocks)
+ * number of window groups (1 for long blocks, 1-8 for short blocks)
+ * max coded scalefactor band
+ *
+ * Outputs: index of Huffman codebook for each scalefactor band in each section
+ *
+ * Return: none
+ *
+ * Notes: sectCB, sectEnd, sfbCodeBook, ordered by window groups for short blocks
+ **********************************************************************************************************************/
+void DecodeSectionData(int32_t winSequence, int32_t numWinGrp, int32_t maxSFB, uint8_t *sfbCodeBook)
+{
+ int32_t g, cb, sfb;
+ int32_t sectLen, sectLenBits, sectLenIncr, sectEscapeVal;
+
+ sectLenBits = (winSequence == 2 ? 3 : 5);
+ sectEscapeVal = (1 << sectLenBits) - 1;
+
+ for (g = 0; g < numWinGrp; g++) {
+ sfb = 0;
+ while (sfb < maxSFB) {
+ cb = GetBits(4); /* next section codebook */
+ sectLen = 0;
+ do {
+ sectLenIncr = GetBits(sectLenBits);
+ sectLen += sectLenIncr;
+ } while (sectLenIncr == sectEscapeVal);
+
+ sfb += sectLen;
+ while (sectLen--)
+ *sfbCodeBook++ = (uint8_t)cb;
+ }
+ ASSERT(sfb == maxSFB);
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeOneScaleFactor
+ *
+ * Description: decode one scalefactor using scalefactor Huffman codebook
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: one decoded scalefactor, including index_offset of -60
+ **********************************************************************************************************************/
+int32_t DecodeOneScaleFactor()
+{
+ int32_t nBits, val;
+ uint32_t bitBuf;
+
+ /* decode next scalefactor from bitstream */
+ bitBuf = GetBitsNoAdvance(huffTabScaleFactInfo.maxBits) << (32 - huffTabScaleFactInfo.maxBits);
+ nBits = DecodeHuffmanScalar(huffTabScaleFact, &huffTabScaleFactInfo, bitBuf, &val);
+ AdvanceBitstream(nBits);
+ return val;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeScaleFactors
+ *
+ * Description: decode scalefactors, PNS energy, and intensity stereo weights
+ *
+ * Inputs: number of window groups (1 for long blocks, 1-8 for short blocks)
+ * max coded scalefactor band
+ * global gain (starting value for differential scalefactor coding)
+ * index of Huffman codebook for each scalefactor band in each section
+ *
+ * Outputs: decoded scalefactor for each section
+ *
+ * Return: none
+ *
+ * Notes: sfbCodeBook, scaleFactors ordered by window groups for short blocks
+ * for section with codebook 13, scaleFactors buffer has decoded PNS
+ * energy instead of regular scalefactor
+ * for section with codebook 14 or 15, scaleFactors buffer has intensity
+ * stereo weight instead of regular scalefactor
+ **********************************************************************************************************************/
+void DecodeScaleFactors(int32_t numWinGrp, int32_t maxSFB, int32_t globalGain,
+ uint8_t *sfbCodeBook, int16_t *scaleFactors)
+{
+ int32_t g, sfbCB, nrg, npf, val, sf, is;
+
+ /* starting values for differential coding */
+ sf = globalGain;
+ is = 0;
+ nrg = globalGain - 90 - 256;
+ npf = 1;
+
+ for (g = 0; g < numWinGrp * maxSFB; g++) {
+ sfbCB = *sfbCodeBook++;
+
+ if (sfbCB == 14 || sfbCB == 15) {
+ /* intensity stereo - differential coding */
+ val = DecodeOneScaleFactor();
+ is += val;
+ *scaleFactors++ = (int16_t)is;
+ } else if (sfbCB == 13) {
+ /* PNS - first energy is directly coded, rest are Huffman coded (npf = noise_pcm_flag) */
+ if (npf) {
+ val = GetBits(9);
+ npf = 0;
+ } else {
+ val = DecodeOneScaleFactor();
+ }
+ nrg += val;
+ *scaleFactors++ = (int16_t)nrg;
+ } else if (sfbCB >= 1 && sfbCB <= 11) {
+ /* regular (non-zero) region - differential coding */
+ val = DecodeOneScaleFactor();
+ sf += val;
+ *scaleFactors++ = (int16_t)sf;
+ } else {
+ /* inactive scalefactor band if codebook 0 */
+ *scaleFactors++ = 0;
+ }
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodePulseInfo
+ *
+ * Description: decode pulse information
+ *
+ * Inputs: none
+ *
+ * Outputs: updated PulseInfo_t struct
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodePulseInfo(uint8_t ch)
+{
+ int32_t i;
+
+ m_pulseInfo[ch].numPulse = GetBits(2) + 1; /* add 1 here */
+ m_pulseInfo[ch].startSFB = GetBits(6);
+ for (i = 0; i < m_pulseInfo[ch].numPulse; i++) {
+ m_pulseInfo[ch].offset[i] = GetBits(5);
+ m_pulseInfo[ch].amp[i] = GetBits(4);
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeTNSInfo
+ *
+ * Description: decode TNS filter information
+ *
+ * Inputs: window sequence (short or long blocks)
+ *
+ * Outputs: updated TNSInfo_t struct
+ * buffer of decoded (signed) TNS filter coefficients
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodeTNSInfo(int32_t winSequence, TNSInfo_t *ti, int8_t *tnsCoef)
+{
+ int32_t i, w, f, coefBits, compress;
+ int8_t c, s, n;
+ uint8_t *filtLength, *filtOrder, *filtDir;
+
+ filtLength = ti->length;
+ filtOrder = ti->order;
+ filtDir = ti->dir;
+
+ if (winSequence == 2) {
+ /* short blocks */
+ for (w = 0; w < NWINDOWS_SHORT; w++) {
+ ti->numFilt[w] = GetBits(1);
+ if (ti->numFilt[w]) {
+ ti->coefRes[w] = GetBits(1) + 3;
+ *filtLength = GetBits(4);
+ *filtOrder = GetBits(3);
+ if (*filtOrder) {
+ *filtDir++ = GetBits(1);
+ compress = GetBits(1);
+ coefBits = (int32_t)ti->coefRes[w] - compress; /* 2, 3, or 4 */
+ s = sgnMask[coefBits - 2];
+ n = negMask[coefBits - 2];
+ for (i = 0; i < *filtOrder; i++) {
+ c = GetBits(coefBits);
+ if (c & s) c |= n;
+ *tnsCoef++ = c;
+ }
+ }
+ filtLength++;
+ filtOrder++;
+ }
+ }
+ } else {
+ /* long blocks */
+ ti->numFilt[0] = GetBits(2);
+ if (ti->numFilt[0])
+ ti->coefRes[0] = GetBits(1) + 3;
+ for (f = 0; f < ti->numFilt[0]; f++) {
+ *filtLength = GetBits(6);
+ *filtOrder = GetBits(5);
+ if (*filtOrder) {
+ *filtDir++ = GetBits(1);
+ compress = GetBits(1);
+ coefBits = (int32_t)ti->coefRes[0] - compress; /* 2, 3, or 4 */
+ s = sgnMask[coefBits - 2];
+ n = negMask[coefBits - 2];
+ for (i = 0; i < *filtOrder; i++) {
+ c = GetBits(coefBits);
+ if (c & s) c |= n;
+ *tnsCoef++ = c;
+ }
+ }
+ filtLength++;
+ filtOrder++;
+ }
+ }
+}
+
+/* bitstream field lengths for gain control data:
+ * gainBits[winSequence][0] = maxWindow (how many gain windows there are)
+ * gainBits[winSequence][1] = locBitsZero (bits for alocCode if window == 0)
+ * gainBits[winSequence][2] = locBits (bits for alocCode if window != 0)
+ */
+static const uint8_t gainBits[4][3] = {
+ {1, 5, 5}, /* long */
+ {2, 4, 2}, /* start */
+ {8, 2, 2}, /* short */
+ {2, 4, 5}, /* stop */
+};
+
+/***********************************************************************************************************************
+ * Function: DecodeGainControlInfo
+ *
+ * Description: decode gain control information (SSR profile only)
+ *
+ * Inputs: window sequence (short or long blocks)
+ *
+ * Outputs: updated GainControlInfo_t struct
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodeGainControlInfo(int32_t winSequence, GainControlInfo_t *gi)
+{
+ int32_t bd, wd, ad;
+ int32_t locBits, locBitsZero, maxWin;
+
+ gi->maxBand = GetBits(2);
+ maxWin = (int32_t)gainBits[winSequence][0];
+ locBitsZero = (int32_t)gainBits[winSequence][1];
+ locBits = (int32_t)gainBits[winSequence][2];
+
+ for (bd = 1; bd <= gi->maxBand; bd++) {
+ for (wd = 0; wd < maxWin; wd++) {
+ gi->adjNum[bd][wd] = GetBits(3);
+ for (ad = 0; ad < gi->adjNum[bd][wd]; ad++) {
+ gi->alevCode[bd][wd][ad] = GetBits(4);
+ gi->alocCode[bd][wd][ad] = GetBits(wd == 0 ? locBitsZero : locBits);
+ }
+ }
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeICS
+ *
+ * Description: decode individual channel stream
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: updated section data, scale factor data, pulse data, TNS data,
+ * and gain control data
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodeICS(int32_t ch)
+{
+ int32_t globalGain;
+ ICSInfo_t *icsInfo;
+ TNSInfo_t *ti;
+ GainControlInfo_t *gi;
+
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+
+ globalGain = GetBits(8);
+ if (!m_PSInfoBase->commonWin)
+ DecodeICSInfo(icsInfo, m_PSInfoBase->sampRateIdx);
+
+ DecodeSectionData(icsInfo->winSequence, icsInfo->numWinGroup, icsInfo->maxSFB, m_PSInfoBase->sfbCodeBook[ch]);
+
+ DecodeScaleFactors(icsInfo->numWinGroup, icsInfo->maxSFB, globalGain, m_PSInfoBase->sfbCodeBook[ch],
+ m_PSInfoBase->scaleFactors[ch]);
+
+ m_pulseInfo[ch].pulseDataPresent = GetBits(1);
+ if (m_pulseInfo[ch].pulseDataPresent)
+ DecodePulseInfo(ch);
+
+ ti = &m_PSInfoBase->tnsInfo[ch];
+ ti->tnsDataPresent = GetBits(1);
+ if (ti->tnsDataPresent)
+ DecodeTNSInfo(icsInfo->winSequence, ti, ti->coef);
+
+ gi = &m_PSInfoBase->gainControlInfo[ch];
+ gi->gainControlDataPresent = GetBits(1);
+ if (gi->gainControlDataPresent)
+ DecodeGainControlInfo(icsInfo->winSequence, gi);
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeNoiselessData
+ *
+ * Description: decode noiseless data (side info and transform coefficients)
+ *
+ * Inputs: double pointer to buffer pointing to start of individual channel stream
+ * (14496-3, table 4.4.24)
+ * pointer to bit offset
+ * pointer to number of valid bits remaining in buf
+ * index of current channel
+ *
+ * Outputs: updated global gain, section data, scale factor data, pulse data,
+ * TNS data, gain control data, and spectral data
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ **********************************************************************************************************************/
+int32_t DecodeNoiselessData(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail, int32_t ch)
+{
+ int32_t bitsUsed;
+ ICSInfo_t *icsInfo;
+
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+
+ SetBitstreamPointer((*bitsAvail+7) >> 3, *buf);
+ GetBits(*bitOffset);
+
+ DecodeICS(ch);
+
+ if (icsInfo->winSequence == 2)
+ DecodeSpectrumShort(ch);
+ else
+ DecodeSpectrumLong(ch);
+
+ bitsUsed = CalcBitsUsed(*buf, *bitOffset);
+ *buf += ((bitsUsed + *bitOffset) >> 3);
+ *bitOffset = ((bitsUsed + *bitOffset) & 0x07);
+ *bitsAvail -= bitsUsed;
+
+ m_AACDecInfo->sbDeinterleaveReqd[ch] = 0;
+ m_AACDecInfo->tnsUsed |= m_PSInfoBase->tnsInfo[ch].tnsDataPresent; /* set flag if TNS used for any channel */
+
+ return ERR_AAC_NONE;
+}
+/***********************************************************************************************************************
+ * Function: DecodeHuffmanScalar
+ *
+ * Description: decode one Huffman symbol from bitstream
+ *
+ * Inputs: pointers to Huffman table and info struct
+ * left-aligned bit buffer with >= huffTabInfo->maxBits bits
+ *
+ * Outputs: decoded symbol in *val
+ *
+ * Return: number of bits in symbol
+ *
+ * Notes: assumes canonical Huffman codes:
+ * first CW always 0, we have "count" CW's of length "nBits" bits
+ * starting CW for codes of length nBits+1 =
+ * (startCW[nBits] + count[nBits]) << 1
+ * if there are no codes at nBits, then we just keep << 1 each time
+ * (since count[nBits] = 0)
+ **********************************************************************************************************************/
+int32_t DecodeHuffmanScalar(const uint16_t *huffTab, const HuffInfo_t *huffTabInfo, uint32_t bitBuf, int32_t *val)
+{
+ uint32_t count, start, shift, t;
+ const uint8_t *countPtr;
+ const uint16_t *map;
+
+ map = huffTab + huffTabInfo->offset;
+ countPtr = huffTabInfo->count;
+
+ start = 0;
+ count = 0;
+ shift = 32;
+ do {
+ start += count;
+ start <<= 1;
+ map += count;
+ count = *countPtr++;
+ shift--;
+ t = (bitBuf >> shift) - start;
+ } while (t >= count);
+
+ *val = (int32_t)map[t];
+ return (countPtr - huffTabInfo->count);
+}
+
+/***********************************************************************************************************************
+* Function: UnpackADTSHeader
+*
+* Description: parse the ADTS frame header and initialize decoder state, Audio Data Transport Stream
+*
+* Inputs: double pointer to buffer with complete ADTS frame header (byte aligned)
+* header size = 7 bytes, plus 2 if CRC
+*
+* Outputs: filled in ADTS struct
+* updated buffer pointer
+* updated bit offset
+* updated number of available bits
+*
+* Return: 0 if successful, error code (< 0) if error
+* verify that fixed fields don't change between frames
+***********************************************************************************************************************/
+int32_t UnpackADTSHeader(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail)
+{
+ int32_t bitsUsed;
+
+ /* init bitstream reader */
+ SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf);
+ GetBits(*bitOffset);
+
+ /* verify that first 12 bits of header are syncword */
+ if (GetBits(12) != 0x0fff) {
+ return ERR_AAC_INVALID_ADTS_HEADER;
+ }
+
+ /* fixed fields - should not change from frame to frame */
+ m_fhADTS.id = GetBits(1);
+ m_fhADTS.layer = GetBits(2);
+ m_fhADTS.protectBit = GetBits(1);
+ m_fhADTS.profile = GetBits(2);
+ m_fhADTS.sampRateIdx = GetBits(4);
+ m_fhADTS.privateBit = GetBits(1);
+ m_fhADTS.channelConfig = GetBits(3);
+ m_fhADTS.origCopy = GetBits(1);
+ m_fhADTS.home = GetBits(1);
+
+ /* variable fields - can change from frame to frame */
+ m_fhADTS.copyBit = GetBits(1);
+ m_fhADTS.copyStart = GetBits(1);
+ m_fhADTS.frameLength = GetBits(13);
+ m_fhADTS.bufferFull = GetBits(11);
+ m_fhADTS.numRawDataBlocks = GetBits(2) + 1;
+
+ /* note - MPEG4 spec, correction 1 changes how CRC is handled when protectBit == 0 and numRawDataBlocks > 1 */
+ if (m_fhADTS.protectBit == 0)
+ m_fhADTS.crcCheckWord = GetBits(16);
+
+ /* byte align */
+ ByteAlignBitstream(); /* should always be aligned anyway */
+
+ /* check validity of header */
+ if (m_fhADTS.layer != 0 || m_fhADTS.profile != AAC_PROFILE_LC ||
+ m_fhADTS.sampRateIdx >= NUM_SAMPLE_RATES || m_fhADTS.channelConfig >= NUM_DEF_CHAN_MAPS)
+ return ERR_AAC_INVALID_ADTS_HEADER;
+
+#ifndef AAC_ENABLE_MPEG4
+ if (m_fhADTS.id != 1)
+ return ERR_AAC_MPEG4_UNSUPPORTED;
+#endif
+
+
+ /* update codec info */
+ m_PSInfoBase->sampRateIdx = m_fhADTS.sampRateIdx;
+ if (!m_PSInfoBase->useImpChanMap)
+ m_PSInfoBase->nChans = channelMapTab[m_fhADTS.channelConfig];
+
+ /* syntactic element fields will be read from bitstream for each element */
+ m_AACDecInfo->prevBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currInstTag = -1;
+
+ /* fill in user-accessible data */
+ m_AACDecInfo->bitRate = 0;
+ m_AACDecInfo->nChans = m_PSInfoBase->nChans;
+ m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx];
+ m_AACDecInfo->id = m_fhADTS.id;
+ m_AACDecInfo->profile = m_fhADTS.profile;
+ m_AACDecInfo->sbrEnabled = 0;
+ m_AACDecInfo->adtsBlocksLeft = m_fhADTS.numRawDataBlocks;
+
+ /* update bitstream reader */
+ bitsUsed = CalcBitsUsed(*buf, *bitOffset);
+ *buf += (bitsUsed + *bitOffset) >> 3;
+ *bitOffset = (bitsUsed + *bitOffset) & 0x07;
+ *bitsAvail -= bitsUsed ;
+ if (*bitsAvail < 0)
+ return ERR_AAC_INDATA_UNDERFLOW;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+* Function: GetADTSChannelMapping
+*
+* Description: determine the number of channels from implicit mapping rules
+*
+* Inputs: pointer to start of raw_data_block
+* bit offset
+* bits available
+*
+* Outputs: updated number of channels
+*
+* Return: 0 if successful, error code (< 0) if error
+*
+* Notes: calculates total number of channels using rules in 14496-3, 4.5.1.2.1
+* does not attempt to deduce speaker geometry
+***********************************************************************************************************************/
+int32_t GetADTSChannelMapping(uint8_t *buf, int32_t bitOffset, int32_t bitsAvail)
+{
+ int32_t ch, nChans, elementChans, err;
+
+ nChans = 0;
+ do {
+ /* parse next syntactic element */
+ err = DecodeNextElement(&buf, &bitOffset, &bitsAvail);
+ if (err)
+ return err;
+
+ elementChans = elementNumChans[m_AACDecInfo->currBlockID];
+ nChans += elementChans;
+
+ for (ch = 0; ch < elementChans; ch++) {
+ err = DecodeNoiselessData(&buf, &bitOffset, &bitsAvail, ch);
+ if (err)
+ return err;
+ }
+ } while (m_AACDecInfo->currBlockID != AAC_ID_END);
+
+ if (nChans <= 0)
+ return ERR_AAC_CHANNEL_MAP;
+
+ /* update number of channels in codec state and user-accessible info structs */
+ m_PSInfoBase->nChans = nChans;
+ m_AACDecInfo->nChans = m_PSInfoBase->nChans;
+ m_PSInfoBase->useImpChanMap = 1;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+* Function: GetNumChannelsADIF
+*
+* Description: get number of channels from program config elements in an ADIF file
+*
+* Inputs: array of filled-in program config element structures
+* number of PCE's
+*
+* Outputs: none
+*
+* Return: total number of channels in file
+* -1 if error (invalid number of PCE's or unsupported mode)
+***********************************************************************************************************************/
+int32_t GetNumChannelsADIF(int32_t nPCE)
+{
+ int32_t i, j, nChans;
+
+ if (nPCE < 1 || nPCE > MAX_NUM_PCE_ADIF)
+ return -1;
+
+ nChans = 0;
+ for (i = 0; i < nPCE; i++) {
+ /* for now: only support LC, no channel coupling */
+ if (m_pce[i]->profile != AAC_PROFILE_LC || m_pce[i]->numCCE > 0)
+ return -1;
+
+ /* add up number of channels in all channel elements (assume all single-channel) */
+ nChans += m_pce[i]->numFCE;
+ nChans += m_pce[i]->numSCE;
+ nChans += m_pce[i]->numBCE;
+ nChans += m_pce[i]->numLCE;
+
+ /* add one more for every element which is a channel pair */
+ for (j = 0; j < m_pce[i]->numFCE; j++) {
+ if ((m_pce[i]->fce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */
+ nChans++;
+ }
+ for (j = 0; j < m_pce[i]->numSCE; j++) {
+ if ((m_pce[i]->sce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */
+ nChans++;
+ }
+ for (j = 0; j < m_pce[i]->numBCE; j++) {
+ if ((m_pce[i]->bce[j] & 0x10) >> 4) /* bit 4 = SCE/CPE flag */
+ nChans++;
+ }
+
+ }
+
+ return nChans;
+}
+
+/***********************************************************************************************************************
+* Function: GetSampleRateIdxADIF
+*
+* Description: get sampling rate index from program config elements in an ADIF file
+*
+* Inputs: array of filled-in program config element structures
+* number of PCE's
+*
+* Outputs: none
+*
+* Return: sample rate of file
+* -1 if error (invalid number of PCE's or sample rate mismatch)
+***********************************************************************************************************************/
+int32_t GetSampleRateIdxADIF(int32_t nPCE)
+{
+ int32_t i, idx;
+
+ if (nPCE < 1 || nPCE > MAX_NUM_PCE_ADIF)
+ return -1;
+
+ /* make sure all PCE's have the same sample rate */
+ idx = m_pce[0]->sampRateIdx;
+ for (i = 1; i < nPCE; i++) {
+ if (m_pce[i]->sampRateIdx != idx)
+ return -1;
+ }
+
+ return idx;
+}
+
+/***********************************************************************************************************************
+* Function: UnpackADIFHeader
+*
+* Description: parse the ADIF file header and initialize decoder state
+*
+* Inputs: double pointer to buffer with complete ADIF header
+* (starting at 'A' in 'ADIF' tag)
+* pointer to bit offset
+* pointer to number of valid bits remaining in inbuf
+*
+* Outputs: filled-in ADIF struct
+* updated buffer pointer
+* updated bit offset
+* updated number of available bits
+*
+* Return: 0 if successful, error code (< 0) if error
+***********************************************************************************************************************/
+int32_t UnpackADIFHeader(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail)
+{
+ uint8_t i;
+ int32_t bitsUsed;
+
+ /* init bitstream reader */
+ SetBitstreamPointer((*bitsAvail + 7) >> 3, *buf);
+ GetBits(*bitOffset);
+
+ /* verify that first 32 bits of header are "ADIF" */
+ if (GetBits(8) != 'A' || GetBits(8) != 'D' || GetBits(8) != 'I' || GetBits(8) != 'F')
+ return ERR_AAC_INVALID_ADIF_HEADER;
+
+ /* read ADIF header fields */
+ m_fhADIF.copyBit = GetBits(1);
+ if (m_fhADIF.copyBit) {
+ for (i = 0; i < ADIF_COPYID_SIZE; i++)
+ m_fhADIF.copyID[i] = GetBits(8);
+ }
+ m_fhADIF.origCopy = GetBits(1);
+ m_fhADIF.home = GetBits(1);
+ m_fhADIF.bsType = GetBits(1);
+ m_fhADIF.bitRate = GetBits(23);
+ m_fhADIF.numPCE = GetBits(4) + 1; /* add 1 (so range = [1, 16]) */
+ if (m_fhADIF.bsType == 0)
+ m_fhADIF.bufferFull = GetBits(20);
+
+ /* parse all program config elements */
+ for (i = 0; i < m_fhADIF.numPCE; i++)
+ DecodeProgramConfigElement(i);
+
+ /* byte align */
+ ByteAlignBitstream();
+
+ /* update codec info */
+ m_PSInfoBase->nChans = GetNumChannelsADIF(m_fhADIF.numPCE);
+ m_PSInfoBase->sampRateIdx = GetSampleRateIdxADIF(m_fhADIF.numPCE);
+
+ /* check validity of header */
+ if (m_PSInfoBase->nChans < 0 || m_PSInfoBase->sampRateIdx < 0 || m_PSInfoBase->sampRateIdx >= NUM_SAMPLE_RATES)
+ return ERR_AAC_INVALID_ADIF_HEADER;
+
+ /* syntactic element fields will be read from bitstream for each element */
+ m_AACDecInfo->prevBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currInstTag = -1;
+
+ /* fill in user-accessible data */
+ m_AACDecInfo->bitRate = 0;
+ m_AACDecInfo->nChans = m_PSInfoBase->nChans;
+ m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx];
+ m_AACDecInfo->profile = m_pce[0]->profile;
+ m_AACDecInfo->sbrEnabled = 0;
+
+ /* update bitstream reader */
+ bitsUsed = CalcBitsUsed(*buf, *bitOffset);
+ *buf += (bitsUsed + *bitOffset) >> 3;
+ *bitOffset = (bitsUsed + *bitOffset) & 0x07;
+ *bitsAvail -= bitsUsed ;
+ if (*bitsAvail < 0)
+ return ERR_AAC_INDATA_UNDERFLOW;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+* Function: SetRawBlockParams
+*
+* Description: set internal state variables for decoding a stream of raw data blocks
+*
+* Inputs: flag indicating source of parameters (from previous headers or passed
+* explicitly by caller)
+* number of channels
+* sample rate
+* profile ID
+*
+* Outputs: updated state variables in aacDecInfo
+*
+* Return: 0 if successful, error code (< 0) if error
+*
+* Notes: if copyLast == 1, then m_PSInfoBase->nChans, m_PSInfoBase->sampRateIdx, and
+* aacDecInfo->profile are not changed (it's assumed that we already
+* set them, such as by a previous call to UnpackADTSHeader())
+* if copyLast == 0, then the parameters we passed in are used instead
+***********************************************************************************************************************/
+int32_t SetRawBlockParams(int32_t copyLast, int32_t nChans, int32_t sampRate, int32_t profile)
+{
+ int32_t idx;
+
+ if (!copyLast) {
+ m_AACDecInfo->profile = profile;
+ m_PSInfoBase->nChans = nChans;
+ for (idx = 0; idx < NUM_SAMPLE_RATES; idx++) {
+ if (sampRate == sampRateTab[idx]) {
+ m_PSInfoBase->sampRateIdx = idx;
+ break;
+ }
+ }
+ if (idx == NUM_SAMPLE_RATES)
+ return ERR_AAC_INVALID_FRAME;
+ }
+ m_AACDecInfo->nChans = m_PSInfoBase->nChans;
+ m_AACDecInfo->sampRate = sampRateTab[m_PSInfoBase->sampRateIdx];
+
+ /* check validity of header */
+ if (m_PSInfoBase->sampRateIdx >= NUM_SAMPLE_RATES || m_PSInfoBase->sampRateIdx < 0 ||
+ m_AACDecInfo->profile != AAC_PROFILE_LC)
+ return ERR_AAC_RAWBLOCK_PARAMS;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+* Function: PrepareRawBlock
+*
+* Description: reset per-block state variables for raw blocks (no ADTS/ADIF headers)
+*
+* Inputs: none
+*
+* Outputs: updated state variables in aacDecInfo
+*
+* Return: 0 if successful, error code (< 0) if error
+***********************************************************************************************************************/
+int32_t PrepareRawBlock()
+{
+ /* syntactic element fields will be read from bitstream for each element */
+ m_AACDecInfo->prevBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currBlockID = AAC_ID_INVALID;
+ m_AACDecInfo->currInstTag = -1;
+
+ /* fill in user-accessible data */
+ m_AACDecInfo->bitRate = 0;
+ m_AACDecInfo->sbrEnabled = 0;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+ * Function: DequantBlock
+ *
+ * Description: dequantize one block of transform coefficients (in-place)
+ *
+ * Inputs: quantized transform coefficients, range = [0, 8191]
+ * number of samples to dequantize
+ * scalefactor for this block of data, range = [0, 256]
+ *
+ * Outputs: dequantized transform coefficients in Q(FBITS_OUT_DQ_OFF)
+ *
+ * Return: guard bit mask (OR of abs value of all dequantized coefs)
+ *
+ * Notes: applies dequant formula y = pow(x, 4.0/3.0) * pow(2, (scale - 100)/4.0)
+ * * pow(2, FBITS_OUT_DQ_OFF)
+ * clips outputs to Q(FBITS_OUT_DQ_OFF)
+ * output has no minimum number of guard bits
+ **********************************************************************************************************************/
+int32_t DequantBlock(int32_t *inbuf, int32_t nSamps, int32_t scale)
+{
+ int32_t iSamp, scalef, scalei, x, y, gbMask, shift, tab4[4];
+ const uint32_t *tab16, *coef;
+
+ if (nSamps <= 0)
+ return 0;
+
+ scale -= SF_OFFSET; /* new range = [-100, 156] */
+
+ /* with two's complement numbers, scalei/scalef factorization works for pos and neg values of scale:
+ * [+4...+7] >> 2 = +1, [ 0...+3] >> 2 = 0, [-4...-1] >> 2 = -1, [-8...-5] >> 2 = -2 ...
+ * (-1 & 0x3) = 3, (-2 & 0x3) = 2, (-3 & 0x3) = 1, (0 & 0x3) = 0
+ *
+ * Example: 2^(-5/4) = 2^(-1) * 2^(-1/4) = 2^-2 * 2^(3/4)
+ */
+ tab16 = pow43_14[scale & 0x3];
+ scalef = pow14[scale & 0x3];
+ scalei = (scale >> 2) + FBITS_OUT_DQ_OFF;
+
+ /* cache first 4 values:
+ * tab16[j] = Q28 for j = [0,3]
+ * tab4[x] = x^(4.0/3.0) * 2^(0.25*scale), Q(FBITS_OUT_DQ_OFF)
+ */
+ shift = 28 - scalei;
+ if (shift > 31) {
+ tab4[0] = tab4[1] = tab4[2] = tab4[3] = 0;
+ } else if (shift <= 0) {
+ shift = -shift;
+ if (shift > 31)
+ shift = 31;
+ for (x = 0; x < 4; x++) {
+ y = tab16[x];
+ if (y > (0x7fffffff >> shift))
+ y = 0x7fffffff; /* clip (rare) */
+ else
+ y <<= shift;
+ tab4[x] = y;
+ }
+ } else {
+ tab4[0] = 0;
+ tab4[1] = tab16[1] >> shift;
+ tab4[2] = tab16[2] >> shift;
+ tab4[3] = tab16[3] >> shift;
+ }
+
+ gbMask = 0;
+ do {
+ iSamp = *inbuf;
+ x = FASTABS(iSamp);
+
+ if (x < 4) {
+ y = tab4[x];
+ } else {
+
+ if (x < 16) {
+ /* result: y = Q25 (tab16 = Q25) */
+ y = tab16[x];
+ shift = 25 - scalei;
+ } else if (x < 64) {
+ /* result: y = Q21 (pow43tab[j] = Q23, scalef = Q30) */
+ y = pow43[x-16];
+ shift = 21 - scalei;
+ y = MULSHIFT32(y, scalef);
+ } else {
+ /* normalize to [0x40000000, 0x7fffffff]
+ * input x = [64, 8191] = [64, 2^13-1]
+ * ranges:
+ * shift = 7: 64 - 127
+ * shift = 6: 128 - 255
+ * shift = 5: 256 - 511
+ * shift = 4: 512 - 1023
+ * shift = 3: 1024 - 2047
+ * shift = 2: 2048 - 4095
+ * shift = 1: 4096 - 8191
+ */
+ x <<= 17;
+ shift = 0;
+ if (x < 0x08000000)
+ x <<= 4, shift += 4;
+ if (x < 0x20000000)
+ x <<= 2, shift += 2;
+ if (x < 0x40000000)
+ x <<= 1, shift += 1;
+
+ coef = (x < SQRTHALF) ? poly43lo : poly43hi;
+
+ /* polynomial */
+ y = coef[0];
+ y = MULSHIFT32(y, x) + coef[1];
+ y = MULSHIFT32(y, x) + coef[2];
+ y = MULSHIFT32(y, x) + coef[3];
+ y = MULSHIFT32(y, x) + coef[4];
+ y = MULSHIFT32(y, pow2frac[shift]) << 3;
+
+ /* fractional scale
+ * result: y = Q21 (pow43tab[j] = Q23, scalef = Q30)
+ */
+ y = MULSHIFT32(y, scalef); /* now y is Q24 */
+ shift = 24 - scalei - pow2exp[shift];
+ }
+
+ /* integer scale */
+ if (shift <= 0) {
+ shift = -shift;
+ if (shift > 31)
+ shift = 31;
+
+ if (y > (0x7fffffff >> shift))
+ y = 0x7fffffff; /* clip (rare) */
+ else
+ y <<= shift;
+ } else {
+ if (shift > 31)
+ shift = 31;
+ y >>= shift;
+ }
+ }
+
+ /* sign and store (gbMask used to count GB's) */
+ gbMask |= y;
+
+ /* apply sign */
+ iSamp >>= 31;
+ y ^= iSamp;
+ y -= iSamp;
+
+ *inbuf++ = y;
+ } while (--nSamps);
+
+ return gbMask;
+}
+
+/***********************************************************************************************************************
+ * Function: AACDequantize
+ *
+ * Description: dequantize all transform coefficients for one channel
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: dequantized coefficients, including short-block deinterleaving
+ * flags indicating if intensity and/or PNS is active
+ * minimum guard bit count for dequantized coefficients
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ **********************************************************************************************************************/
+int32_t AACDequantize(int32_t ch)
+{
+ int32_t gp, cb, sfb, win, width, nSamps, gbMask;
+ int32_t *coef;
+ const uint16_t *sfbTab;
+ uint8_t *sfbCodeBook;
+ int16_t *scaleFactors;
+ ICSInfo_t *icsInfo;
+
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+
+ if (icsInfo->winSequence == 2) {
+ sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx];
+ nSamps = NSAMPS_SHORT;
+ } else {
+ sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx];
+ nSamps = NSAMPS_LONG;
+ }
+ coef = m_PSInfoBase->coef[ch];
+ sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch];
+ scaleFactors = m_PSInfoBase->scaleFactors[ch];
+
+ m_PSInfoBase->intensityUsed[ch] = 0;
+ m_PSInfoBase->pnsUsed[ch] = 0;
+ gbMask = 0;
+ for (gp = 0; gp < icsInfo->numWinGroup; gp++) {
+ for (win = 0; win < icsInfo->winGroupLen[gp]; win++) {
+ for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) {
+ /* dequantize one scalefactor band (not necessary if codebook is intensity or PNS)
+ * for zero codebook, still run dequantizer in case non-zero pulse data was added
+ */
+ cb = (int32_t)(sfbCodeBook[sfb]);
+ width = sfbTab[sfb+1] - sfbTab[sfb];
+ if (cb >= 0 && cb <= 11)
+ gbMask |= DequantBlock(coef, width, scaleFactors[sfb]);
+ else if (cb == 13)
+ m_PSInfoBase->pnsUsed[ch] = 1;
+ else if (cb == 14 || cb == 15)
+ m_PSInfoBase->intensityUsed[ch] = 1; /* should only happen if ch == 1 */
+ coef += width;
+ }
+ coef += (nSamps - sfbTab[icsInfo->maxSFB]);
+ }
+ sfbCodeBook += icsInfo->maxSFB;
+ scaleFactors += icsInfo->maxSFB;
+ }
+ m_AACDecInfo->pnsUsed |= m_PSInfoBase->pnsUsed[ch]; /* set flag if PNS used for any channel */
+
+ /* calculate number of guard bits in dequantized data */
+ m_PSInfoBase->gbCurrent[ch] = CLZ(gbMask) - 1;
+
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+ * Function: DeinterleaveShortBlocks
+ *
+ * Description: deinterleave transform coefficients in short blocks for one channel
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: deinterleaved coefficients (window groups into 8 separate windows)
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ *
+ * Notes: only necessary if deinterleaving not part of Huffman decoding
+ **********************************************************************************************************************/
+int32_t DeinterleaveShortBlocks(int32_t ch)
+{
+// (void)aacDecInfo;
+// (void)ch;
+ /* not used for this implementation - short block deinterleaving performed during Huffman decoding */
+ return ERR_AAC_NONE;
+}
+
+/***********************************************************************************************************************
+ * Function: Get32BitVal
+ *
+ * Description: generate 32-bit unsigned random number
+ *
+ * Inputs: last number calculated (seed, first time through)
+ *
+ * Outputs: new number, saved in *last
+ *
+ * Return: 32-bit number, uniformly distributed between [0, 2^32)
+ *
+ * Notes: uses simple linear congruential generator
+ **********************************************************************************************************************/
+uint32_t Get32BitVal(uint32_t *last)
+{
+ uint32_t r = *last;
+
+ /* use same coefs as MPEG reference code (classic LCG)
+ * use unsigned multiply to force reliable wraparound behavior in C (mod 2^32)
+ */
+ r = (1664525U * r) + 1013904223U;
+ *last = r;
+
+ return r;
+}
+
+/***********************************************************************************************************************
+ * Function: InvRootR
+ *
+ * Description: use Newton's method to solve for x = 1/sqrt(r)
+ *
+ * Inputs: r in Q30 format, range = [0.25, 1] (normalize inputs to this range)
+ *
+ * Outputs: none
+ *
+ * Return: x = Q29, range = (1, 2)
+ *
+ * Notes: guaranteed to converge and not overflow for any r in this range
+ *
+ * xn+1 = xn - f(xn)/f'(xn)
+ * f(x) = 1/sqrt(r) - x = 0 (find root)
+ * = 1/x^2 - r
+ * f'(x) = -2/x^3
+ *
+ * so xn+1 = xn/2 * (3 - r*xn^2)
+ *
+ * NUM_ITER_INVSQRT = 3, maxDiff = 1.3747e-02
+ * NUM_ITER_INVSQRT = 4, maxDiff = 3.9832e-04
+ **********************************************************************************************************************/
+int32_t InvRootR(int32_t r)
+{
+ int32_t i, xn, t;
+
+ /* use linear equation for initial guess
+ * x0 = -2*r + 3 (so x0 always >= correct answer in range [0.25, 1))
+ * xn = Q29 (at every step)
+ */
+ xn = (MULSHIFT32(r, X0_COEF_2) << 2) + X0_OFF_2;
+
+ for (i = 0; i < NUM_ITER_INVSQRT; i++) {
+ t = MULSHIFT32(xn, xn); /* Q26 = Q29*Q29 */
+ t = Q26_3 - (MULSHIFT32(r, t) << 2); /* Q26 = Q26 - (Q31*Q26 << 1) */
+ xn = MULSHIFT32(xn, t) << (6 - 1); /* Q29 = (Q29*Q26 << 6), and -1 for division by 2 */
+ }
+
+ /* clip to range (1.0, 2.0)
+ * (because of rounding, this can converge to xn slightly > 2.0 when r is near 0.25)
+ */
+ if (xn >> 30)
+ xn = (1 << 30) - 1;
+
+ return xn;
+}
+
+/***********************************************************************************************************************
+ * Function: ScaleNoiseVector
+ *
+ * Description: apply scaling to vector of noise coefficients for one scalefactor band
+ *
+ * Inputs: unscaled coefficients
+ * number of coefficients in vector (one scalefactor band of coefs)
+ * scalefactor for this band (i.e. noise energy)
+ *
+ * Outputs: nVals coefficients in Q(FBITS_OUT_DQ_OFF)
+ *
+ * Return: guard bit mask (OR of abs value of all noise coefs)
+ **********************************************************************************************************************/
+int32_t ScaleNoiseVector(int32_t *coef, int32_t nVals, int32_t sf)
+{
+
+/* pow(2, i/4.0) for i = [0,1,2,3], format = Q30 */
+static const int32_t pow14[4] PROGMEM = {
+ 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65
+};
+
+ int32_t i, c, spec, energy, sq, scalef, scalei, invSqrtEnergy, z, gbMask;
+
+ energy = 0;
+ for (i = 0; i < nVals; i++) {
+ spec = coef[i];
+
+ /* max nVals = max SFB width = 96, so energy can gain < 2^7 bits in accumulation */
+ sq = (spec * spec) >> 8; /* spec*spec range = (-2^30, 2^30) */
+ energy += sq;
+ }
+
+ /* unless nVals == 1 (or the number generator is broken...), this should not happen */
+ if (energy == 0)
+ return 0; /* coef[i] must = 0 for i = [0, nVals-1], so gbMask = 0 */
+
+ /* pow(2, sf/4) * pow(2, FBITS_OUT_DQ_OFF) */
+ scalef = pow14[sf & 0x3];
+ scalei = (sf >> 2) + FBITS_OUT_DQ_OFF;
+
+ /* energy has implied factor of 2^-8 since we shifted the accumulator
+ * normalize energy to range [0.25, 1.0), calculate 1/sqrt(1), and denormalize
+ * i.e. divide input by 2^(30-z) and convert to Q30
+ * output of 1/sqrt(i) now has extra factor of 2^((30-z)/2)
+ * for energy > 0, z is an even number between 0 and 28
+ * final scaling of invSqrtEnergy:
+ * 2^(15 - z/2) to compensate for implicit 2^(30-z) factor in input
+ * +4 to compensate for implicit 2^-8 factor in input
+ */
+ z = CLZ(energy) - 2; /* energy has at least 2 leading zeros (see acc loop) */
+ z &= 0xfffffffe; /* force even */
+ invSqrtEnergy = InvRootR(energy << z); /* energy << z must be in range [0x10000000, 0x40000000] */
+ scalei -= (15 - z/2 + 4); /* nInt = 1/sqrt(energy) in Q29 */
+
+ /* normalize for final scaling */
+ z = CLZ(invSqrtEnergy) - 1;
+ invSqrtEnergy <<= z;
+ scalei -= (z - 3 - 2); /* -2 for scalef, z-3 for invSqrtEnergy */
+ scalef = MULSHIFT32(scalef, invSqrtEnergy); /* scalef (input) = Q30, invSqrtEnergy = Q29 * 2^z */
+ gbMask = 0;
+
+ if (scalei < 0) {
+ scalei = -scalei;
+ if (scalei > 31)
+ scalei = 31;
+ for (i = 0; i < nVals; i++) {
+ c = MULSHIFT32(coef[i], scalef) >> scalei;
+ gbMask |= FASTABS(c);
+ coef[i] = c;
+ }
+ } else {
+ /* for scalei <= 16, no clipping possible (coef[i] is < 2^15 before scaling)
+ * for scalei > 16, just saturate exponent (rare)
+ * scalef is close to full-scale (since we normalized invSqrtEnergy)
+ * remember, we are just producing noise here
+ */
+ if (scalei > 16)
+ scalei = 16;
+ for (i = 0; i < nVals; i++) {
+ c = MULSHIFT32(coef[i] << scalei, scalef);
+ coef[i] = c;
+ gbMask |= FASTABS(c);
+ }
+ }
+
+ return gbMask;
+}
+
+/***********************************************************************************************************************
+ * Function: GenerateNoiseVector
+ *
+ * Description: create vector of noise coefficients for one scalefactor band
+ *
+ * Inputs: seed for number generator
+ * number of coefficients to generate
+ *
+ * Outputs: buffer of nVals coefficients, range = [-2^15, 2^15)
+ * updated seed for number generator
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void GenerateNoiseVector(int32_t *coef, int32_t *last, int32_t nVals)
+{
+ int32_t i;
+
+ for (i = 0; i < nVals; i++)
+ coef[i] = ((int32_t)Get32BitVal((uint32_t *)last)) >> 16;
+}
+
+/***********************************************************************************************************************
+ * Function: CopyNoiseVector
+ *
+ * Description: copy vector of noise coefficients for one scalefactor band from L to R
+ *
+ * Inputs: buffer of left coefficients
+ * number of coefficients to copy
+ *
+ * Outputs: buffer of right coefficients
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CopyNoiseVector(int32_t *coefL, int32_t *coefR, int32_t nVals)
+{
+ int32_t i;
+
+ for (i = 0; i < nVals; i++)
+ coefR[i] = coefL[i];
+}
+
+/***********************************************************************************************************************
+ * Function: PNS
+ *
+ * Description: apply perceptual noise substitution, if enabled (MPEG-4 only)
+ *
+ * Inputs: index of current channel
+ *
+ * Outputs: shaped noise in scalefactor bands where PNS is active
+ * updated minimum guard bit count for this channel
+ *
+ * Return: 0 if successful, -1 if error
+ **********************************************************************************************************************/
+int32_t PNS(int32_t ch)
+{
+ int32_t gp, sfb, win, width, nSamps, gb, gbMask;
+ int32_t *coef;
+ const uint16_t *sfbTab;
+ uint8_t *sfbCodeBook;
+ int16_t *scaleFactors;
+ int32_t msMaskOffset, checkCorr, genNew;
+ uint8_t msMask;
+ uint8_t *msMaskPtr;
+ ICSInfo_t *icsInfo;
+
+ icsInfo = (ch == 1 && m_PSInfoBase->commonWin == 1) ? &(m_PSInfoBase->icsInfo[0]) : &(m_PSInfoBase->icsInfo[ch]);
+
+ if (!m_PSInfoBase->pnsUsed[ch])
+ return 0;
+
+ if (icsInfo->winSequence == 2) {
+ sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx];
+ nSamps = NSAMPS_SHORT;
+ } else {
+ sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx];
+ nSamps = NSAMPS_LONG;
+ }
+ coef = m_PSInfoBase->coef[ch];
+ sfbCodeBook = m_PSInfoBase->sfbCodeBook[ch];
+ scaleFactors = m_PSInfoBase->scaleFactors[ch];
+ checkCorr = (m_AACDecInfo->currBlockID == AAC_ID_CPE && m_PSInfoBase->commonWin == 1 ? 1 : 0);
+
+ gbMask = 0;
+ for (gp = 0; gp < icsInfo->numWinGroup; gp++) {
+ for (win = 0; win < icsInfo->winGroupLen[gp]; win++) {
+ msMaskPtr = m_PSInfoBase->msMaskBits + ((gp*icsInfo->maxSFB) >> 3);
+ msMaskOffset = ((gp*icsInfo->maxSFB) & 0x07);
+ msMask = (*msMaskPtr++) >> msMaskOffset;
+
+ for (sfb = 0; sfb < icsInfo->maxSFB; sfb++) {
+ width = sfbTab[sfb+1] - sfbTab[sfb];
+ if (sfbCodeBook[sfb] == 13) {
+ if (ch == 0) {
+ /* generate new vector, copy into ch 1 if it's possible that the channels will be correlated
+ * if ch 1 has PNS enabled for this SFB but it's uncorrelated (i.e. ms_used == 0),
+ * the copied values will be overwritten when we process ch 1
+ */
+ GenerateNoiseVector(coef, &m_PSInfoBase->pnsLastVal, width);
+ if (checkCorr && m_PSInfoBase->sfbCodeBook[1][gp*icsInfo->maxSFB + sfb] == 13)
+ CopyNoiseVector(coef, m_PSInfoBase->coef[1] + (coef - m_PSInfoBase->coef[0]), width);
+ } else {
+ /* generate new vector if no correlation between channels */
+ genNew = 1;
+ if (checkCorr && m_PSInfoBase->sfbCodeBook[0][gp*icsInfo->maxSFB + sfb] == 13) {
+ if((m_PSInfoBase->msMaskPresent==1 && (msMask & 0x01)) || m_PSInfoBase->msMaskPresent == 2 )
+ genNew = 0;
+ }
+ if (genNew)
+ GenerateNoiseVector(coef, &m_PSInfoBase->pnsLastVal, width);
+ }
+ gbMask |= ScaleNoiseVector(coef, width, m_PSInfoBase->scaleFactors[ch][gp*icsInfo->maxSFB + sfb]);
+ }
+ coef += width;
+
+ /* get next mask bit (should be branchless on ARM) */
+ msMask >>= 1;
+ if (++msMaskOffset == 8) {
+ msMask = *msMaskPtr++;
+ msMaskOffset = 0;
+ }
+ }
+ coef += (nSamps - sfbTab[icsInfo->maxSFB]);
+ }
+ sfbCodeBook += icsInfo->maxSFB;
+ scaleFactors += icsInfo->maxSFB;
+ }
+
+ /* update guard bit count if necessary */
+ gb = CLZ(gbMask) - 1;
+ if (m_PSInfoBase->gbCurrent[ch] > gb)
+ m_PSInfoBase->gbCurrent[ch] = gb;
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: GetSampRateIdx
+ *
+ * Description: get index of given sample rate
+ *
+ * Inputs: sample rate (in Hz)
+ *
+ * Outputs: none
+ *
+ * Return: index of sample rate (table 1.15 in 14496-3:2001(E))
+ * -1 if sample rate not found in table
+ **********************************************************************************************************************/
+int32_t GetSampRateIdx(int32_t sampRate)
+{
+ int32_t idx;
+
+ for (idx = 0; idx < NUM_SAMPLE_RATES; idx++) {
+ if (sampRate == sampRateTab[idx])
+ return idx;
+ }
+
+ return -1;
+}
+
+/***********************************************************************************************************************
+ * Function: StereoProcessGroup
+ *
+ * Description: apply mid-side and intensity stereo to group of transform coefficients
+ *
+ * Inputs: dequantized transform coefficients for both channels
+ * pointer to appropriate scalefactor band table
+ * mid-side mask enabled flag
+ * buffer with mid-side mask (one bit for each scalefactor band)
+ * bit offset into mid-side mask buffer
+ * max coded scalefactor band
+ * buffer of codebook indices for right channel
+ * buffer of scalefactors for right channel, range = [0, 256]
+ *
+ * Outputs: updated transform coefficients in Q(FBITS_OUT_DQ_OFF)
+ * updated minimum guard bit count for both channels
+ *
+ * Return: none
+ *
+ * Notes: assume no guard bits in input
+ * gains 0 int32_t bits
+ **********************************************************************************************************************/
+void StereoProcessGroup(int32_t *coefL, int32_t *coefR, const uint16_t *sfbTab,
+ int32_t msMaskPres, uint8_t *msMaskPtr, int32_t msMaskOffset, int32_t maxSFB,
+ uint8_t *cbRight, int16_t *sfRight, int32_t *gbCurrent)
+{
+//fb
+static const uint32_t pow14[2][4] PROGMEM = {
+ { 0xc0000000, 0xb3e407d7, 0xa57d8666, 0x945d819b },
+ { 0x40000000, 0x4c1bf829, 0x5a82799a, 0x6ba27e65 }
+};
+
+ int32_t sfb, width, cbIdx, sf, cl, cr, scalef, scalei;
+ int32_t gbMaskL, gbMaskR;
+ uint8_t msMask;
+
+ msMask = (*msMaskPtr++) >> msMaskOffset;
+ gbMaskL = 0;
+ gbMaskR = 0;
+
+ for (sfb = 0; sfb < maxSFB; sfb++) {
+ width = sfbTab[sfb+1] - sfbTab[sfb]; /* assume >= 0 (see sfBandTabLong/sfBandTabShort) */
+ cbIdx = cbRight[sfb];
+
+ if (cbIdx == 14 || cbIdx == 15) {
+ /* intensity stereo */
+ if (msMaskPres == 1 && (msMask & 0x01))
+ cbIdx ^= 0x01; /* invert_intensity(): 14 becomes 15, or 15 becomes 14 */
+ sf = -sfRight[sfb]; /* negative since we use identity 0.5^(x) = 2^(-x) (see spec) */
+ cbIdx &= 0x01; /* choose - or + scale factor */
+ scalef = pow14[cbIdx][sf & 0x03];
+ scalei = (sf >> 2) + 2; /* +2 to compensate for scalef = Q30 */
+
+ if (scalei > 0) {
+ if (scalei > 30)
+ scalei = 30;
+ do {
+ cr = MULSHIFT32(*coefL++, scalef);
+ {int32_t sign = (cr) >> 31; if (sign != (cr) >> (31-scalei)) {(cr) = sign ^ ((1 << (31-scalei)) - 1);}}
+ cr <<= scalei;
+ gbMaskR |= FASTABS(cr);
+ *coefR++ = cr;
+ } while (--width);
+ } else {
+ scalei = -scalei;
+ if (scalei > 31)
+ scalei = 31;
+ do {
+ cr = MULSHIFT32(*coefL++, scalef) >> scalei;
+ gbMaskR |= FASTABS(cr);
+ *coefR++ = cr;
+ } while (--width);
+ }
+ } else if ( cbIdx != 13 && ((msMaskPres == 1 && (msMask & 0x01)) || msMaskPres == 2) ) {
+ /* mid-side stereo (assumes no GB in inputs) */
+ do {
+ cl = *coefL;
+ cr = *coefR;
+
+ if ( (FASTABS(cl) | FASTABS(cr)) >> 30 ) {
+ /* avoid overflow (rare) */
+ cl >>= 1;
+ sf = cl + (cr >> 1);
+ {int32_t sign = (sf) >> 31; if (sign != (sf) >> (30)) {(sf) = sign ^ ((1 << (30)) - 1);}}
+ sf <<= 1;
+ cl = cl - (cr >> 1);
+ {int32_t sign = (cl) >> 31; if (sign != (cl) >> (30)) {(cl) = sign ^ ((1 << (30)) - 1);}}
+ cl <<= 1;
+ } else {
+ /* usual case */
+ sf = cl + cr;
+ cl -= cr;
+ }
+
+ *coefL++ = sf;
+ gbMaskL |= FASTABS(sf);
+ *coefR++ = cl;
+ gbMaskR |= FASTABS(cl);
+ } while (--width);
+
+ } else {
+ /* nothing to do */
+ coefL += width;
+ coefR += width;
+ }
+
+ /* get next mask bit (should be branchless on ARM) */
+ msMask >>= 1;
+ if (++msMaskOffset == 8) {
+ msMask = *msMaskPtr++;
+ msMaskOffset = 0;
+ }
+ }
+
+ cl = CLZ(gbMaskL) - 1;
+ if (gbCurrent[0] > cl)
+ gbCurrent[0] = cl;
+
+ cr = CLZ(gbMaskR) - 1;
+ if (gbCurrent[1] > cr)
+ gbCurrent[1] = cr;
+
+ return;
+}
+
+/***********************************************************************************************************************
+ * Function: StereoProcess
+ *
+ * Description: apply mid-side and intensity stereo, if enabled
+ *
+ * Inputs: none
+ *
+ * Outputs: updated transform coefficients in Q(FBITS_OUT_DQ_OFF)
+ * updated minimum guard bit count for both channels
+ *
+ * Return: 0 if successful, -1 if error
+ **********************************************************************************************************************/
+int32_t StereoProcess()
+{
+ ICSInfo_t *icsInfo;
+ int32_t gp, win, nSamps, msMaskOffset;
+ int32_t *coefL, *coefR;
+ uint8_t *msMaskPtr;
+ const uint16_t *sfbTab;
+
+
+ /* mid-side and intensity stereo require common_window == 1 (see MPEG4 spec, Correction 2, 2004) */
+ if (m_PSInfoBase->commonWin != 1 || m_AACDecInfo->currBlockID != AAC_ID_CPE)
+ return 0;
+
+ /* nothing to do */
+ if (!m_PSInfoBase->msMaskPresent && !m_PSInfoBase->intensityUsed[1])
+ return 0;
+
+ icsInfo = &(m_PSInfoBase->icsInfo[0]);
+ if (icsInfo->winSequence == 2) {
+ sfbTab = sfBandTabShort + sfBandTabShortOffset[m_PSInfoBase->sampRateIdx];
+ nSamps = NSAMPS_SHORT;
+ } else {
+ sfbTab = sfBandTabLong + sfBandTabLongOffset[m_PSInfoBase->sampRateIdx];
+ nSamps = NSAMPS_LONG;
+ }
+ coefL = m_PSInfoBase->coef[0];
+ coefR = m_PSInfoBase->coef[1];
+
+ /* do fused mid-side/intensity processing for each block (one long or eight short) */
+ msMaskOffset = 0;
+ msMaskPtr = m_PSInfoBase->msMaskBits;
+ for (gp = 0; gp < icsInfo->numWinGroup; gp++) {
+ for (win = 0; win < icsInfo->winGroupLen[gp]; win++) {
+ StereoProcessGroup(coefL, coefR, sfbTab, m_PSInfoBase->msMaskPresent,
+ msMaskPtr, msMaskOffset, icsInfo->maxSFB, m_PSInfoBase->sfbCodeBook[1] + gp*icsInfo->maxSFB,
+ m_PSInfoBase->scaleFactors[1] + gp*icsInfo->maxSFB, m_PSInfoBase->gbCurrent);
+ coefL += nSamps;
+ coefR += nSamps;
+ }
+ /* we use one bit per sfb, so there are maxSFB bits for each window group */
+ msMaskPtr += (msMaskOffset + icsInfo->maxSFB) >> 3;
+ msMaskOffset = (msMaskOffset + icsInfo->maxSFB) & 0x07;
+ }
+
+ ASSERT(coefL == m_PSInfoBase->coef[0] + 1024);
+ ASSERT(coefR == m_PSInfoBase->coef[1] + 1024);
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * Function: RatioPowInv
+ *
+ * Description: use Taylor (MacLaurin) series expansion to calculate (a/b) ^ (1/c)
+ *
+ * Inputs: a = [1, 64], b = [1, 64], c = [1, 64], a >= b
+ *
+ * Outputs: none
+ *
+ * Return: y = Q24, range ~= [0.015625, 64]
+ **********************************************************************************************************************/
+int32_t RatioPowInv(int32_t a, int32_t b, int32_t c)
+{
+ int32_t lna, lnb, i, p, t, y;
+
+ if (a < 1 || b < 1 || c < 1 || a > 64 || b > 64 || c > 64 || a < b)
+ return 0;
+
+ lna = MULSHIFT32(log2Tab[a], LOG2_EXP_INV) << 1; /* ln(a), Q28 */
+ lnb = MULSHIFT32(log2Tab[b], LOG2_EXP_INV) << 1; /* ln(b), Q28 */
+ p = (lna - lnb) / c; /* Q28 */
+
+ /* sum in Q24 */
+ y = (1 << 24);
+ t = p >> 4; /* t = p^1 * 1/1! (Q24)*/
+ y += t;
+
+ for (i = 2; i <= NUM_TERMS_RPI; i++) {
+ t = MULSHIFT32(invTab[i-1], t) << 2;
+ t = MULSHIFT32(p, t) << 4; /* t = p^i * 1/i! (Q24) */
+ y += t;
+ }
+
+ return y;
+}
+
+/***********************************************************************************************************************
+ * Function: SqrtFix
+ *
+ * Description: use binary search to calculate sqrt(q)
+ *
+ * Inputs: q = Q30
+ * number of fraction bits in input
+ *
+ * Outputs: number of fraction bits in output
+ *
+ * Return: lo = Q(fBitsOut)
+ *
+ * Notes: absolute precision varies depending on fBitsIn
+ * normalizes input to range [0x200000000, 0x7fffffff] and takes
+ * floor(sqrt(input)), and sets fBitsOut appropriately
+ **********************************************************************************************************************/
+int32_t SqrtFix(int32_t q, int32_t fBitsIn, int32_t *fBitsOut)
+{
+ int32_t z, lo, hi, mid;
+
+ if (q <= 0) {
+ *fBitsOut = fBitsIn;
+ return 0;
+ }
+
+ /* force even fBitsIn */
+ z = fBitsIn & 0x01;
+ q >>= z;
+ fBitsIn -= z;
+
+ /* for max precision, normalize to [0x20000000, 0x7fffffff] */
+ z = (CLZ(q) - 1);
+ z >>= 1;
+ q <<= (2*z);
+
+ /* choose initial bounds */
+ lo = 1;
+ if (q >= 0x10000000)
+ lo = 16384; /* (int32_t)sqrt(0x10000000) */
+ hi = 46340; /* (int32_t)sqrt(0x7fffffff) */
+
+ /* do binary search with 32x32->32 multiply test */
+ do {
+ mid = (lo + hi) >> 1;
+ if (mid*mid > q)
+ hi = mid - 1;
+ else
+ lo = mid + 1;
+ } while (hi >= lo);
+ lo--;
+
+ *fBitsOut = ((fBitsIn + 2*z) >> 1);
+ return lo;
+}
+
+/***********************************************************************************************************************
+ * Function: InvRNormalized
+ *
+ * Description: use Newton's method to solve for x = 1/r
+ *
+ * Inputs: r = Q31, range = [0.5, 1) (normalize your inputs to this range)
+ *
+ * Outputs: none
+ *
+ * Return: x = Q29, range ~= [1.0, 2.0]
+ *
+ * Notes: guaranteed to converge and not overflow for any r in [0.5, 1)
+ *
+ * xn+1 = xn - f(xn)/f'(xn)
+ * f(x) = 1/r - x = 0 (find root)
+ * = 1/x - r
+ * f'(x) = -1/x^2
+ *
+ * so xn+1 = xn - (1/xn - r) / (-1/xn^2)
+ * = xn * (2 - r*xn)
+ *
+ * NUM_ITER_IRN = 2, maxDiff = 6.2500e-02 (precision of about 4 bits)
+ * NUM_ITER_IRN = 3, maxDiff = 3.9063e-03 (precision of about 8 bits)
+ * NUM_ITER_IRN = 4, maxDiff = 1.5288e-05 (precision of about 16 bits)
+ * NUM_ITER_IRN = 5, maxDiff = 3.0034e-08 (precision of about 24 bits)
+ **********************************************************************************************************************/
+int32_t InvRNormalized(int32_t r)
+{
+ int32_t i, xn, t;
+
+ /* r = [0.5, 1.0)
+ * 1/r = (1.0, 2.0]
+ * so use 1.5 as initial guess
+ */
+ xn = Q28_15;
+
+ /* xn = xn*(2.0 - r*xn) */
+ for (i = NUM_ITER_IRN; i != 0; i--) {
+ t = MULSHIFT32(r, xn); /* Q31*Q29 = Q28 */
+ t = Q28_2 - t; /* Q28 */
+ xn = MULSHIFT32(xn, t) << 4; /* Q29*Q28 << 4 = Q29 */
+ }
+
+ return xn;
+}
+
+
+
+/***********************************************************************************************************************
+ * Function: BitReverse32
+ *
+ * Description: Ken's fast in-place bit reverse
+ *
+ * Inputs: buffer of 32 complex samples
+ *
+ * Outputs: bit-reversed samples in same buffer
+ *
+ * Return: none
+***********************************************************************************************************************/
+void BitReverse32(int32_t *inout)
+{
+ int32_t t;
+ t=inout[2] ; inout[2]=inout[32]; inout[32]=t;
+ t=inout[3] ; inout[3]=inout[33]; inout[33]=t;
+
+ t=inout[4] ; inout[4]=inout[16]; inout[16]=t;
+ t=inout[5] ; inout[5]=inout[17]; inout[17]=t;
+
+ t=inout[6] ; inout[6]=inout[48]; inout[48]=t;
+ t=inout[7] ; inout[7]=inout[49]; inout[49]=t;
+
+ t=inout[10]; inout[10]=inout[40]; inout[40]=t;
+ t=inout[11]; inout[11]=inout[41]; inout[41]=t;
+
+ t=inout[12]; inout[12]=inout[24]; inout[24]=t;
+ t=inout[13]; inout[13]=inout[25]; inout[25]=t;
+
+ t=inout[14]; inout[14]=inout[56]; inout[56]=t;
+ t=inout[15]; inout[15]=inout[57]; inout[57]=t;
+
+ t=inout[18]; inout[18]=inout[36]; inout[36]=t;
+ t=inout[19]; inout[19]=inout[37]; inout[37]=t;
+
+ t=inout[22]; inout[22]=inout[52]; inout[52]=t;
+ t=inout[23]; inout[23]=inout[53]; inout[53]=t;
+
+ t=inout[26]; inout[26]=inout[44]; inout[44]=t;
+ t=inout[27]; inout[27]=inout[45]; inout[45]=t;
+
+ t=inout[30]; inout[30]=inout[60]; inout[60]=t;
+ t=inout[31]; inout[31]=inout[61]; inout[61]=t;
+
+ t=inout[38]; inout[38]=inout[50]; inout[50]=t;
+ t=inout[39]; inout[39]=inout[51]; inout[51]=t;
+
+ t=inout[46]; inout[46]=inout[58]; inout[58]=t;
+ t=inout[47]; inout[47]=inout[59]; inout[59]=t;
+
+}
+
+/***********************************************************************************************************************
+ * Function: R8FirstPass32
+ *
+ * Description: radix-8 trivial pass for decimation-in-time FFT (log2(N) = 5)
+ *
+ * Inputs: buffer of (bit-reversed) samples
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: assumes 3 guard bits, gains 1 integer bit
+ * guard bits out = guard bits in - 3 (if inputs are full scale)
+ * or guard bits in - 2 (if inputs bounded to +/- sqrt(2)/2)
+ * see scaling comments in fft.c for base AAC
+ * should compile with no stack spills on ARM (verify compiled output)
+ * current instruction count (per pass): 16 LDR, 16 STR, 4 SMULL, 61 ALU
+ **********************************************************************************************************************/
+void R8FirstPass32(int32_t *r0)
+{
+ int32_t r1, r2, r3, r4, r5, r6, r7;
+ int32_t r8, r9, r10, r11, r12, r14;
+
+ /* number of passes = fft size / 8 = 32 / 8 = 4 */
+ r1 = (32 >> 3);
+ do {
+
+ r2 = r0[8];
+ r3 = r0[9];
+ r4 = r0[10];
+ r5 = r0[11];
+ r6 = r0[12];
+ r7 = r0[13];
+ r8 = r0[14];
+ r9 = r0[15];
+
+ r10 = r2 + r4;
+ r11 = r3 + r5;
+ r12 = r6 + r8;
+ r14 = r7 + r9;
+
+ r2 -= r4;
+ r3 -= r5;
+ r6 -= r8;
+ r7 -= r9;
+
+ r4 = r2 - r7;
+ r5 = r2 + r7;
+ r8 = r3 - r6;
+ r9 = r3 + r6;
+
+ r2 = r4 - r9;
+ r3 = r4 + r9;
+ r6 = r5 - r8;
+ r7 = r5 + r8;
+
+ r2 = MULSHIFT32(SQRTHALF, r2); /* can use r4, r5, r8, or r9 for constant and lo32 scratch reg */
+ r3 = MULSHIFT32(SQRTHALF, r3);
+ r6 = MULSHIFT32(SQRTHALF, r6);
+ r7 = MULSHIFT32(SQRTHALF, r7);
+
+ r4 = r10 + r12;
+ r5 = r10 - r12;
+ r8 = r11 + r14;
+ r9 = r11 - r14;
+
+ r10 = r0[0];
+ r11 = r0[2];
+ r12 = r0[4];
+ r14 = r0[6];
+
+ r10 += r11;
+ r12 += r14;
+
+ r4 >>= 1;
+ r10 += r12;
+ r4 += (r10 >> 1);
+ r0[ 0] = r4;
+ r4 -= (r10 >> 1);
+ r4 = (r10 >> 1) - r4;
+ r0[ 8] = r4;
+
+ r9 >>= 1;
+ r10 -= 2*r12;
+ r4 = (r10 >> 1) + r9;
+ r0[ 4] = r4;
+ r4 = (r10 >> 1) - r9;
+ r0[12] = r4;
+ r10 += r12;
+
+ r10 -= 2*r11;
+ r12 -= 2*r14;
+
+ r4 = r0[1];
+ r9 = r0[3];
+ r11 = r0[5];
+ r14 = r0[7];
+
+ r4 += r9;
+ r11 += r14;
+
+ r8 >>= 1;
+ r4 += r11;
+ r8 += (r4 >> 1);
+ r0[ 1] = r8;
+ r8 -= (r4 >> 1);
+ r8 = (r4 >> 1) - r8;
+ r0[ 9] = r8;
+
+ r5 >>= 1;
+ r4 -= 2*r11;
+ r8 = (r4 >> 1) - r5;
+ r0[ 5] = r8;
+ r8 = (r4 >> 1) + r5;
+ r0[13] = r8;
+ r4 += r11;
+
+ r4 -= 2*r9;
+ r11 -= 2*r14;
+
+ r9 = r10 - r11;
+ r10 += r11;
+ r14 = r4 + r12;
+ r4 -= r12;
+
+ r5 = (r10 >> 1) + r7;
+ r8 = (r4 >> 1) - r6;
+ r0[ 2] = r5;
+ r0[ 3] = r8;
+
+ r5 = (r9 >> 1) - r2;
+ r8 = (r14 >> 1) - r3;
+ r0[ 6] = r5;
+ r0[ 7] = r8;
+
+ r5 = (r10 >> 1) - r7;
+ r8 = (r4 >> 1) + r6;
+ r0[10] = r5;
+ r0[11] = r8;
+
+ r5 = (r9 >> 1) + r2;
+ r8 = (r14 >> 1) + r3;
+ r0[14] = r5;
+ r0[15] = r8;
+
+ r0 += 16;
+ r1--;
+ } while (r1 != 0);
+}
+
+/***********************************************************************************************************************
+ * Function: R4Core32
+ *
+ * Description: radix-4 pass for 32-point decimation-in-time FFT
+ *
+ * Inputs: buffer of samples
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: gain 2 integer bits
+ * guard bits out = guard bits in - 1 (if inputs are full scale)
+ * see scaling comments in fft.c for base AAC
+ * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add
+ * should compile with no stack spills on ARM (verify compiled output)
+ * current instruction count (per pass): 16 LDR, 16 STR, 4 SMULL, 61 ALU
+ **********************************************************************************************************************/
+void R4Core32(int32_t *r0)
+{
+ int32_t r2, r3, r4, r5, r6, r7;
+ int32_t r8, r9, r10, r12, r14;
+ int32_t *r1;
+
+ r1 = (int32_t *)twidTabOdd32;
+ r10 = 8;
+ do {
+ /* can use r14 for lo32 scratch register in all MULSHIFT32 */
+ r2 = r1[0];
+ r3 = r1[1];
+ r4 = r0[16];
+ r5 = r0[17];
+ r12 = r4 + r5;
+ r12 = MULSHIFT32(r3, r12);
+ r5 = MULSHIFT32(r2, r5) + r12;
+ r2 += 2*r3;
+ r4 = MULSHIFT32(r2, r4) - r12;
+
+ r2 = r1[2];
+ r3 = r1[3];
+ r6 = r0[32];
+ r7 = r0[33];
+ r12 = r6 + r7;
+ r12 = MULSHIFT32(r3, r12);
+ r7 = MULSHIFT32(r2, r7) + r12;
+ r2 += 2*r3;
+ r6 = MULSHIFT32(r2, r6) - r12;
+
+ r2 = r1[4];
+ r3 = r1[5];
+ r8 = r0[48];
+ r9 = r0[49];
+ r12 = r8 + r9;
+ r12 = MULSHIFT32(r3, r12);
+ r9 = MULSHIFT32(r2, r9) + r12;
+ r2 += 2*r3;
+ r8 = MULSHIFT32(r2, r8) - r12;
+
+ r2 = r0[0];
+ r3 = r0[1];
+
+ r12 = r6 + r8;
+ r8 = r6 - r8;
+ r14 = r9 - r7;
+ r9 = r9 + r7;
+
+ r6 = (r2 >> 2) - r4;
+ r7 = (r3 >> 2) - r5;
+ r4 += (r2 >> 2);
+ r5 += (r3 >> 2);
+
+ r2 = r4 + r12;
+ r3 = r5 + r9;
+ r0[0] = r2;
+ r0[1] = r3;
+ r2 = r6 - r14;
+ r3 = r7 - r8;
+ r0[16] = r2;
+ r0[17] = r3;
+ r2 = r4 - r12;
+ r3 = r5 - r9;
+ r0[32] = r2;
+ r0[33] = r3;
+ r2 = r6 + r14;
+ r3 = r7 + r8;
+ r0[48] = r2;
+ r0[49] = r3;
+
+ r0 += 2;
+ r1 += 6;
+ r10--;
+ } while (r10 != 0);
+}
+
+/***********************************************************************************************************************
+ * Function: FFT32C
+ *
+ * Description: Ken's very fast in-place radix-4 decimation-in-time FFT
+ *
+ * Inputs: buffer of 32 complex samples (before bit-reversal)
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: assumes 3 guard bits in, gains 3 integer bits
+ * guard bits out = guard bits in - 2
+ * (guard bit analysis includes assumptions about steps immediately
+ * before and after, i.e. PreMul and PostMul for DCT)
+ **********************************************************************************************************************/
+void FFT32C(int32_t *x)
+{
+ /* decimation in time */
+ BitReverse32(x);
+
+ /* 32-point complex FFT */
+ R8FirstPass32(x); /* gain 1 int32_t bit, lose 2 GB (making assumptions about input) */
+ R4Core32(x); /* gain 2 int32_t bits, lose 0 GB (making assumptions about input) */
+}
+
+/***********************************************************************************************************************
+ * Function: CVKernel1
+ *
+ * Description: kernel of covariance matrix calculation for p01, p11, p12, p22
+ *
+ * Inputs: buffer of low-freq samples, starting at time index = 0,
+ * freq index = patch subband
+ *
+ * Outputs: 64-bit accumulators for p01re, p01im, p12re, p12im, p11re, p22re
+ * stored in accBuf
+ *
+ * Return: none
+ *
+ * Notes: this is carefully written to be efficient on ARM
+ * use the assembly code version in sbrcov.s when building for ARM!
+ **********************************************************************************************************************/
+void CVKernel1(int32_t *XBuf, int32_t *accBuf)
+{
+ U64 p01re, p01im, p12re, p12im, p11re, p22re;
+ int32_t n, x0re, x0im, x1re, x1im;
+
+ x0re = XBuf[0];
+ x0im = XBuf[1];
+ XBuf += (2*64);
+ x1re = XBuf[0];
+ x1im = XBuf[1];
+ XBuf += (2*64);
+
+ p01re.w64 = p01im.w64 = 0;
+ p12re.w64 = p12im.w64 = 0;
+ p11re.w64 = 0;
+ p22re.w64 = 0;
+
+ p12re.w64 = MADD64(p12re.w64, x1re, x0re);
+ p12re.w64 = MADD64(p12re.w64, x1im, x0im);
+ p12im.w64 = MADD64(p12im.w64, x0re, x1im);
+ p12im.w64 = MADD64(p12im.w64, -x0im, x1re);
+ p22re.w64 = MADD64(p22re.w64, x0re, x0re);
+ p22re.w64 = MADD64(p22re.w64, x0im, x0im);
+ for (n = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6); n != 0; n--) {
+ /* 4 input, 3*2 acc, 1 ptr, 1 loop counter = 12 registers (use same for x0im, -x0im) */
+ x0re = x1re;
+ x0im = x1im;
+ x1re = XBuf[0];
+ x1im = XBuf[1];
+
+ p01re.w64 = MADD64(p01re.w64, x1re, x0re);
+ p01re.w64 = MADD64(p01re.w64, x1im, x0im);
+ p01im.w64 = MADD64(p01im.w64, x0re, x1im);
+ p01im.w64 = MADD64(p01im.w64, -x0im, x1re);
+ p11re.w64 = MADD64(p11re.w64, x0re, x0re);
+ p11re.w64 = MADD64(p11re.w64, x0im, x0im);
+
+ XBuf += (2*64);
+ }
+ /* these can be derived by slight changes to account for boundary conditions */
+ p12re.w64 += p01re.w64;
+ p12re.w64 = MADD64(p12re.w64, x1re, -x0re);
+ p12re.w64 = MADD64(p12re.w64, x1im, -x0im);
+ p12im.w64 += p01im.w64;
+ p12im.w64 = MADD64(p12im.w64, x0re, -x1im);
+ p12im.w64 = MADD64(p12im.w64, x0im, x1re);
+ p22re.w64 += p11re.w64;
+ p22re.w64 = MADD64(p22re.w64, x0re, -x0re);
+ p22re.w64 = MADD64(p22re.w64, x0im, -x0im);
+
+ accBuf[0] = p01re.r.lo32; accBuf[1] = p01re.r.hi32;
+ accBuf[2] = p01im.r.lo32; accBuf[3] = p01im.r.hi32;
+ accBuf[4] = p11re.r.lo32; accBuf[5] = p11re.r.hi32;
+ accBuf[6] = p12re.r.lo32; accBuf[7] = p12re.r.hi32;
+ accBuf[8] = p12im.r.lo32; accBuf[9] = p12im.r.hi32;
+ accBuf[10] = p22re.r.lo32; accBuf[11] = p22re.r.hi32;
+}
+
+/***********************************************************************************************************************
+ * Function: CVKernel2
+ *
+ * Description: kernel of covariance matrix calculation for p02
+ *
+ * Inputs: buffer of low-freq samples, starting at time index = 0,
+ * freq index = patch subband
+ *
+ * Outputs: 64-bit accumulators for p02re, p02im stored in accBuf
+ *
+ * Return: none
+ *
+ * Notes: this is carefully written to be efficient on ARM
+ * use the assembly code version in sbrcov.s when building for ARM!
+ **********************************************************************************************************************/
+void CVKernel2(int32_t *XBuf, int32_t *accBuf)
+{
+ U64 p02re, p02im;
+ int32_t n, x0re, x0im, x1re, x1im, x2re, x2im;
+
+ p02re.w64 = p02im.w64 = 0;
+
+ x0re = XBuf[0];
+ x0im = XBuf[1];
+ XBuf += (2*64);
+ x1re = XBuf[0];
+ x1im = XBuf[1];
+ XBuf += (2*64);
+
+ for (n = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6); n != 0; n--) {
+ /* 6 input, 2*2 acc, 1 ptr, 1 loop counter = 12 registers (use same for x0im, -x0im) */
+ x2re = XBuf[0];
+ x2im = XBuf[1];
+
+ p02re.w64 = MADD64(p02re.w64, x2re, x0re);
+ p02re.w64 = MADD64(p02re.w64, x2im, x0im);
+ p02im.w64 = MADD64(p02im.w64, x0re, x2im);
+ p02im.w64 = MADD64(p02im.w64, -x0im, x2re);
+
+ x0re = x1re;
+ x0im = x1im;
+ x1re = x2re;
+ x1im = x2im;
+ XBuf += (2*64);
+ }
+
+ accBuf[0] = p02re.r.lo32;
+ accBuf[1] = p02re.r.hi32;
+ accBuf[2] = p02im.r.lo32;
+ accBuf[3] = p02im.r.hi32;
+}
+
+/***********************************************************************************************************************
+ * Function: SetBitstreamPointer
+ *
+ * Description: initialize bitstream reader
+ *
+ * Inputs: number of bytes in bitstream
+ * pointer to byte-aligned buffer of data to read from
+ *
+ * Outputs: initialized bitstream info struct
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void SetBitstreamPointer(int32_t nBytes, uint8_t *buf)
+{
+ /* init bitstream */
+ m_aac_BitStreamInfo.bytePtr = buf;
+ m_aac_BitStreamInfo.iCache = 0; /* 4-byte uint32_t */
+ m_aac_BitStreamInfo.cachedBits = 0; /* i.e. zero bits in cache */
+ m_aac_BitStreamInfo.nBytes = nBytes;
+}
+
+/***********************************************************************************************************************
+ * Function: RefillBitstreamCache
+ *
+ * Description: read new data from bitstream buffer into 32-bit cache
+ *
+ * Inputs: none
+ *
+ * Outputs: updated bitstream info struct
+ *
+ * Return: none
+ *
+ * Notes: only call when iCache is completely drained (resets bitOffset to 0)
+ * always loads 4 new bytes except when bsi->nBytes < 4 (end of buffer)
+ * stores data as big-endian in cache, regardless of machine endian-ness
+ **********************************************************************************************************************/
+//Optimized for REV16, REV32 (FB)
+inline void RefillBitstreamCache()
+{
+ int32_t nBytes = m_aac_BitStreamInfo.nBytes;
+ if (nBytes >= 4) {
+ /* optimize for common case, independent of machine endian-ness */
+ m_aac_BitStreamInfo.iCache = (*m_aac_BitStreamInfo.bytePtr++) << 24;
+ m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++) << 16;
+ m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++) << 8;
+ m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++);
+
+ m_aac_BitStreamInfo.cachedBits = 32;
+ m_aac_BitStreamInfo.nBytes -= 4;
+ } else {
+ m_aac_BitStreamInfo.iCache = 0;
+ while (nBytes--) {
+ m_aac_BitStreamInfo.iCache |= (*m_aac_BitStreamInfo.bytePtr++);
+ m_aac_BitStreamInfo.iCache <<= 8;
+ }
+ m_aac_BitStreamInfo.iCache <<= ((3 - m_aac_BitStreamInfo.nBytes)*8);
+ m_aac_BitStreamInfo.cachedBits = 8*m_aac_BitStreamInfo.nBytes;
+ m_aac_BitStreamInfo.nBytes = 0;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: GetBits
+ *
+ * Description: get bits from bitstream, advance bitstream pointer
+ *
+ * Inputs: pointer to initialized aac_BitStreamInfo_t struct
+ * number of bits to get from bitstream
+ *
+ * Outputs: updated bitstream info struct
+ *
+ * Return: the next nBits bits of data from bitstream buffer
+ *
+ * Notes: nBits must be in range [0, 31], nBits outside this range masked by 0x1f
+ * for speed, does not indicate error if you overrun bit buffer
+ * if nBits == 0, returns 0
+ **********************************************************************************************************************/
+uint32_t GetBits(int32_t nBits)
+{
+ uint32_t data, lowBits;
+
+ nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */
+ data = m_aac_BitStreamInfo.iCache >> (31 - nBits); /* unsigned >> so zero-extend */
+ data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */
+ m_aac_BitStreamInfo.iCache <<= nBits; /* left-justify cache */
+ m_aac_BitStreamInfo.cachedBits -= nBits; /* how many bits have we drawn from the cache so far */
+
+ /* if we cross an int32_t boundary, refill the cache */
+ if (m_aac_BitStreamInfo.cachedBits < 0) {
+ lowBits = -m_aac_BitStreamInfo.cachedBits;
+ RefillBitstreamCache();
+ data |= m_aac_BitStreamInfo.iCache >> (32 - lowBits); /* get the low-order bits */
+
+ m_aac_BitStreamInfo.cachedBits -= lowBits; /* how many bits have we drawn from the cache so far */
+ m_aac_BitStreamInfo.iCache <<= lowBits; /* left-justify cache */
+ }
+
+ return data;
+}
+
+/***********************************************************************************************************************
+ * Function: GetBitsNoAdvance
+ *
+ * Description: get bits from bitstream, do not advance bitstream pointer
+ *
+ * Inputs: pointer to initialized aac_BitStreamInfo_t struct
+ * number of bits to get from bitstream
+ *
+ * Outputs: none (state of aac_BitStreamInfo_t struct left unchanged)
+ *
+ * Return: the next nBits bits of data from bitstream buffer
+ *
+ * Notes: nBits must be in range [0, 31], nBits outside this range masked by 0x1f
+ * for speed, does not indicate error if you overrun bit buffer
+ * if nBits == 0, returns 0
+ **********************************************************************************************************************/
+uint32_t GetBitsNoAdvance(int32_t nBits)
+{
+ uint8_t *buf;
+ uint32_t data, iCache;
+ int32_t lowBits;
+
+ nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */
+ data = m_aac_BitStreamInfo.iCache >> (31 - nBits); /* unsigned >> so zero-extend */
+ data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */
+ lowBits = nBits - m_aac_BitStreamInfo.cachedBits; /* how many bits do we have left to read */
+
+ /* if we cross an int32_t boundary, read next bytes in buffer */
+ if (lowBits > 0) {
+ iCache = 0;
+ buf = m_aac_BitStreamInfo.bytePtr;
+ while (lowBits > 0) {
+ iCache <<= 8;
+ if (buf < m_aac_BitStreamInfo.bytePtr + m_aac_BitStreamInfo.nBytes)
+ iCache |= (uint32_t)*buf++;
+ lowBits -= 8;
+ }
+ lowBits = -lowBits;
+ data |= iCache >> lowBits;
+ }
+
+ return data;
+}
+
+/***********************************************************************************************************************
+ * Function: AdvanceBitstream
+ *
+ * Description: move bitstream pointer ahead
+ *
+ * Inputs: number of bits to advance bitstream
+ *
+ * Outputs: updated bitstream info struct
+ *
+ * Return: none
+ *
+ * Notes: generally used following GetBitsNoAdvance(bsi, maxBits)
+ **********************************************************************************************************************/
+void AdvanceBitstream(int32_t nBits)
+{
+ nBits &= 0x1f;
+ if (nBits > m_aac_BitStreamInfo.cachedBits) {
+ nBits -= m_aac_BitStreamInfo.cachedBits;
+ RefillBitstreamCache();
+ }
+ m_aac_BitStreamInfo.iCache <<= nBits;
+ m_aac_BitStreamInfo.cachedBits -= nBits;
+}
+
+/***********************************************************************************************************************
+ * Function: CalcBitsUsed
+ *
+ * Description: calculate how many bits have been read from bitstream
+ *
+ * Inputs: pointer to start of bitstream buffer
+ * bit offset into first byte of startBuf (0-7)
+ *
+ * Outputs: none
+ *
+ * Return: number of bits read from bitstream, as offset from startBuf:startOffset
+ **********************************************************************************************************************/
+int32_t CalcBitsUsed(uint8_t *startBuf, int32_t startOffset) {
+
+ int32_t bitsUsed;
+
+ bitsUsed = (m_aac_BitStreamInfo.bytePtr - startBuf) * 8;
+ bitsUsed -= m_aac_BitStreamInfo.cachedBits;
+ bitsUsed -= startOffset;
+
+ return bitsUsed;
+}
+/***********************************************************************************************************************
+ * Function: ByteAlignBitstream
+ *
+ * Description: bump bitstream pointer to start of next byte
+ *
+ * Inputs: none
+ *
+ * Outputs: byte-aligned bitstream aac_BitStreamInfo_t struct
+ *
+ * Return: none
+ *
+ * Notes: if bitstream is already byte-aligned, do nothing
+ **********************************************************************************************************************/
+void ByteAlignBitstream(){
+
+ int32_t offset;
+
+ offset = m_aac_BitStreamInfo.cachedBits & 0x07;
+ AdvanceBitstream(offset);
+}
+
+#ifdef AAC_ENABLE_SBR
+
+/**************************************************************************************
+ * Function: InitSBRState
+ *
+ * Description: initialize PSInfoSBR struct at start of stream or after flush
+ *
+ * Inputs: valid AACDecInfo struct
+ *
+ * Outputs: PSInfoSBR struct with proper initial state
+ *
+ * Return: none
+ **************************************************************************************/
+void InitSBRState() {
+
+ int32_t i, ch;
+ uint8_t *c;
+
+ if (!m_PSInfoSBR)
+ return;
+
+ /* clear SBR state structure */
+ c = (uint8_t *)m_PSInfoSBR;
+ for (i = 0; i < (int32_t)sizeof(m_PSInfoSBR); i++)
+ *c++ = 0;
+
+ /* initialize non-zero state variables */
+ for (ch = 0; ch < AAC_MAX_NCHANS; ch++) {
+ m_PSInfoSBR->sbrChan[ch].reset = 1;
+ m_PSInfoSBR->sbrChan[ch].laPrev = -1;
+ }
+}
+#endif
+
+/***********************************************************************************************************************
+ * Function: DecodeSBRBitstream
+ *
+ * Description: decode sideband information for SBR
+ *
+ * Inputs: base output channel (range = [0, nChans-1])
+ *
+ * Outputs: initialized state structs (SBRHdr, SBRGrid, SBRFreq, SBRChan)
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ *
+ * Notes: SBR payload should be in aacDecInfo->fillBuf
+ * returns with no error if fill buffer is not an SBR extension block,
+ * or if current block is not a fill block (e.g. for LFE upsampling)
+ **********************************************************************************************************************/
+int32_t DecodeSBRBitstream(int32_t chBase) {
+
+ int32_t headerFlag;
+
+ if(m_AACDecInfo->currBlockID != AAC_ID_FIL
+ || (m_AACDecInfo->fillExtType != EXT_SBR_DATA && m_AACDecInfo->fillExtType != EXT_SBR_DATA_CRC))
+ return ERR_AAC_NONE;
+
+ SetBitstreamPointer(m_AACDecInfo->fillCount, m_AACDecInfo->fillBuf);
+ if(GetBits(4) != (uint32_t) m_AACDecInfo->fillExtType) return ERR_AAC_SBR_BITSTREAM;
+
+ if(m_AACDecInfo->fillExtType == EXT_SBR_DATA_CRC) m_PSInfoSBR->crcCheckWord = GetBits(10);
+
+ headerFlag = GetBits(1);
+ if(headerFlag) {
+ /* get sample rate index for output sample rate (2x base rate) */
+ m_PSInfoSBR->sampRateIdx = GetSampRateIdx(2 * m_AACDecInfo->sampRate);
+ if(m_PSInfoSBR->sampRateIdx < 0 || m_PSInfoSBR->sampRateIdx >= NUM_SAMPLE_RATES)
+ return ERR_AAC_SBR_BITSTREAM;
+ else if(m_PSInfoSBR->sampRateIdx >= NUM_SAMPLE_RATES_SBR) return ERR_AAC_SBR_SINGLERATE_UNSUPPORTED;
+
+ /* reset flag = 1 if header values changed */
+ if(UnpackSBRHeader(&(m_PSInfoSBR->sbrHdr[chBase]))) m_PSInfoSBR->sbrChan[chBase].reset = 1;
+
+ /* first valid SBR header should always trigger CalcFreqTables(), since psi->reset was set in InitSBR() */
+ if(m_PSInfoSBR->sbrChan[chBase].reset)
+ CalcFreqTables(&(m_PSInfoSBR->sbrHdr[chBase + 0]), &(m_PSInfoSBR->sbrFreq[chBase]),
+ m_PSInfoSBR->sampRateIdx);
+
+ /* copy and reset state to right channel for CPE */
+ if(m_AACDecInfo->prevBlockID == AAC_ID_CPE)
+ m_PSInfoSBR->sbrChan[chBase + 1].reset = m_PSInfoSBR->sbrChan[chBase + 0].reset;
+ }
+
+ /* if no header has been received, upsample only */
+ if(m_PSInfoSBR->sbrHdr[chBase].count == 0) return ERR_AAC_NONE;
+
+ if(m_AACDecInfo->prevBlockID == AAC_ID_SCE) {
+ UnpackSBRSingleChannel(chBase);
+ }
+ else if(m_AACDecInfo->prevBlockID == AAC_ID_CPE) {
+ UnpackSBRChannelPair(chBase);
+ }
+ else {
+ return ERR_AAC_SBR_BITSTREAM;
+ }
+
+ ByteAlignBitstream();
+
+ return ERR_AAC_NONE;
+}
+
+#ifdef AAC_ENABLE_SBR
+
+/***********************************************************************************************************************
+ * Function: DecodeSBRData
+ *
+ * Description: apply SBR to one frame of PCM data
+ *
+ * Inputs: 1024 samples of decoded 32-bit PCM, before SBR
+ * size of input PCM samples (must be 4 bytes)
+ * number of fraction bits in input PCM samples
+ * base output channel (range = [0, nChans-1])
+ * initialized state structs (SBRHdr, SBRGrid, SBRFreq, SBRChan)
+ *
+ * Outputs: 2048 samples of decoded 16-bit PCM, after SBR
+ *
+ * Return: 0 if successful, error code (< 0) if error
+ **********************************************************************************************************************/
+int32_t DecodeSBRData(int32_t chBase, int16_t *outbuf) {
+
+ int32_t k, l, ch, chBlock, qmfaBands, qmfsBands;
+ int32_t upsampleOnly, gbIdx, gbMask;
+ int32_t *inbuf;
+ int16_t *outptr;
+
+ SBRHeader *sbrHdr;
+ SBRGrid *sbrGrid;
+ SBRFreq *sbrFreq;
+ SBRChan *sbrChan;
+
+ /* same header and freq tables for both channels in CPE */
+ sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]);
+ sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]);
+
+ /* upsample only if we haven't received an SBR header yet or if we have an LFE block */
+ if(m_AACDecInfo->currBlockID == AAC_ID_LFE) {
+ chBlock = 1;
+ upsampleOnly = 1;
+ }
+ else if(m_AACDecInfo->currBlockID == AAC_ID_FIL) {
+ if(m_AACDecInfo->prevBlockID == AAC_ID_SCE)
+ chBlock = 1;
+ else if(m_AACDecInfo->prevBlockID == AAC_ID_CPE)
+ chBlock = 2;
+ else
+ return ERR_AAC_NONE;
+
+ upsampleOnly = (sbrHdr->count == 0 ? 1 : 0);
+ if(m_AACDecInfo->fillExtType != EXT_SBR_DATA && m_AACDecInfo->fillExtType != EXT_SBR_DATA_CRC)
+ return ERR_AAC_NONE;
+ }
+ else {
+ /* ignore non-SBR blocks */
+ return ERR_AAC_NONE;
+ }
+
+ if(upsampleOnly) {
+ sbrFreq->kStart = 32;
+ sbrFreq->numQMFBands = 0;
+ }
+
+ for(ch = 0; ch < chBlock; ch++) {
+ sbrGrid = &(m_PSInfoSBR->sbrGrid[chBase + ch]);
+ sbrChan = &(m_PSInfoSBR->sbrChan[chBase + ch]);
+
+ if(m_AACDecInfo->rawSampleBuf[ch] == 0 || m_AACDecInfo->rawSampleBytes != 4) return ERR_AAC_SBR_PCM_FORMAT;
+ inbuf = (int32_t*) m_AACDecInfo->rawSampleBuf[ch];
+ outptr = outbuf + chBase + ch;
+
+ /* restore delay buffers (could use ring buffer or keep in temp buffer for nChans == 1) */
+ for(l = 0; l < HF_GEN; l++) {
+ for(k = 0; k < 64; k++) {
+ m_PSInfoSBR->XBuf[l][k][0] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0];
+ m_PSInfoSBR->XBuf[l][k][1] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1];
+ }
+ }
+
+ /* step 1 - analysis QMF */
+ qmfaBands = sbrFreq->kStart;
+ for(l = 0; l < 32; l++) {
+ gbMask = QMFAnalysis(inbuf + l * 32, m_PSInfoSBR->delayQMFA[chBase + ch], m_PSInfoSBR->XBuf[l + HF_GEN][0],
+ m_AACDecInfo->rawSampleFBits, &(m_PSInfoSBR->delayIdxQMFA[chBase + ch]), qmfaBands);
+
+ gbIdx = ((l + HF_GEN) >> 5) & 0x01;
+ sbrChan->gbMask[gbIdx] |= gbMask; /* gbIdx = (0 if i < 32), (1 if i >= 32) */
+ }
+
+ if(upsampleOnly) {
+ /* no SBR - just run synthesis QMF to upsample by 2x */
+ qmfsBands = 32;
+ for(l = 0; l < 32; l++) {
+ /* step 4 - synthesis QMF */
+ QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch],
+ &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans);
+ outptr += 64 * m_AACDecInfo->nChans;
+ }
+ }
+ else {
+ /* if previous frame had lower SBR starting freq than current, zero out the synthesized QMF
+ * bands so they aren't used as sources for patching
+ * after patch generation, restore from delay buffer
+ * can only happen after header reset
+ */
+ for(k = sbrFreq->kStartPrev; k < sbrFreq->kStart; k++) {
+ for(l = 0; l < sbrGrid->envTimeBorder[0] + HF_ADJ; l++) {
+ m_PSInfoSBR->XBuf[l][k][0] = 0;
+ m_PSInfoSBR->XBuf[l][k][1] = 0;
+ }
+ }
+
+ /* step 2 - HF generation */
+ GenerateHighFreq(sbrGrid, sbrFreq, sbrChan, ch);
+
+ /* restore SBR bands that were cleared before patch generation (time slots 0, 1 no longer needed) */
+ for(k = sbrFreq->kStartPrev; k < sbrFreq->kStart; k++) {
+ for(l = HF_ADJ; l < sbrGrid->envTimeBorder[0] + HF_ADJ; l++) {
+ m_PSInfoSBR->XBuf[l][k][0] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0];
+ m_PSInfoSBR->XBuf[l][k][1] = m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1];
+ }
+ }
+
+ /* step 3 - HF adjustment */
+ AdjustHighFreq(sbrHdr, sbrGrid, sbrFreq, sbrChan, ch);
+
+ /* step 4 - synthesis QMF */
+ qmfsBands = sbrFreq->kStartPrev + sbrFreq->numQMFBandsPrev;
+ for(l = 0; l < sbrGrid->envTimeBorder[0]; l++) {
+ /* if new envelope starts mid-frame, use old settings until start of first envelope in this frame */
+ QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch],
+ &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans);
+ outptr += 64 * m_AACDecInfo->nChans;
+ }
+
+ qmfsBands = sbrFreq->kStart + sbrFreq->numQMFBands;
+ for(; l < 32; l++) {
+ /* use new settings for rest of frame (usually the entire frame, unless the first envelope starts mid-frame) */
+ QMFSynthesis(m_PSInfoSBR->XBuf[l + HF_ADJ][0], m_PSInfoSBR->delayQMFS[chBase + ch],
+ &(m_PSInfoSBR->delayIdxQMFS[chBase + ch]), qmfsBands, outptr, m_AACDecInfo->nChans);
+ outptr += 64 * m_AACDecInfo->nChans;
+ }
+ }
+
+ /* save delay */
+ for(l = 0; l < HF_GEN; l++) {
+ for(k = 0; k < 64; k++) {
+ m_PSInfoSBR->XBufDelay[chBase + ch][l][k][0] = m_PSInfoSBR->XBuf[l + 32][k][0];
+ m_PSInfoSBR->XBufDelay[chBase + ch][l][k][1] = m_PSInfoSBR->XBuf[l + 32][k][1];
+ }
+ }
+ sbrChan->gbMask[0] = sbrChan->gbMask[1];
+ sbrChan->gbMask[1] = 0;
+
+ if(sbrHdr->count > 0) sbrChan->reset = 0;
+ }
+ sbrFreq->kStartPrev = sbrFreq->kStart;
+ sbrFreq->numQMFBandsPrev = sbrFreq->numQMFBands;
+
+ if(m_AACDecInfo->nChans > 0 && (chBase + ch) == m_AACDecInfo->nChans) m_PSInfoSBR->frameCount++;
+
+ return ERR_AAC_NONE;
+}
+
+#endif
+
+/***********************************************************************************************************************
+ * Function: BubbleSort
+ *
+ * Description: in-place sort of uint8_ts
+ *
+ * Inputs: buffer of elements to sort
+ * number of elements to sort
+ *
+ * Outputs: sorted buffer
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void BubbleSort(uint8_t *v, int32_t nItems) {
+
+ int32_t i;
+ uint8_t t;
+
+ while(nItems >= 2) {
+ for(i = 0; i < nItems - 1; i++) {
+ if(v[i + 1] < v[i]) {
+ t = v[i + 1];
+ v[i + 1] = v[i];
+ v[i] = t;
+ }
+ }
+ nItems--;
+ }
+}
+/***********************************************************************************************************************
+ * Function: VMin
+ *
+ * Description: find smallest element in a buffer of uint8_ts
+ *
+ * Inputs: buffer of elements to search
+ * number of elements to search
+ *
+ * Outputs: none
+ *
+ * Return: smallest element in buffer
+ **********************************************************************************************************************/
+uint8_t VMin(uint8_t *v, int32_t nItems) {
+
+ int32_t i;
+ uint8_t vMin;
+
+ vMin = v[0];
+ for(i = 1; i < nItems; i++) {
+ if(v[i] < vMin) vMin = v[i];
+ }
+ return vMin;
+}
+/***********************************************************************************************************************
+ * Function: VMax
+ *
+ * Description: find largest element in a buffer of uint8_ts
+ *
+ * Inputs: buffer of elements to search
+ * number of elements to search
+ *
+ * Outputs: none
+ *
+ * Return: largest element in buffer
+ **********************************************************************************************************************/
+uint8_t VMax(uint8_t *v, int32_t nItems) {
+
+ int32_t i;
+ uint8_t vMax;
+
+ vMax = v[0];
+ for(i = 1; i < nItems; i++) {
+ if(v[i] > vMax) vMax = v[i];
+ }
+ return vMax;
+}
+/***********************************************************************************************************************
+ * Function: CalcFreqMasterScaleZero
+ *
+ * Description: calculate master frequency table when freqScale == 0
+ * (4.6.18.3.2.1, figure 4.39)
+ *
+ * Inputs: alterScale flag
+ * index of first QMF subband in master freq table (k0)
+ * index of last QMF subband (k2)
+ *
+ * Outputs: master frequency table
+ *
+ * Return: number of bands in master frequency table
+ *
+ * Notes: assumes k2 - k0 <= 48 and k2 >= k0 (4.6.18.3.6)
+ **********************************************************************************************************************/
+int32_t CalcFreqMasterScaleZero(uint8_t *freqMaster, int32_t alterScale, int32_t k0, int32_t k2) {
+
+ int32_t nMaster, k, nBands, k2Achieved, dk, vDk[64], k2Diff;
+
+ if(alterScale) {
+ dk = 2;
+ nBands = 2 * ((k2 - k0 + 2) >> 2);
+ }
+ else {
+ dk = 1;
+ nBands = 2 * ((k2 - k0) >> 1);
+ }
+
+ if(nBands <= 0) return 0;
+
+ k2Achieved = k0 + nBands * dk;
+ k2Diff = k2 - k2Achieved;
+ for(k = 0; k < nBands; k++)
+ vDk[k] = dk;
+
+ if(k2Diff > 0) {
+ k = nBands - 1;
+ while(k2Diff) {
+ vDk[k]++;
+ k--;
+ k2Diff--;
+ }
+ }
+ else if(k2Diff < 0) {
+ k = 0;
+ while(k2Diff) {
+ vDk[k]--;
+ k++;
+ k2Diff++;
+ }
+ }
+
+ nMaster = nBands;
+ freqMaster[0] = k0;
+ for(k = 1; k <= nBands; k++)
+ freqMaster[k] = freqMaster[k - 1] + vDk[k - 1];
+
+ return nMaster;
+}
+
+/* mBandTab[i] = temp1[i] / 2 */
+static const int32_t mBandTab[3] PROGMEM = {6, 5, 4};
+
+/* invWarpTab[i] = 1.0 / temp2[i], Q30 (see 4.6.18.3.2.1) */
+static const int32_t invWarpTab[2] PROGMEM = {0x40000000, 0x313b13b1};
+
+/***********************************************************************************************************************
+ * Function: CalcFreqMasterScale
+ *
+ * Description: calculate master frequency table when freqScale > 0
+ * (4.6.18.3.2.1, figure 4.39)
+ *
+ * Inputs: alterScale flag
+ * freqScale flag
+ * index of first QMF subband in master freq table (k0)
+ * index of last QMF subband (k2)
+ *
+ * Outputs: master frequency table
+ *
+ * Return: number of bands in master frequency table
+ *
+ * Notes: assumes k2 - k0 <= 48 and k2 >= k0 (4.6.18.3.6)
+ **********************************************************************************************************************/
+int32_t CalcFreqMaster(uint8_t *freqMaster, int32_t freqScale, int32_t alterScale, int32_t k0, int32_t k2) {
+
+ int32_t bands, twoRegions, k, k1, t, vLast, vCurr, pCurr;
+ int32_t invWarp, nBands0, nBands1, change;
+ uint8_t vDk1Min, vDk0Max;
+ uint8_t *vDelta;
+
+ if(freqScale < 1 || freqScale > 3) return -1;
+
+ bands = mBandTab[freqScale - 1];
+ invWarp = invWarpTab[alterScale];
+
+ /* tested for all k0 = [5, 64], k2 = [k0, 64] */
+ if(k2 * 10000 > 22449 * k0) {
+ twoRegions = 1;
+ k1 = 2 * k0;
+ }
+ else {
+ twoRegions = 0;
+ k1 = k2;
+ }
+
+ /* tested for all k0 = [5, 64], k1 = [k0, 64], freqScale = [1,3] */
+ t = (log2Tab[k1] - log2Tab[k0]) >> 3; /* log2(k1/k0), Q28 to Q25 */
+ nBands0 = 2 * (((bands * t) + (1 << 24)) >> 25); /* multiply by bands/2, round to nearest int32_t (mBandTab has factor of 1/2 rolled in) */
+
+ /* tested for all valid combinations of k0, k1, nBands (from sampRate, freqScale, alterScale)
+ * roundoff error can be a problem with fixpt (e.g. pCurr = 12.499999 instead of 12.50003)
+ * because successive multiplication always undershoots a little bit, but this
+ * doesn't occur in any of the ratios we encounter from the valid k0/k1 bands in the spec
+ */
+ t = RatioPowInv(k1, k0, nBands0);
+ pCurr = k0 << 24;
+ vLast = k0;
+ vDelta = freqMaster + 1; /* operate in-place */
+ for(k = 0; k < nBands0; k++) {
+ pCurr = MULSHIFT32(pCurr, t) << 8; /* keep in Q24 */
+ vCurr = (pCurr + (1 << 23)) >> 24;
+ vDelta[k] = (vCurr - vLast);
+ vLast = vCurr;
+ }
+
+ /* sort the deltas and find max delta for first region */
+ BubbleSort(vDelta, nBands0);
+ vDk0Max = VMax(vDelta, nBands0);
+
+ /* fill master frequency table with bands from first region */
+ freqMaster[0] = k0;
+ for(k = 1; k <= nBands0; k++)
+ freqMaster[k] += freqMaster[k - 1];
+
+ /* if only one region, then the table is complete */
+ if(!twoRegions) return nBands0;
+
+ /* tested for all k1 = [10, 64], k2 = [k0, 64], freqScale = [1,3] */
+ t = (log2Tab[k2] - log2Tab[k1]) >> 3; /* log2(k1/k0), Q28 to Q25 */
+ t = MULSHIFT32(bands * t, invWarp) << 2; /* multiply by bands/2, divide by warp factor, keep Q25 */
+ nBands1 = 2 * ((t + (1 << 24)) >> 25); /* round to nearest int32_t */
+
+ /* see comments above for calculations in first region */
+ t = RatioPowInv(k2, k1, nBands1);
+ pCurr = k1 << 24;
+ vLast = k1;
+ vDelta = freqMaster + nBands0 + 1; /* operate in-place */
+ for(k = 0; k < nBands1; k++) {
+ pCurr = MULSHIFT32(pCurr, t) << 8; /* keep in Q24 */
+ vCurr = (pCurr + (1 << 23)) >> 24;
+ vDelta[k] = (vCurr - vLast);
+ vLast = vCurr;
+ }
+
+ /* sort the deltas, adjusting first and last if the second region has smaller deltas than the first */
+ vDk1Min = VMin(vDelta, nBands1);
+ if(vDk1Min < vDk0Max) {
+ BubbleSort(vDelta, nBands1);
+ change = vDk0Max - vDelta[0];
+ if(change > ((vDelta[nBands1 - 1] - vDelta[0]) >> 1)) change = ((vDelta[nBands1 - 1] - vDelta[0]) >> 1);
+ vDelta[0] += change;
+ vDelta[nBands1 - 1] -= change;
+ }
+ BubbleSort(vDelta, nBands1);
+
+ /* fill master frequency table with bands from second region
+ * Note: freqMaster[nBands0] = k1
+ */
+ for(k = 1; k <= nBands1; k++)
+ freqMaster[k + nBands0] += freqMaster[k + nBands0 - 1];
+
+ return (nBands0 + nBands1);
+}
+/***********************************************************************************************************************
+ * Function: CalcFreqHigh
+ *
+ * Description: calculate high resolution frequency table (4.6.18.3.2.2)
+ *
+ * Inputs: master frequency table
+ * number of bands in master frequency table
+ * crossover band from header
+ *
+ * Outputs: high resolution frequency table
+ *
+ * Return: number of bands in high resolution frequency table
+ **********************************************************************************************************************/
+int32_t CalcFreqHigh(uint8_t *freqHigh, uint8_t *freqMaster, int32_t nMaster, int32_t crossOverBand) {
+
+ int32_t k, nHigh;
+
+ nHigh = nMaster - crossOverBand;
+
+ for(k = 0; k <= nHigh; k++)
+ freqHigh[k] = freqMaster[k + crossOverBand];
+
+ return nHigh;
+}
+/***********************************************************************************************************************
+ * Function: CalcFreqLow
+ *
+ * Description: calculate low resolution frequency table (4.6.18.3.2.2)
+ *
+ * Inputs: high resolution frequency table
+ * number of bands in high resolution frequency table
+ *
+ * Outputs: low resolution frequency table
+ *
+ * Return: number of bands in low resolution frequency table
+ **********************************************************************************************************************/
+int32_t CalcFreqLow(uint8_t *freqLow, uint8_t *freqHigh, int32_t nHigh) {
+
+ int32_t k, nLow, oddFlag;
+
+ nLow = nHigh - (nHigh >> 1);
+ freqLow[0] = freqHigh[0];
+ oddFlag = nHigh & 0x01;
+
+ for(k = 1; k <= nLow; k++)
+ freqLow[k] = freqHigh[2 * k - oddFlag];
+
+ return nLow;
+}
+/***********************************************************************************************************************
+ * Function: CalcFreqNoise
+ *
+ * Description: calculate noise floor frequency table (4.6.18.3.2.2)
+ *
+ * Inputs: low resolution frequency table
+ * number of bands in low resolution frequency table
+ * index of starting QMF subband for SBR (kStart)
+ * index of last QMF subband (k2)
+ * number of noise bands
+ *
+ * Outputs: noise floor frequency table
+ *
+ * Return: number of bands in noise floor frequency table
+ **********************************************************************************************************************/
+int32_t CalcFreqNoise(uint8_t *freqNoise, uint8_t *freqLow, int32_t nLow, int32_t kStart, int32_t k2, int32_t noiseBands) {
+
+ int32_t i, iLast, k, nQ, lTop, lBottom;
+
+ lTop = log2Tab[k2];
+ lBottom = log2Tab[kStart];
+ nQ = noiseBands * ((lTop - lBottom) >> 2); /* Q28 to Q26, noiseBands = [0,3] */
+ nQ = (nQ + (1 << 25)) >> 26;
+ if(nQ < 1) nQ = 1;
+
+ ASSERT(nQ <= MAX_NUM_NOISE_FLOOR_BANDS); /* required from 4.6.18.3.6 */
+
+ iLast = 0;
+ freqNoise[0] = freqLow[0];
+ for(k = 1; k <= nQ; k++) {
+ i = iLast + (nLow - iLast) / (nQ + 1 - k); /* truncating division */
+ freqNoise[k] = freqLow[i];
+ iLast = i;
+ }
+
+ return nQ;
+}
+/***********************************************************************************************************************
+ * Function: BuildPatches
+ *
+ * Description: build high frequency patches (4.6.18.6.3)
+ *
+ * Inputs: master frequency table
+ * number of bands in low resolution frequency table
+ * index of first QMF subband in master freq table (k0)
+ * index of starting QMF subband for SBR (kStart)
+ * number of QMF bands in high resolution frequency table
+ * sample rate index
+ *
+ * Outputs: starting subband for each patch
+ * number of subbands in each patch
+ *
+ * Return: number of patches
+ **********************************************************************************************************************/
+int32_t BuildPatches(uint8_t *patchNumSubbands, uint8_t *patchStartSubband, uint8_t *freqMaster, int32_t nMaster, int32_t k0,
+ int32_t kStart, int32_t numQMFBands, int32_t sampRateIdx) {
+
+ int32_t i, j, k;
+ int32_t msb, sb, usb, numPatches, goalSB, oddFlag;
+
+ msb = k0;
+ usb = kStart;
+ numPatches = 0;
+ goalSB = goalSBTab[sampRateIdx];
+
+ if(nMaster == 0) {
+ patchNumSubbands[0] = 0;
+ patchStartSubband[0] = 0;
+ return 0;
+ }
+
+ if(goalSB < kStart + numQMFBands) {
+ k = 0;
+ for(i = 0; freqMaster[i] < goalSB; i++)
+ k = i + 1;
+ }
+ else {
+ k = nMaster;
+ }
+
+ do {
+ j = k + 1;
+ do {
+ j--;
+ sb = freqMaster[j];
+ oddFlag = (sb - 2 + k0) & 0x01;
+ } while(sb > k0 - 1 + msb - oddFlag);
+
+ patchNumSubbands[numPatches] = MAX(sb - usb, (int32_t)0);
+ patchStartSubband[numPatches] = k0 - oddFlag - patchNumSubbands[numPatches];
+
+ /* from MPEG reference code - slightly different from spec */
+ if((patchNumSubbands[numPatches] < 3) && (numPatches > 0)) break;
+
+ if(patchNumSubbands[numPatches] > 0) {
+ usb = sb;
+ msb = sb;
+ numPatches++;
+ }
+ else {
+ msb = kStart;
+ }
+
+ if(freqMaster[k] - sb < 3) k = nMaster;
+
+ } while(sb != (kStart + numQMFBands) && numPatches <= MAX_NUM_PATCHES);
+
+ return numPatches;
+}
+/***********************************************************************************************************************
+ * Function: FindFreq
+ *
+ * Description: search buffer of uint8_ts for a specific value
+ *
+ * Inputs: buffer of elements to search
+ * number of elements to search
+ * value to search for
+ *
+ * Outputs: none
+ *
+ * Return: non-zero if the value is found anywhere in the buffer, zero otherwise
+ **********************************************************************************************************************/
+int32_t FindFreq(uint8_t *freq, int32_t nFreq, uint8_t val) {
+
+ int32_t k;
+
+ for(k = 0; k < nFreq; k++) {
+ if(freq[k] == val) return 1;
+ }
+
+ return 0;
+}
+/***********************************************************************************************************************
+ * Function: RemoveFreq
+ *
+ * Description: remove one element from a buffer of uint8_ts
+ *
+ * Inputs: buffer of elements
+ * number of elements
+ * index of element to remove
+ *
+ * Outputs: new buffer of length nFreq-1
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void RemoveFreq(uint8_t *freq, int32_t nFreq, int32_t removeIdx) {
+
+ int32_t k;
+
+ if(removeIdx >= nFreq) return;
+
+ for(k = removeIdx; k < nFreq - 1; k++)
+ freq[k] = freq[k + 1];
+}
+/***********************************************************************************************************************
+ * Function: CalcFreqLimiter
+ *
+ * Description: calculate limiter frequency table (4.6.18.3.2.3)
+ *
+ * Inputs: number of subbands in each patch
+ * low resolution frequency table
+ * number of bands in low resolution frequency table
+ * index of starting QMF subband for SBR (kStart)
+ * number of limiter bands
+ * number of patches
+ *
+ * Outputs: limiter frequency table
+ *
+ * Return: number of bands in limiter frequency table
+ **********************************************************************************************************************/
+int32_t CalcFreqLimiter(uint8_t *freqLimiter, uint8_t *patchNumSubbands, uint8_t *freqLow, int32_t nLow, int32_t kStart,
+ int32_t limiterBands, int32_t numPatches) {
+
+ int32_t k, bands, nLimiter, nOctaves;
+ int32_t limBandsPerOctave[3] = { 120, 200, 300 }; /* [1.2, 2.0, 3.0] * 100 */
+ uint8_t patchBorders[MAX_NUM_PATCHES + 1];
+
+ /* simple case */
+ if(limiterBands == 0) {
+ freqLimiter[0] = freqLow[0] - kStart;
+ freqLimiter[1] = freqLow[nLow] - kStart;
+ return 1;
+ }
+
+ bands = limBandsPerOctave[limiterBands - 1];
+ patchBorders[0] = kStart;
+
+ /* from MPEG reference code - slightly different from spec (top border) */
+ for(k = 1; k < numPatches; k++)
+ patchBorders[k] = patchBorders[k - 1] + patchNumSubbands[k - 1];
+ patchBorders[k] = freqLow[nLow];
+
+ for(k = 0; k <= nLow; k++)
+ freqLimiter[k] = freqLow[k];
+
+ for(k = 1; k < numPatches; k++)
+ freqLimiter[k + nLow] = patchBorders[k];
+
+ k = 1;
+ nLimiter = nLow + numPatches - 1;
+ BubbleSort(freqLimiter, nLimiter + 1);
+
+ while(k <= nLimiter) {
+ nOctaves = log2Tab[freqLimiter[k]] - log2Tab[freqLimiter[k - 1]]; /* Q28 */
+ nOctaves = (nOctaves >> 9) * bands; /* Q19, max bands = 300 < 2^9 */
+ if(nOctaves < (49 << 19)) { /* compare with 0.49*100, in Q19 */
+ if(freqLimiter[k] == freqLimiter[k - 1] || FindFreq(patchBorders, numPatches + 1, freqLimiter[k]) == 0) {
+ RemoveFreq(freqLimiter, nLimiter + 1, k);
+ nLimiter--;
+ }
+ else if(FindFreq(patchBorders, numPatches + 1, freqLimiter[k - 1]) == 0) {
+ RemoveFreq(freqLimiter, nLimiter + 1, k - 1);
+ nLimiter--;
+ }
+ else {
+ k++;
+ }
+ }
+ else {
+ k++;
+ }
+ }
+
+ /* store limiter boundaries as offsets from kStart */
+ for(k = 0; k <= nLimiter; k++)
+ freqLimiter[k] -= kStart;
+
+ return nLimiter;
+}
+/***********************************************************************************************************************
+ * Function: CalcFreqTables
+ *
+ * Description: calulate master and derived frequency tables, and patches
+ *
+ * Inputs: initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRFreq struct for this SCE/CPE block
+ * sample rate index of output sample rate (after SBR)
+ *
+ * Outputs: master and derived frequency tables, and patches
+ *
+ * Return: non-zero if error, zero otherwise
+ **********************************************************************************************************************/
+int32_t CalcFreqTables(SBRHeader *sbrHdr, SBRFreq *sbrFreq, int32_t sampRateIdx) {
+ int32_t k0, k2;
+
+ k0 = k0Tab[sampRateIdx][sbrHdr->startFreq];
+
+ if(sbrHdr->stopFreq == 14)
+ k2 = 2 * k0;
+ else if(sbrHdr->stopFreq == 15)
+ k2 = 3 * k0;
+ else
+ k2 = k2Tab[sampRateIdx][sbrHdr->stopFreq];
+ if(k2 > 64) k2 = 64;
+
+ /* calculate master frequency table */
+ if(sbrHdr->freqScale == 0)
+ sbrFreq->nMaster = CalcFreqMasterScaleZero(sbrFreq->freqMaster, sbrHdr->alterScale, k0, k2);
+ else
+ sbrFreq->nMaster = CalcFreqMaster(sbrFreq->freqMaster, sbrHdr->freqScale, sbrHdr->alterScale, k0, k2);
+
+ /* calculate high frequency table and related parameters */
+ sbrFreq->nHigh = CalcFreqHigh(sbrFreq->freqHigh, sbrFreq->freqMaster, sbrFreq->nMaster, sbrHdr->crossOverBand);
+ sbrFreq->numQMFBands = sbrFreq->freqHigh[sbrFreq->nHigh] - sbrFreq->freqHigh[0];
+ sbrFreq->kStart = sbrFreq->freqHigh[0];
+
+ /* calculate low frequency table */
+ sbrFreq->nLow = CalcFreqLow(sbrFreq->freqLow, sbrFreq->freqHigh, sbrFreq->nHigh);
+
+ /* calculate noise floor frequency table */
+ sbrFreq->numNoiseFloorBands = CalcFreqNoise(sbrFreq->freqNoise, sbrFreq->freqLow, sbrFreq->nLow, sbrFreq->kStart,
+ k2, sbrHdr->noiseBands);
+
+ /* calculate limiter table */
+ sbrFreq->numPatches = BuildPatches(sbrFreq->patchNumSubbands, sbrFreq->patchStartSubband, sbrFreq->freqMaster,
+ sbrFreq->nMaster, k0, sbrFreq->kStart, sbrFreq->numQMFBands, sampRateIdx);
+ sbrFreq->nLimiter = CalcFreqLimiter(sbrFreq->freqLimiter, sbrFreq->patchNumSubbands, sbrFreq->freqLow,
+ sbrFreq->nLow, sbrFreq->kStart, sbrHdr->limiterBands, sbrFreq->numPatches);
+
+ return 0;
+}
+/***********************************************************************************************************************
+ * Function: EstimateEnvelope
+ *
+ * Description: estimate power of generated HF QMF bands in one time-domain envelope
+ * (4.6.18.7.3)
+ *
+ * Inputs: initialized PSInfoSBR struct
+ * initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * index of current envelope
+ *
+ * Outputs: power of each QMF subband, stored as integer (Q0) * 2^N, N >= 0
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void EstimateEnvelope(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int32_t env) {
+
+ int32_t i, m, iStart, iEnd, xre, xim, nScale, expMax;
+ int32_t p, n, mStart, mEnd, invFact, t;
+ int32_t *XBuf;
+ U64 eCurr;
+ uint8_t *freqBandTab;
+
+ /* estimate current envelope */
+ iStart = sbrGrid->envTimeBorder[env] + HF_ADJ;
+ iEnd = sbrGrid->envTimeBorder[env + 1] + HF_ADJ;
+ if(sbrGrid->freqRes[env]) {
+ n = sbrFreq->nHigh;
+ freqBandTab = sbrFreq->freqHigh;
+ }
+ else {
+ n = sbrFreq->nLow;
+ freqBandTab = sbrFreq->freqLow;
+ }
+
+ /* ADS should inline MADD64 (smlal) properly, but check to make sure */
+ expMax = 0;
+ if(sbrHdr->interpFreq) {
+ for(m = 0; m < sbrFreq->numQMFBands; m++) {
+ eCurr.w64 = 0;
+ XBuf = m_PSInfoSBR->XBuf[iStart][sbrFreq->kStart + m];
+ for(i = iStart; i < iEnd; i++) {
+ /* scale to int32_t before calculating power (precision not critical, and avoids overflow) */
+ xre = (*XBuf) >> FBITS_OUT_QMFA;
+ XBuf += 1;
+ xim = (*XBuf) >> FBITS_OUT_QMFA;
+ XBuf += (2 * 64 - 1);
+ eCurr.w64 = MADD64(eCurr.w64, xre, xre);
+ eCurr.w64 = MADD64(eCurr.w64, xim, xim);
+ }
+
+ /* eCurr.w64 is now Q(64 - 2*FBITS_OUT_QMFA) (64-bit word)
+ * if energy is too big to fit in 32-bit word (> 2^31) scale down by power of 2
+ */
+ nScale = 0;
+ if(eCurr.r.hi32) {
+ nScale = (32 - CLZ(eCurr.r.hi32)) + 1;
+ t = (int32_t) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */
+ t |= eCurr.r.hi32 << (32 - nScale);
+ }
+ else if(eCurr.r.lo32 >> 31) {
+ nScale = 1;
+ t = (int32_t) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */
+ }
+ else {
+ t = (int32_t) eCurr.r.lo32;
+ }
+
+ invFact = invBandTab[(iEnd - iStart) - 1];
+ m_PSInfoSBR->eCurr[m] = MULSHIFT32(t, invFact);
+ m_PSInfoSBR->eCurrExp[m] = nScale + 1; /* +1 for invFact = Q31 */
+ if(m_PSInfoSBR->eCurrExp[m] > expMax) expMax = m_PSInfoSBR->eCurrExp[m];
+ }
+ }
+ else {
+ for(p = 0; p < n; p++) {
+ mStart = freqBandTab[p];
+ mEnd = freqBandTab[p + 1];
+ eCurr.w64 = 0;
+ for(i = iStart; i < iEnd; i++) {
+ XBuf = m_PSInfoSBR->XBuf[i][mStart];
+ for(m = mStart; m < mEnd; m++) {
+ xre = (*XBuf++) >> FBITS_OUT_QMFA;
+ xim = (*XBuf++) >> FBITS_OUT_QMFA;
+ eCurr.w64 = MADD64(eCurr.w64, xre, xre);
+ eCurr.w64 = MADD64(eCurr.w64, xim, xim);
+ }
+ }
+
+ nScale = 0;
+ if(eCurr.r.hi32) {
+ nScale = (32 - CLZ(eCurr.r.hi32)) + 1;
+ t = (int32_t) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */
+ t |= eCurr.r.hi32 << (32 - nScale);
+ }
+ else if(eCurr.r.lo32 >> 31) {
+ nScale = 1;
+ t = (int32_t) (eCurr.r.lo32 >> nScale); /* logical (unsigned) >> */
+ }
+ else {
+ t = (int32_t) eCurr.r.lo32;
+ }
+
+ invFact = invBandTab[(iEnd - iStart) - 1];
+ invFact = MULSHIFT32(invBandTab[(mEnd - mStart) - 1], invFact) << 1;
+ t = MULSHIFT32(t, invFact);
+
+ for(m = mStart; m < mEnd; m++) {
+ m_PSInfoSBR->eCurr[m - sbrFreq->kStart] = t;
+ m_PSInfoSBR->eCurrExp[m - sbrFreq->kStart] = nScale + 1; /* +1 for invFact = Q31 */
+ }
+ if(m_PSInfoSBR->eCurrExp[mStart - sbrFreq->kStart] > expMax)
+ expMax = m_PSInfoSBR->eCurrExp[mStart - sbrFreq->kStart];
+ }
+ }
+ m_PSInfoSBR->eCurrExpMax = expMax;
+}
+/***********************************************************************************************************************
+ * Function: GetSMapped
+ *
+ * Description: calculate SMapped (4.6.18.7.2)
+ *
+ * Inputs: initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current envelope
+ * index of current QMF band
+ * la flag for this envelope
+ *
+ * Outputs: none
+ *
+ * Return: 1 if a sinusoid is present in this band, 0 if not
+ **********************************************************************************************************************/
+int32_t GetSMapped(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t env, int32_t band, int32_t la) {
+
+ int32_t bandStart, bandEnd, oddFlag, r;
+
+ if (sbrGrid->freqRes[env]) {
+ /* high resolution */
+ bandStart = band;
+ bandEnd = band+1;
+ } else {
+ /* low resolution (see CalcFreqLow() for mapping) */
+ oddFlag = sbrFreq->nHigh & 0x01;
+ bandStart = (band > 0 ? 2*band - oddFlag : 0); /* starting index for freqLow[band] */
+ bandEnd = 2*(band+1) - oddFlag; /* ending index for freqLow[band+1] */
+ }
+
+ /* sMapped = 1 if sIndexMapped == 1 for any frequency in this band */
+ for (band = bandStart; band < bandEnd; band++) {
+ if (sbrChan->addHarmonic[1][band]) {
+ r = ((sbrFreq->freqHigh[band+1] + sbrFreq->freqHigh[band]) >> 1);
+ if (env >= la || sbrChan->addHarmonic[0][r] == 1)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#define GBOOST_MAX 0x2830afd3 /* Q28, 1.584893192 squared */
+#define ACC_SCALE 6
+
+/* squared version of table in 4.6.18.7.5 */ /* Q30 (0x80000000 = sentinel for GMAX) */
+static const uint32_t limGainTab[4] PROGMEM = {0x20138ca7, 0x40000000, 0x7fb27dce, 0x80000000};
+
+/***********************************************************************************************************************
+ * Function: CalcMaxGain
+ *
+ * Description: calculate max gain in one limiter band (4.6.18.7.5)
+ *
+ * Inputs: initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ * index of current envelope
+ * index of current limiter band
+ * number of fraction bits in dequantized envelope
+ * (max = Q(FBITS_OUT_DQ_ENV - 6) = Q23, can go negative)
+ *
+ * Outputs: updated gainMax, gainMaxFBits, and sumEOrigMapped in PSInfoSBR struct
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CalcMaxGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int32_t ch, int32_t env, int32_t lim, int32_t fbitsDQ) {
+
+ int32_t m, mStart, mEnd, q, z, r;
+ int32_t sumEOrigMapped, sumECurr, gainMax, eOMGainMax, envBand;
+ uint8_t eCurrExpMax;
+ uint8_t *freqBandTab;
+
+ mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */
+ mEnd = sbrFreq->freqLimiter[lim + 1];
+ freqBandTab = (sbrGrid->freqRes[env] ? sbrFreq->freqHigh : sbrFreq->freqLow);
+
+ /* calculate max gain to apply to signal in this limiter band */
+ sumECurr = 0;
+ sumEOrigMapped = 0;
+ eCurrExpMax = m_PSInfoSBR->eCurrExpMax;
+ eOMGainMax = m_PSInfoSBR->eOMGainMax;
+ envBand = m_PSInfoSBR->envBand;
+ for(m = mStart; m < mEnd; m++) {
+ /* map current QMF band to appropriate envelope band */
+ if(m == freqBandTab[envBand + 1] - sbrFreq->kStart) {
+ envBand++;
+ eOMGainMax = m_PSInfoSBR->envDataDequant[ch][env][envBand] >> ACC_SCALE; /* summing max 48 bands */
+ }
+ sumEOrigMapped += eOMGainMax;
+
+ /* easy test for overflow on ARM */
+ sumECurr += (m_PSInfoSBR->eCurr[m] >> (eCurrExpMax - m_PSInfoSBR->eCurrExp[m]));
+ if(sumECurr >> 30) {
+ sumECurr >>= 1;
+ eCurrExpMax++;
+ }
+ }
+ m_PSInfoSBR->eOMGainMax = eOMGainMax;
+ m_PSInfoSBR->envBand = envBand;
+
+ m_PSInfoSBR->gainMaxFBits = 30; /* Q30 tables */
+ if(sumECurr == 0) {
+ /* any non-zero numerator * 1/EPS_0 is > G_MAX */
+ gainMax = (sumEOrigMapped == 0 ? (int32_t) limGainTab[sbrHdr->limiterGains] : (int32_t) 0x80000000);
+ }
+ else if(sumEOrigMapped == 0) {
+ /* 1/(any non-zero denominator) * EPS_0 * limGainTab[x] is appx. 0 */
+ gainMax = 0;
+ }
+ else {
+ /* sumEOrigMapped = Q(fbitsDQ - ACC_SCALE), sumECurr = Q(-eCurrExpMax) */
+ gainMax = limGainTab[sbrHdr->limiterGains];
+ if(sbrHdr->limiterGains != 3) {
+ q = MULSHIFT32(sumEOrigMapped, gainMax); /* Q(fbitsDQ - ACC_SCALE - 2), gainMax = Q30 */
+ z = CLZ(sumECurr) - 1;
+ r = InvRNormalized(sumECurr << z); /* in = Q(z - eCurrExpMax), out = Q(29 + 31 - z + eCurrExpMax) */
+ gainMax = MULSHIFT32(q, r); /* Q(29 + 31 - z + eCurrExpMax + fbitsDQ - ACC_SCALE - 2 - 32) */
+ m_PSInfoSBR->gainMaxFBits = 26 - z + eCurrExpMax + fbitsDQ - ACC_SCALE;
+ }
+ }
+ m_PSInfoSBR->sumEOrigMapped = sumEOrigMapped;
+ m_PSInfoSBR->gainMax = gainMax;
+}
+/***********************************************************************************************************************
+ * Function: CalcNoiseDivFactors
+ *
+ * Description: calculate 1/(1+Q) and Q/(1+Q) (4.6.18.7.4; 4.6.18.7.5)
+ *
+ * Inputs: dequantized noise floor scalefactor
+ *
+ * Outputs: 1/(1+Q) and Q/(1+Q), format = Q31
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CalcNoiseDivFactors(int32_t q, int32_t *qp1Inv, int32_t *qqp1Inv) {
+
+ int32_t z, qp1, t, s;
+
+ /* 1 + Q_orig */
+ qp1 = (q >> 1);
+ qp1 += (1 << (FBITS_OUT_DQ_NOISE - 1)); /* >> 1 to avoid overflow when adding 1.0 */
+ z = CLZ(qp1) - 1; /* z <= 31 - FBITS_OUT_DQ_NOISE */
+ qp1 <<= z; /* Q(FBITS_OUT_DQ_NOISE + z) = Q31 * 2^-(31 - (FBITS_OUT_DQ_NOISE + z)) */
+ t = InvRNormalized(qp1) << 1; /* Q30 * 2^(31 - (FBITS_OUT_DQ_NOISE + z)), guaranteed not to overflow */
+
+ /* normalize to Q31 */
+ s = (31 - (FBITS_OUT_DQ_NOISE - 1) - z - 1); /* clearly z >= 0, z <= (30 - (FBITS_OUT_DQ_NOISE - 1)) */
+ *qp1Inv = (t >> s); /* s = [0, 31 - FBITS_OUT_DQ_NOISE] */
+ *qqp1Inv = MULSHIFT32(t, q) << (32 - FBITS_OUT_DQ_NOISE - s);
+}
+/***********************************************************************************************************************
+ * Function: CalcComponentGains
+ *
+ * Description: calculate gain of envelope, sinusoids, and noise in one limiter band
+ * (4.6.18.7.5)
+ *
+ * Inputs: initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ * index of current envelope
+ * index of current limiter band
+ * number of fraction bits in dequantized envelope
+ *
+ * Outputs: gains for envelope, sinusoids and noise
+ * number of fraction bits for envelope gain
+ * sum of the total gain for each component in this band
+ * other updated state variables
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CalcComponentGains(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch, int32_t env, int32_t lim, int32_t fbitsDQ) {
+
+ int32_t d, m, mStart, mEnd, q, qm, noiseFloor, sIndexMapped;
+ int32_t shift, eCurr, maxFlag, gainMax, gainMaxFBits;
+ int32_t gain, sm, z, r, fbitsGain, gainScale;
+ uint8_t *freqBandTab;
+
+ mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */
+ mEnd = sbrFreq->freqLimiter[lim + 1];
+
+ gainMax = m_PSInfoSBR->gainMax;
+ gainMaxFBits = m_PSInfoSBR->gainMaxFBits;
+
+ d = (env == m_PSInfoSBR->la || env == sbrChan->laPrev ? 0 : 1);
+ freqBandTab = (sbrGrid->freqRes[env] ? sbrFreq->freqHigh : sbrFreq->freqLow);
+
+ /* figure out which noise floor this envelope is in (only 1 or 2 noise floors allowed) */
+ noiseFloor = 0;
+ if(sbrGrid->numNoiseFloors == 2 && sbrGrid->noiseTimeBorder[1] <= sbrGrid->envTimeBorder[env]) noiseFloor++;
+
+ m_PSInfoSBR->sumECurrGLim = 0;
+ m_PSInfoSBR->sumSM = 0;
+ m_PSInfoSBR->sumQM = 0;
+ /* calculate energy of noise to add in this limiter band */
+ for(m = mStart; m < mEnd; m++) {
+ if(m == sbrFreq->freqNoise[m_PSInfoSBR->noiseFloorBand + 1] - sbrFreq->kStart) {
+ /* map current QMF band to appropriate noise floor band (NOTE: freqLimiter[0] == freqLow[0] = freqHigh[0]) */
+ m_PSInfoSBR->noiseFloorBand++;
+ CalcNoiseDivFactors(m_PSInfoSBR->noiseDataDequant[ch][noiseFloor][m_PSInfoSBR->noiseFloorBand],
+ &(m_PSInfoSBR->qp1Inv), &(m_PSInfoSBR->qqp1Inv));
+ }
+ if(m == sbrFreq->freqHigh[m_PSInfoSBR->highBand + 1] - sbrFreq->kStart) m_PSInfoSBR->highBand++;
+ if(m == freqBandTab[m_PSInfoSBR->sBand + 1] - sbrFreq->kStart) {
+ m_PSInfoSBR->sBand++;
+ m_PSInfoSBR->sMapped = GetSMapped(sbrGrid, sbrFreq, sbrChan, env, m_PSInfoSBR->sBand, m_PSInfoSBR->la);
+ }
+
+ /* get sIndexMapped for this QMF subband */
+ sIndexMapped = 0;
+ r = ((sbrFreq->freqHigh[m_PSInfoSBR->highBand + 1] + sbrFreq->freqHigh[m_PSInfoSBR->highBand]) >> 1);
+ if(m + sbrFreq->kStart == r) {
+ /* r = center frequency, deltaStep = (env >= la || sIndexMapped'(r, numEnv'-1) == 1) */
+ if(env >= m_PSInfoSBR->la || sbrChan->addHarmonic[0][r] == 1) sIndexMapped =
+ sbrChan->addHarmonic[1][m_PSInfoSBR->highBand];
+ }
+
+ /* save sine flags from last envelope in this frame:
+ * addHarmonic[0][0...63] = saved sine present flag from previous frame, for each QMF subband
+ * addHarmonic[1][0...nHigh-1] = addHarmonic bit from current frame, for each high-res frequency band
+ * from MPEG reference code - slightly different from spec
+ * (sIndexMapped'(m,LE'-1) can still be 0 when numEnv == psi->la)
+ */
+ if(env == sbrGrid->numEnv - 1) {
+ if(m + sbrFreq->kStart == r)
+ sbrChan->addHarmonic[0][m + sbrFreq->kStart] = sbrChan->addHarmonic[1][m_PSInfoSBR->highBand];
+ else
+ sbrChan->addHarmonic[0][m + sbrFreq->kStart] = 0;
+ }
+
+ gain = m_PSInfoSBR->envDataDequant[ch][env][m_PSInfoSBR->sBand];
+ qm = MULSHIFT32(gain, m_PSInfoSBR->qqp1Inv) << 1;
+ sm = (sIndexMapped ? MULSHIFT32(gain, m_PSInfoSBR->qp1Inv) << 1 : 0);
+
+ /* three cases: (sMapped == 0 && delta == 1), (sMapped == 0 && delta == 0), (sMapped == 1) */
+ if(d == 1 && m_PSInfoSBR->sMapped == 0)
+ gain = MULSHIFT32(m_PSInfoSBR->qp1Inv, gain) << 1;
+ else if(m_PSInfoSBR->sMapped != 0) gain = MULSHIFT32(m_PSInfoSBR->qqp1Inv, gain) << 1;
+
+ /* gain, qm, sm = Q(fbitsDQ), gainMax = Q(fbitsGainMax) */
+ eCurr = m_PSInfoSBR->eCurr[m];
+ if(eCurr) {
+ z = CLZ(eCurr) - 1;
+ r = InvRNormalized(eCurr << z); /* in = Q(z - eCurrExp), out = Q(29 + 31 - z + eCurrExp) */
+ gainScale = MULSHIFT32(gain, r); /* out = Q(29 + 31 - z + eCurrExp + fbitsDQ - 32) */
+ fbitsGain = 29 + 31 - z + m_PSInfoSBR->eCurrExp[m] + fbitsDQ - 32;
+ }
+ else {
+ /* if eCurr == 0, then gain is unchanged (divide by EPS = 1) */
+ gainScale = gain;
+ fbitsGain = fbitsDQ;
+ }
+
+ /* see if gain for this band exceeds max gain */
+ maxFlag = 0;
+ if(gainMax != (int32_t) 0x80000000) {
+ if(fbitsGain >= gainMaxFBits) {
+ shift = MIN(fbitsGain - gainMaxFBits, (int32_t)31);
+ maxFlag = ((gainScale >> shift) > gainMax ? 1 : 0);
+ }
+ else {
+ shift = MIN(gainMaxFBits - fbitsGain, (int32_t)31);
+ maxFlag = (gainScale > (gainMax >> shift) ? 1 : 0);
+ }
+ }
+
+ if(maxFlag) {
+ /* gainScale > gainMax, calculate ratio with 32/16 division */
+ q = 0;
+ r = gainScale; /* guaranteed > 0, else maxFlag could not have been set */
+ z = CLZ(r);
+ if(z < 16) {
+ q = 16 - z;
+ r >>= q; /* out = Q(fbitsGain - q) */
+ }
+
+ z = CLZ(gainMax) - 1;
+ r = (gainMax << z) / r; /* out = Q((fbitsGainMax + z) - (fbitsGain - q)) */
+ q = (gainMaxFBits + z) - (fbitsGain - q); /* r = Q(q) */
+ if(q > 30) {
+ r >>= MIN(q - 30, (int32_t)31);
+ }
+ else {
+ z = MIN((int32_t)30 - q, (int32_t)30);
+ r = CLIP_2N_SHIFT30(r, z); /* let r = Q30 since range = [0.0, 1.0) (clip to 0x3fffffff = 0.99999) */
+ }
+
+ qm = MULSHIFT32(qm, r) << 2;
+ gain = MULSHIFT32(gain, r) << 2;
+ m_PSInfoSBR->gLimBuf[m] = gainMax;
+ m_PSInfoSBR->gLimFbits[m] = gainMaxFBits;
+ }
+ else {
+ m_PSInfoSBR->gLimBuf[m] = gainScale;
+ m_PSInfoSBR->gLimFbits[m] = fbitsGain;
+ }
+
+ /* sumSM, sumQM, sumECurrGLim = Q(fbitsDQ - ACC_SCALE) */
+ m_PSInfoSBR->smBuf[m] = sm;
+ m_PSInfoSBR->sumSM += (sm >> ACC_SCALE);
+
+ m_PSInfoSBR->qmLimBuf[m] = qm;
+ if(env != m_PSInfoSBR->la && env != sbrChan->laPrev && sm == 0) m_PSInfoSBR->sumQM += (qm >> ACC_SCALE);
+
+ /* eCurr * gain^2 same as gain^2, before division by eCurr
+ * (but note that gain != 0 even if eCurr == 0, since it's divided by eps)
+ */
+ if(eCurr) m_PSInfoSBR->sumECurrGLim += (gain >> ACC_SCALE);
+ }
+}
+/***********************************************************************************************************************
+ * Function: ApplyBoost
+ *
+ * Description: calculate and apply boost factor for envelope, sinusoids, and noise
+ * in this limiter band (4.6.18.7.5)
+ *
+ * Inputs: initialized SBRFreq struct for this SCE/CPE block
+ * index of current limiter band
+ * number of fraction bits in dequantized envelope
+ *
+ * Outputs: envelope gain, sinusoids and noise after scaling by gBoost
+ * format = Q(FBITS_GLIM_BOOST) for envelope gain,
+ * = Q(FBITS_QLIM_BOOST) for noise
+ * = Q(FBITS_OUT_QMFA) for sinusoids
+ *
+ * Return: none
+ *
+ * Notes: after scaling, each component has at least 1 GB
+ **********************************************************************************************************************/
+void ApplyBoost(SBRFreq *sbrFreq, int32_t lim, int32_t fbitsDQ) {
+
+ int32_t m, mStart, mEnd, q, z, r;
+ int32_t sumEOrigMapped, gBoost;
+
+ mStart = sbrFreq->freqLimiter[lim]; /* these are offsets from kStart */
+ mEnd = sbrFreq->freqLimiter[lim + 1];
+
+ sumEOrigMapped = m_PSInfoSBR->sumEOrigMapped >> 1;
+ r = (m_PSInfoSBR->sumECurrGLim >> 1) + (m_PSInfoSBR->sumSM >> 1) + (m_PSInfoSBR->sumQM >> 1); /* 1 GB fine (sm and qm are mutually exclusive in acc) */
+ if(r < (1 << (31 - 28))) {
+ /* any non-zero numerator * 1/EPS_0 is > GBOOST_MAX
+ * round very small r to zero to avoid scaling problems
+ */
+ gBoost = (sumEOrigMapped == 0 ? (1 << 28) : GBOOST_MAX);
+ z = 0;
+ }
+ else if(sumEOrigMapped == 0) {
+ /* 1/(any non-zero denominator) * EPS_0 is appx. 0 */
+ gBoost = 0;
+ z = 0;
+ }
+ else {
+ /* numerator (sumEOrigMapped) and denominator (r) have same Q format (before << z) */
+ z = CLZ(r) - 1; /* z = [0, 27] */
+ r = InvRNormalized(r << z);
+ gBoost = MULSHIFT32(sumEOrigMapped, r);
+ }
+
+ /* gBoost = Q(28 - z) */
+ if(gBoost > (GBOOST_MAX >> z)) {
+ gBoost = GBOOST_MAX;
+ z = 0;
+ }
+ gBoost <<= z; /* gBoost = Q28, minimum 1 GB */
+
+ /* convert gain, noise, sinusoids to fixed Q format, clipping if necessary
+ * (rare, usually only happens at very low bitrates, introduces slight
+ * distortion into final HF mapping, but should be inaudible)
+ */
+ for(m = mStart; m < mEnd; m++) {
+ /* let gLimBoost = Q24, since in practice the max values are usually 16 to 20
+ * unless limiterGains == 3 (limiter off) and eCurr ~= 0 (i.e. huge gain, but only
+ * because the envelope has 0 power anyway)
+ */
+ q = MULSHIFT32(m_PSInfoSBR->gLimBuf[m], gBoost) << 2; /* Q(gLimFbits) * Q(28) --> Q(gLimFbits[m]-2) */
+ r = SqrtFix(q, m_PSInfoSBR->gLimFbits[m] - 2, &z);
+ z -= FBITS_GLIM_BOOST;
+ if(z >= 0) {
+ m_PSInfoSBR->gLimBoost[m] = r >> MIN(z, (int32_t)31);
+ }
+ else {
+ z = MIN((int32_t)30, -z);
+ r = CLIP_2N_SHIFT30(r, z);
+ m_PSInfoSBR->gLimBoost[m] = r;
+ }
+
+ q = MULSHIFT32(m_PSInfoSBR->qmLimBuf[m], gBoost) << 2; /* Q(fbitsDQ) * Q(28) --> Q(fbitsDQ-2) */
+ r = SqrtFix(q, fbitsDQ - 2, &z);
+ z -= FBITS_QLIM_BOOST; /* << by 14, since integer sqrt of x < 2^16, and we want to leave 1 GB */
+ if(z >= 0) {
+ m_PSInfoSBR->qmLimBoost[m] = r >> MIN((int32_t)31, z);
+ }
+ else {
+ z = MIN((int32_t)30, -z);
+ r = CLIP_2N_SHIFT30(r, z);
+ m_PSInfoSBR->qmLimBoost[m] = r;
+ }
+
+ q = MULSHIFT32(m_PSInfoSBR->smBuf[m], gBoost) << 2; /* Q(fbitsDQ) * Q(28) --> Q(fbitsDQ-2) */
+ r = SqrtFix(q, fbitsDQ - 2, &z);
+ z -= FBITS_OUT_QMFA; /* justify for adding to signal (xBuf) later */
+ if(z >= 0) {
+ m_PSInfoSBR->smBoost[m] = r >> MIN((int32_t)31, z);
+ }
+ else {
+ z = MIN((int32_t)30, -z);
+ r = CLIP_2N_SHIFT30(r, z);
+ m_PSInfoSBR->smBoost[m] = r;
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: CalcGain
+ *
+ * Description: calculate and apply proper gain to HF components in one envelope
+ * (4.6.18.7.5)
+ *
+ * Inputs: initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ * index of current envelope
+ *
+ * Outputs: envelope gain, sinusoids and noise after scaling
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CalcGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch, int32_t env) {
+
+ int32_t lim, fbitsDQ;
+
+ /* initialize to -1 so that mapping limiter bands to env/noise bands works right on first pass */
+ m_PSInfoSBR->envBand = -1;
+ m_PSInfoSBR->noiseFloorBand = -1;
+ m_PSInfoSBR->sBand = -1;
+ m_PSInfoSBR->highBand = -1;
+
+ fbitsDQ = (FBITS_OUT_DQ_ENV - m_PSInfoSBR->envDataDequantScale[ch][env]); /* Q(29 - optional scalefactor) */
+ for(lim = 0; lim < sbrFreq->nLimiter; lim++) {
+ /* the QMF bands are divided into lim regions (consecutive, non-overlapping) */
+ CalcMaxGain(sbrHdr, sbrGrid, sbrFreq, ch, env, lim, fbitsDQ);
+ CalcComponentGains(sbrGrid, sbrFreq, sbrChan, ch, env, lim, fbitsDQ);
+ ApplyBoost(sbrFreq, lim, fbitsDQ);
+ }
+}
+
+/* hSmooth table from 4.7.18.7.6, format = Q31 */
+static const int32_t hSmoothCoef[MAX_NUM_SMOOTH_COEFS] PROGMEM = { 0x2aaaaaab, 0x2697a512, 0x1becfa68, 0x0ebdb043,
+ 0x04130598, };
+
+/***********************************************************************************************************************
+ * Function: MapHF
+ *
+ * Description: map HF components to proper QMF bands, with optional gain smoothing
+ * filter (4.6.18.7.6)
+ *
+ * Inputs: initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current envelope
+ * reset flag (can be non-zero for first envelope only)
+ *
+ * Outputs: complete reconstructed subband QMF samples for this envelope
+ *
+ * Return: none
+ *
+ * Notes: ensures that output has >= MIN_GBITS_IN_QMFS guard bits,
+ * so it's not necessary to check anything in the synth QMF
+ **********************************************************************************************************************/
+void MapHF(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t env, int32_t hfReset) {
+
+ int32_t noiseTabIndex, sinIndex, gainNoiseIndex, hSL;
+ int32_t i, iStart, iEnd, m, idx, j, s, n, smre, smim;
+ int32_t gFilt, qFilt, xre, xim, gbMask, gbIdx;
+ int32_t *XBuf;
+
+ noiseTabIndex = sbrChan->noiseTabIndex;
+ sinIndex = sbrChan->sinIndex;
+ gainNoiseIndex = sbrChan->gainNoiseIndex; /* oldest entries in filter delay buffer */
+
+ if(hfReset) noiseTabIndex = 2; /* starts at 1, double since complex */
+ hSL = (sbrHdr->smoothMode ? 0 : 4);
+
+ if(hfReset) {
+ for(i = 0; i < hSL; i++) {
+ for(m = 0; m < sbrFreq->numQMFBands; m++) {
+ sbrChan->gTemp[gainNoiseIndex][m] = m_PSInfoSBR->gLimBoost[m];
+ sbrChan->qTemp[gainNoiseIndex][m] = m_PSInfoSBR->qmLimBoost[m];
+ }
+ gainNoiseIndex++;
+ if(gainNoiseIndex == MAX_NUM_SMOOTH_COEFS) gainNoiseIndex = 0;
+ } ASSERT(env == 0); /* should only be reset when env == 0 */
+ }
+
+ iStart = sbrGrid->envTimeBorder[env];
+ iEnd = sbrGrid->envTimeBorder[env + 1];
+ for(i = iStart; i < iEnd; i++) {
+ /* save new values in temp buffers (delay)
+ * we only store MAX_NUM_SMOOTH_COEFS most recent values,
+ * so don't keep storing the same value over and over
+ */
+ if(i - iStart < MAX_NUM_SMOOTH_COEFS) {
+ for(m = 0; m < sbrFreq->numQMFBands; m++) {
+ sbrChan->gTemp[gainNoiseIndex][m] = m_PSInfoSBR->gLimBoost[m];
+ sbrChan->qTemp[gainNoiseIndex][m] = m_PSInfoSBR->qmLimBoost[m];
+ }
+ }
+
+ /* see 4.6.18.7.6 */
+ XBuf = m_PSInfoSBR->XBuf[i + HF_ADJ][sbrFreq->kStart];
+ gbMask = 0;
+ for(m = 0; m < sbrFreq->numQMFBands; m++) {
+ if(env == m_PSInfoSBR->la || env == sbrChan->laPrev) {
+ /* no smoothing filter for gain, and qFilt = 0 (only need to do once) */
+ if(i == iStart) {
+ m_PSInfoSBR->gFiltLast[m] = sbrChan->gTemp[gainNoiseIndex][m];
+ m_PSInfoSBR->qFiltLast[m] = 0;
+ }
+ }
+ else if(hSL == 0) {
+ /* no smoothing filter for gain, (only need to do once) */
+ if(i == iStart) {
+ m_PSInfoSBR->gFiltLast[m] = sbrChan->gTemp[gainNoiseIndex][m];
+ m_PSInfoSBR->qFiltLast[m] = sbrChan->qTemp[gainNoiseIndex][m];
+ }
+ }
+ else {
+ /* apply smoothing filter to gain and noise (after MAX_NUM_SMOOTH_COEFS, it's always the same) */
+ if(i - iStart < MAX_NUM_SMOOTH_COEFS) {
+ gFilt = 0;
+ qFilt = 0;
+ idx = gainNoiseIndex;
+ for(j = 0; j < MAX_NUM_SMOOTH_COEFS; j++) {
+ /* sum(abs(hSmoothCoef[j])) for all j < 1.0 */
+ gFilt += MULSHIFT32(sbrChan->gTemp[idx][m], hSmoothCoef[j]);
+ qFilt += MULSHIFT32(sbrChan->qTemp[idx][m], hSmoothCoef[j]);
+ idx--;
+ if(idx < 0) idx += MAX_NUM_SMOOTH_COEFS;
+ }
+ m_PSInfoSBR->gFiltLast[m] = gFilt << 1; /* restore to Q(FBITS_GLIM_BOOST) (gain of filter < 1.0, so no overflow) */
+ m_PSInfoSBR->qFiltLast[m] = qFilt << 1; /* restore to Q(FBITS_QLIM_BOOST) */
+ }
+ }
+
+ if(m_PSInfoSBR->smBoost[m] != 0) {
+ /* add scaled signal and sinusoid, don't add noise (qFilt = 0) */
+ smre = m_PSInfoSBR->smBoost[m];
+ smim = smre;
+
+ /* sinIndex: [0] xre += sm [1] xim += sm*s [2] xre -= sm [3] xim -= sm*s */
+ s = (sinIndex >> 1); /* if 2 or 3, flip sign to subtract sm */
+ s <<= 31;
+ smre ^= (s >> 31);
+ smre -= (s >> 31);
+ s ^= ((m + sbrFreq->kStart) << 31);
+ smim ^= (s >> 31);
+ smim -= (s >> 31);
+
+ /* if sinIndex == 0 or 2, smim = 0; if sinIndex == 1 or 3, smre = 0 */
+ s = sinIndex << 31;
+ smim &= (s >> 31);
+ s ^= 0x80000000;
+ smre &= (s >> 31);
+
+ noiseTabIndex += 2; /* noise filtered by 0, but still need to bump index */
+ }
+ else {
+ /* add scaled signal and scaled noise */
+ qFilt = m_PSInfoSBR->qFiltLast[m];
+ n = noiseTab[noiseTabIndex++];
+ smre = MULSHIFT32(n, qFilt) >> (FBITS_QLIM_BOOST - 1 - FBITS_OUT_QMFA);
+
+ n = noiseTab[noiseTabIndex++];
+ smim = MULSHIFT32(n, qFilt) >> (FBITS_QLIM_BOOST - 1 - FBITS_OUT_QMFA);
+ }
+ noiseTabIndex &= 1023; /* 512 complex numbers */
+
+ gFilt = m_PSInfoSBR->gFiltLast[m];
+ xre = MULSHIFT32(gFilt, XBuf[0]);
+ xim = MULSHIFT32(gFilt, XBuf[1]);
+ xre = CLIP_2N_SHIFT30(xre, 32 - FBITS_GLIM_BOOST);
+ xim = CLIP_2N_SHIFT30(xim, 32 - FBITS_GLIM_BOOST);
+
+ xre += smre;
+ *XBuf++ = xre;
+ xim += smim;
+ *XBuf++ = xim;
+
+ gbMask |= FASTABS(xre);
+ gbMask |= FASTABS(xim);
+ }
+ /* update circular buffer index */
+ gainNoiseIndex++;
+ if(gainNoiseIndex == MAX_NUM_SMOOTH_COEFS) gainNoiseIndex = 0;
+
+ sinIndex++;
+ sinIndex &= 3;
+
+ /* ensure MIN_GBITS_IN_QMFS guard bits in output
+ * almost never occurs in practice, but checking here makes synth QMF logic very simple
+ */
+ if(gbMask >> (31 - MIN_GBITS_IN_QMFS)) {
+ XBuf = m_PSInfoSBR->XBuf[i + HF_ADJ][sbrFreq->kStart];
+ for(m = 0; m < sbrFreq->numQMFBands; m++) {
+ xre = XBuf[0];
+ xim = XBuf[1];
+ xre = CLIP_2N(xre, (31 - MIN_GBITS_IN_QMFS));
+ xim = CLIP_2N(xim, (31 - MIN_GBITS_IN_QMFS));
+ *XBuf++ = xre;
+ *XBuf++ = xim;
+ }
+ gbMask = CLIP_2N(gbMask, (31 - MIN_GBITS_IN_QMFS));
+ }
+ gbIdx = ((i + HF_ADJ) >> 5) & 0x01;
+ sbrChan->gbMask[gbIdx] |= gbMask;
+ }
+ sbrChan->noiseTabIndex = noiseTabIndex;
+ sbrChan->sinIndex = sinIndex;
+ sbrChan->gainNoiseIndex = gainNoiseIndex;
+}
+/***********************************************************************************************************************
+ * Function: AdjustHighFreq
+ *
+ * Description: adjust high frequencies and add noise and sinusoids (4.6.18.7)
+ *
+ * Inputs: initialized SBRHeader struct for this SCE/CPE block
+ * initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ *
+ * Outputs: complete reconstructed subband QMF samples for this channel
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void AdjustHighFreq(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch) {
+
+ int32_t i, env, hfReset;
+ uint8_t frameClass, pointer;
+
+ frameClass = sbrGrid->frameClass;
+ pointer = sbrGrid->pointer;
+
+ /* derive la from table 4.159 */
+ if ((frameClass == SBR_GRID_FIXVAR || frameClass == SBR_GRID_VARVAR) && pointer > 0)
+ m_PSInfoSBR->la = sbrGrid->numEnv + 1 - pointer;
+ else if (frameClass == SBR_GRID_VARFIX && pointer > 1)
+ m_PSInfoSBR->la = pointer - 1;
+ else
+ m_PSInfoSBR->la = -1;
+
+ /* for each envelope, estimate gain and adjust SBR QMF bands */
+ hfReset = sbrChan->reset;
+ for (env = 0; env < sbrGrid->numEnv; env++) {
+ EstimateEnvelope(sbrHdr, sbrGrid, sbrFreq, env);
+ CalcGain(sbrHdr, sbrGrid, sbrFreq, sbrChan, ch, env);
+ MapHF(sbrHdr, sbrGrid, sbrFreq, sbrChan, env, hfReset);
+ hfReset = 0; /* only set for first envelope after header reset */
+ }
+
+ /* set saved sine flags to 0 for QMF bands outside of current frequency range */
+ for (i = 0; i < sbrFreq->freqLimiter[0] + sbrFreq->kStart; i++)
+ sbrChan->addHarmonic[0][i] = 0;
+ for (i = sbrFreq->freqLimiter[sbrFreq->nLimiter] + sbrFreq->kStart; i < 64; i++)
+ sbrChan->addHarmonic[0][i] = 0;
+ sbrChan->addHarmonicFlag[0] = sbrChan->addHarmonicFlag[1];
+
+ /* save la for next frame */
+ if (m_PSInfoSBR->la == sbrGrid->numEnv)
+ sbrChan->laPrev = 0;
+ else
+ sbrChan->laPrev = -1;
+}
+/***********************************************************************************************************************
+ * Function: CalcCovariance1
+ *
+ * Description: calculate covariance matrix for p01, p12, p11, p22 (4.6.18.6.2)
+ *
+ * Inputs: buffer of low-freq samples, starting at time index 0,
+ * freq index = patch subband
+ *
+ * Outputs: complex covariance elements p01re, p01im, p12re, p12im, p11re, p22re
+ * (p11im = p22im = 0)
+ * format = integer (Q0) * 2^N, with scalefactor N >= 0
+ *
+ * Return: scalefactor N
+ *
+ * Notes: outputs are normalized to have 1 GB (sign in at least top 2 bits)
+ **********************************************************************************************************************/
+int32_t CalcCovariance1(int32_t *XBuf, int32_t *p01reN, int32_t *p01imN, int32_t *p12reN, int32_t *p12imN, int32_t *p11reN, int32_t *p22reN) {
+
+ int32_t accBuf[2*6];
+ int32_t n, z, s, loShift, hiShift, gbMask;
+ U64 p01re, p01im, p12re, p12im, p11re, p22re;
+
+ CVKernel1(XBuf, accBuf);
+ p01re.r.lo32 = accBuf[0]; p01re.r.hi32 = accBuf[1];
+ p01im.r.lo32 = accBuf[2]; p01im.r.hi32 = accBuf[3];
+ p11re.r.lo32 = accBuf[4]; p11re.r.hi32 = accBuf[5];
+ p12re.r.lo32 = accBuf[6]; p12re.r.hi32 = accBuf[7];
+ p12im.r.lo32 = accBuf[8]; p12im.r.hi32 = accBuf[9];
+ p22re.r.lo32 = accBuf[10]; p22re.r.hi32 = accBuf[11];
+
+ /* 64-bit accumulators now have 2*FBITS_OUT_QMFA fraction bits
+ * want to scale them down to integers (32-bit signed, Q0)
+ * with scale factor of 2^n, n >= 0
+ * leave 2 GB's for calculating determinant, so take top 30 non-zero bits
+ */
+ gbMask = ((p01re.r.hi32) ^ (p01re.r.hi32 >> 31)) | ((p01im.r.hi32) ^ (p01im.r.hi32 >> 31));
+ gbMask |= ((p12re.r.hi32) ^ (p12re.r.hi32 >> 31)) | ((p12im.r.hi32) ^ (p12im.r.hi32 >> 31));
+ gbMask |= ((p11re.r.hi32) ^ (p11re.r.hi32 >> 31)) | ((p22re.r.hi32) ^ (p22re.r.hi32 >> 31));
+ if (gbMask == 0) {
+ s = p01re.r.hi32 >> 31; gbMask = (p01re.r.lo32 ^ s) - s;
+ s = p01im.r.hi32 >> 31; gbMask |= (p01im.r.lo32 ^ s) - s;
+ s = p12re.r.hi32 >> 31; gbMask |= (p12re.r.lo32 ^ s) - s;
+ s = p12im.r.hi32 >> 31; gbMask |= (p12im.r.lo32 ^ s) - s;
+ s = p11re.r.hi32 >> 31; gbMask |= (p11re.r.lo32 ^ s) - s;
+ s = p22re.r.hi32 >> 31; gbMask |= (p22re.r.lo32 ^ s) - s;
+ z = 32 + CLZ(gbMask);
+ } else {
+ gbMask = FASTABS(p01re.r.hi32) | FASTABS(p01im.r.hi32);
+ gbMask |= FASTABS(p12re.r.hi32) | FASTABS(p12im.r.hi32);
+ gbMask |= FASTABS(p11re.r.hi32) | FASTABS(p22re.r.hi32);
+ z = CLZ(gbMask);
+ }
+
+ n = 64 - z; /* number of non-zero bits in bottom of 64-bit word */
+ if (n <= 30) {
+ loShift = (30 - n);
+ *p01reN = p01re.r.lo32 << loShift; *p01imN = p01im.r.lo32 << loShift;
+ *p12reN = p12re.r.lo32 << loShift; *p12imN = p12im.r.lo32 << loShift;
+ *p11reN = p11re.r.lo32 << loShift; *p22reN = p22re.r.lo32 << loShift;
+ return -(loShift + 2*FBITS_OUT_QMFA);
+ } else if (n < 32 + 30) {
+ loShift = (n - 30);
+ hiShift = 32 - loShift;
+ *p01reN = (p01re.r.hi32 << hiShift) | (p01re.r.lo32 >> loShift);
+ *p01imN = (p01im.r.hi32 << hiShift) | (p01im.r.lo32 >> loShift);
+ *p12reN = (p12re.r.hi32 << hiShift) | (p12re.r.lo32 >> loShift);
+ *p12imN = (p12im.r.hi32 << hiShift) | (p12im.r.lo32 >> loShift);
+ *p11reN = (p11re.r.hi32 << hiShift) | (p11re.r.lo32 >> loShift);
+ *p22reN = (p22re.r.hi32 << hiShift) | (p22re.r.lo32 >> loShift);
+ return (loShift - 2*FBITS_OUT_QMFA);
+ } else {
+ hiShift = n - (32 + 30);
+ *p01reN = p01re.r.hi32 >> hiShift; *p01imN = p01im.r.hi32 >> hiShift;
+ *p12reN = p12re.r.hi32 >> hiShift; *p12imN = p12im.r.hi32 >> hiShift;
+ *p11reN = p11re.r.hi32 >> hiShift; *p22reN = p22re.r.hi32 >> hiShift;
+ return (32 - 2*FBITS_OUT_QMFA - hiShift);
+ }
+
+ return 0;
+}
+/***********************************************************************************************************************
+ * Function: CalcCovariance2
+ *
+ * Description: calculate covariance matrix for p02 (4.6.18.6.2)
+ *
+ * Inputs: buffer of low-freq samples, starting at time index = 0,
+ * freq index = patch subband
+ *
+ * Outputs: complex covariance element p02re, p02im
+ * format = integer (Q0) * 2^N, with scalefactor N >= 0
+ *
+ * Return: scalefactor N
+ *
+ * Notes: outputs are normalized to have 1 GB (sign in at least top 2 bits)
+ **********************************************************************************************************************/
+int32_t CalcCovariance2(int32_t *XBuf, int32_t *p02reN, int32_t *p02imN) {
+
+ U64 p02re, p02im;
+ int32_t n, z, s, loShift, hiShift, gbMask;
+ int32_t accBuf[2*2];
+
+ CVKernel2(XBuf, accBuf);
+ p02re.r.lo32 = accBuf[0];
+ p02re.r.hi32 = accBuf[1];
+ p02im.r.lo32 = accBuf[2];
+ p02im.r.hi32 = accBuf[3];
+
+ /* 64-bit accumulators now have 2*FBITS_OUT_QMFA fraction bits
+ * want to scale them down to integers (32-bit signed, Q0)
+ * with scale factor of 2^n, n >= 0
+ * leave 1 GB for calculating determinant, so take top 30 non-zero bits
+ */
+ gbMask = ((p02re.r.hi32) ^ (p02re.r.hi32 >> 31)) | ((p02im.r.hi32) ^ (p02im.r.hi32 >> 31));
+ if (gbMask == 0) {
+ s = p02re.r.hi32 >> 31; gbMask = (p02re.r.lo32 ^ s) - s;
+ s = p02im.r.hi32 >> 31; gbMask |= (p02im.r.lo32 ^ s) - s;
+ z = 32 + CLZ(gbMask);
+ } else {
+ gbMask = FASTABS(p02re.r.hi32) | FASTABS(p02im.r.hi32);
+ z = CLZ(gbMask);
+ }
+ n = 64 - z; /* number of non-zero bits in bottom of 64-bit word */
+
+ if (n <= 30) {
+ loShift = (30 - n);
+ *p02reN = p02re.r.lo32 << loShift;
+ *p02imN = p02im.r.lo32 << loShift;
+ return -(loShift + 2*FBITS_OUT_QMFA);
+ } else if (n < 32 + 30) {
+ loShift = (n - 30);
+ hiShift = 32 - loShift;
+ *p02reN = (p02re.r.hi32 << hiShift) | (p02re.r.lo32 >> loShift);
+ *p02imN = (p02im.r.hi32 << hiShift) | (p02im.r.lo32 >> loShift);
+ return (loShift - 2*FBITS_OUT_QMFA);
+ } else {
+ hiShift = n - (32 + 30);
+ *p02reN = p02re.r.hi32 >> hiShift;
+ *p02imN = p02im.r.hi32 >> hiShift;
+ return (32 - 2*FBITS_OUT_QMFA - hiShift);
+ }
+
+ return 0;
+}
+/***********************************************************************************************************************
+ * Function: CalcLPCoefs
+ *
+ * Description: calculate linear prediction coefficients for one subband (4.6.18.6.2)
+ *
+ * Inputs: buffer of low-freq samples, starting at time index = 0,
+ * freq index = patch subband
+ * number of guard bits in input sample buffer
+ *
+ * Outputs: complex LP coefficients a0re, a0im, a1re, a1im, format = Q29
+ *
+ * Return: none
+ *
+ * Notes: output coefficients (a0re, a0im, a1re, a1im) clipped to range (-4, 4)
+ * if the comples coefficients have magnitude >= 4.0, they are all
+ * set to 0 (see spec)
+ **********************************************************************************************************************/
+void CalcLPCoefs(int32_t *XBuf, int32_t *a0re, int32_t *a0im, int32_t *a1re, int32_t *a1im, int32_t gb) {
+
+ int32_t zFlag, n1, n2, nd, d, dInv, tre, tim;
+ int32_t p01re, p01im, p02re, p02im, p12re, p12im, p11re, p22re;
+
+ /* pre-scale to avoid overflow - probably never happens in practice (see QMFA)
+ * max bit growth per accumulator = 38*2 = 76 mul-adds (X * X)
+ * using 64-bit MADD, so if X has n guard bits, X*X has 2n+1 guard bits
+ * gain 1 extra sign bit per multiply, so ensure ceil(log2(76/2) / 2) = 3 guard bits on inputs
+ */
+ if (gb < 3) {
+ nd = 3 - gb;
+ for (n1 = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2); n1 != 0; n1--) {
+ XBuf[0] >>= nd; XBuf[1] >>= nd;
+ XBuf += (2*64);
+ }
+ XBuf -= (2*64*(NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2));
+ }
+
+ /* calculate covariance elements */
+ n1 = CalcCovariance1(XBuf, &p01re, &p01im, &p12re, &p12im, &p11re, &p22re);
+ n2 = CalcCovariance2(XBuf, &p02re, &p02im);
+
+ /* normalize everything to larger power of 2 scalefactor, call it n1 */
+ if (n1 < n2) {
+ nd = MIN(n2 - n1, (int32_t)31);
+ p01re >>= nd; p01im >>= nd;
+ p12re >>= nd; p12im >>= nd;
+ p11re >>= nd; p22re >>= nd;
+ n1 = n2;
+ } else if (n1 > n2) {
+ nd = MIN(n1 - n2, (int32_t)31);
+ p02re >>= nd; p02im >>= nd;
+ }
+
+ /* calculate determinant of covariance matrix (at least 1 GB in pXX) */
+ d = MULSHIFT32(p12re, p12re) + MULSHIFT32(p12im, p12im);
+ d = MULSHIFT32(d, RELAX_COEF) << 1;
+ d = MULSHIFT32(p11re, p22re) - d;
+ ASSERT(d >= 0); /* should never be < 0 */
+
+ zFlag = 0;
+ *a0re = *a0im = 0;
+ *a1re = *a1im = 0;
+ if (d > 0) {
+ /* input = Q31 d = Q(-2*n1 - 32 + nd) = Q31 * 2^(31 + 2*n1 + 32 - nd)
+ * inverse = Q29 dInv = Q29 * 2^(-31 - 2*n1 - 32 + nd) = Q(29 + 31 + 2*n1 + 32 - nd)
+ *
+ * numerator has same Q format as d, since it's sum of normalized squares
+ * so num * inverse = Q(-2*n1 - 32) * Q(29 + 31 + 2*n1 + 32 - nd)
+ * = Q(29 + 31 - nd), drop low 32 in MULSHIFT32
+ * = Q(29 + 31 - 32 - nd) = Q(28 - nd)
+ */
+ nd = CLZ(d) - 1;
+ d <<= nd;
+ dInv = InvRNormalized(d);
+
+ /* 1 GB in pXX */
+ tre = MULSHIFT32(p01re, p12re) - MULSHIFT32(p01im, p12im) - MULSHIFT32(p02re, p11re);
+ tre = MULSHIFT32(tre, dInv);
+ tim = MULSHIFT32(p01re, p12im) + MULSHIFT32(p01im, p12re) - MULSHIFT32(p02im, p11re);
+ tim = MULSHIFT32(tim, dInv);
+
+ /* if d is extremely small, just set coefs to 0 (would have poor precision anyway) */
+ if (nd > 28 || (FASTABS(tre) >> (28 - nd)) >= 4 || (FASTABS(tim) >> (28 - nd)) >= 4) {
+ zFlag = 1;
+ } else {
+ *a1re = tre << (FBITS_LPCOEFS - 28 + nd); /* i.e. convert Q(28 - nd) to Q(29) */
+ *a1im = tim << (FBITS_LPCOEFS - 28 + nd);
+ }
+ }
+
+ if (p11re) {
+ /* input = Q31 p11re = Q(-n1 + nd) = Q31 * 2^(31 + n1 - nd)
+ * inverse = Q29 dInv = Q29 * 2^(-31 - n1 + nd) = Q(29 + 31 + n1 - nd)
+ *
+ * numerator is Q(-n1 - 3)
+ * so num * inverse = Q(-n1 - 3) * Q(29 + 31 + n1 - nd)
+ * = Q(29 + 31 - 3 - nd), drop low 32 in MULSHIFT32
+ * = Q(29 + 31 - 3 - 32 - nd) = Q(25 - nd)
+ */
+ nd = CLZ(p11re) - 1; /* assume positive */
+ p11re <<= nd;
+ dInv = InvRNormalized(p11re);
+
+ /* a1re, a1im = Q29, so scaled by (n1 + 3) */
+ tre = (p01re >> 3) + MULSHIFT32(p12re, *a1re) + MULSHIFT32(p12im, *a1im);
+ tre = -MULSHIFT32(tre, dInv);
+ tim = (p01im >> 3) - MULSHIFT32(p12im, *a1re) + MULSHIFT32(p12re, *a1im);
+ tim = -MULSHIFT32(tim, dInv);
+
+ if (nd > 25 || (FASTABS(tre) >> (25 - nd)) >= 4 || (FASTABS(tim) >> (25 - nd)) >= 4) {
+ zFlag = 1;
+ } else {
+ *a0re = tre << (FBITS_LPCOEFS - 25 + nd); /* i.e. convert Q(25 - nd) to Q(29) */
+ *a0im = tim << (FBITS_LPCOEFS - 25 + nd);
+ }
+ }
+
+ /* see 4.6.18.6.2 - if magnitude of a0 or a1 >= 4 then a0 = a1 = 0
+ * i.e. a0re < 4, a0im < 4, a1re < 4, a1im < 4
+ * Q29*Q29 = Q26
+ */
+ if (zFlag || MULSHIFT32(*a0re, *a0re) + MULSHIFT32(*a0im, *a0im) >= MAG_16 || MULSHIFT32(*a1re, *a1re) + MULSHIFT32(*a1im, *a1im) >= MAG_16) {
+ *a0re = *a0im = 0;
+ *a1re = *a1im = 0;
+ }
+
+ /* no need to clip - we never changed the XBuf data, just used it to calculate a0 and a1 */
+ if (gb < 3) {
+ nd = 3 - gb;
+ for (n1 = (NUM_TIME_SLOTS*SAMPLES_PER_SLOT + 6 + 2); n1 != 0; n1--) {
+ XBuf[0] <<= nd; XBuf[1] <<= nd;
+ XBuf += (2*64);
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: GenerateHighFreq
+ *
+ * Description: generate high frequencies with SBR (4.6.18.6)
+ *
+ * Inputs: initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ *
+ * Outputs: new high frequency samples starting at frequency kStart
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void GenerateHighFreq(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch) {
+
+ int32_t band, newBW, c, t, gb, gbMask, gbIdx;
+ int32_t currPatch, p, x, k, g, i, iStart, iEnd, bw, bwsq;
+ int32_t a0re, a0im, a1re, a1im;
+ int32_t x1re, x1im, x2re, x2im;
+ int32_t ACCre, ACCim;
+ int32_t *XBufLo, *XBufHi;
+ (void) ch;
+
+ /* calculate array of chirp factors */
+ for (band = 0; band < sbrFreq->numNoiseFloorBands; band++) {
+ c = sbrChan->chirpFact[band]; /* previous (bwArray') */
+ newBW = newBWTab[sbrChan->invfMode[0][band]][sbrChan->invfMode[1][band]];
+
+ /* weighted average of new and old (can't overflow - total gain = 1.0) */
+ if (newBW < c)
+ t = MULSHIFT32(newBW, 0x60000000) + MULSHIFT32(0x20000000, c); /* new is smaller: 0.75*new + 0.25*old */
+ else
+ t = MULSHIFT32(newBW, 0x74000000) + MULSHIFT32(0x0c000000, c); /* new is larger: 0.90625*new + 0.09375*old */
+ t <<= 1;
+
+ if (t < 0x02000000) /* below 0.015625, clip to 0 */
+ t = 0;
+ if (t > 0x7f800000) /* clip to 0.99609375 */
+ t = 0x7f800000;
+
+ /* save curr as prev for next time */
+ sbrChan->chirpFact[band] = t;
+ sbrChan->invfMode[0][band] = sbrChan->invfMode[1][band];
+ }
+
+ iStart = sbrGrid->envTimeBorder[0] + HF_ADJ;
+ iEnd = sbrGrid->envTimeBorder[sbrGrid->numEnv] + HF_ADJ;
+
+ /* generate new high freqs from low freqs, patches, and chirp factors */
+ k = sbrFreq->kStart;
+ g = 0;
+ bw = sbrChan->chirpFact[g];
+ bwsq = MULSHIFT32(bw, bw) << 1;
+
+ gbMask = (sbrChan->gbMask[0] | sbrChan->gbMask[1]); /* older 32 | newer 8 */
+ gb = CLZ(gbMask) - 1;
+
+ for (currPatch = 0; currPatch < sbrFreq->numPatches; currPatch++) {
+ for (x = 0; x < sbrFreq->patchNumSubbands[currPatch]; x++) {
+ /* map k to corresponding noise floor band */
+ if (k >= sbrFreq->freqNoise[g+1]) {
+ g++;
+ bw = sbrChan->chirpFact[g]; /* Q31 */
+ bwsq = MULSHIFT32(bw, bw) << 1; /* Q31 */
+ }
+
+ p = sbrFreq->patchStartSubband[currPatch] + x; /* low QMF band */
+ XBufHi = m_PSInfoSBR->XBuf[iStart][k];
+ if (bw) {
+ CalcLPCoefs(m_PSInfoSBR->XBuf[0][p], &a0re, &a0im, &a1re, &a1im, gb);
+
+ a0re = MULSHIFT32(bw, a0re); /* Q31 * Q29 = Q28 */
+ a0im = MULSHIFT32(bw, a0im);
+ a1re = MULSHIFT32(bwsq, a1re);
+ a1im = MULSHIFT32(bwsq, a1im);
+
+ XBufLo = m_PSInfoSBR->XBuf[iStart-2][p];
+
+ x2re = XBufLo[0]; /* RE{XBuf[n-2]} */
+ x2im = XBufLo[1]; /* IM{XBuf[n-2]} */
+ XBufLo += (64*2);
+
+ x1re = XBufLo[0]; /* RE{XBuf[n-1]} */
+ x1im = XBufLo[1]; /* IM{XBuf[n-1]} */
+ XBufLo += (64*2);
+
+ for (i = iStart; i < iEnd; i++) {
+ /* a0re/im, a1re/im are Q28 with at least 1 GB,
+ * so the summing for AACre/im is fine (1 GB in, plus 1 from MULSHIFT32)
+ */
+ ACCre = MULSHIFT32(x2re, a1re) - MULSHIFT32(x2im, a1im);
+ ACCim = MULSHIFT32(x2re, a1im) + MULSHIFT32(x2im, a1re);
+ x2re = x1re;
+ x2im = x1im;
+
+ ACCre += MULSHIFT32(x1re, a0re) - MULSHIFT32(x1im, a0im);
+ ACCim += MULSHIFT32(x1re, a0im) + MULSHIFT32(x1im, a0re);
+ x1re = XBufLo[0]; /* RE{XBuf[n]} */
+ x1im = XBufLo[1]; /* IM{XBuf[n]} */
+ XBufLo += (64*2);
+
+ /* lost 4 fbits when scaling by a0re/im, a1re/im (Q28) */
+ ACCre = CLIP_2N_SHIFT30(ACCre, 4);
+ ACCre += x1re;
+ ACCim = CLIP_2N_SHIFT30(ACCim, 4);
+ ACCim += x1im;
+
+ XBufHi[0] = ACCre;
+ XBufHi[1] = ACCim;
+ XBufHi += (64*2);
+
+ /* update guard bit masks */
+ gbMask = FASTABS(ACCre);
+ gbMask |= FASTABS(ACCim);
+ gbIdx = (i >> 5) & 0x01; /* 0 if i < 32, 1 if i >= 32 */
+ sbrChan->gbMask[gbIdx] |= gbMask;
+ }
+ } else {
+ XBufLo = (int32_t *)m_PSInfoSBR->XBuf[iStart][p];
+ for (i = iStart; i < iEnd; i++) {
+ XBufHi[0] = XBufLo[0];
+ XBufHi[1] = XBufLo[1];
+ XBufLo += (64*2);
+ XBufHi += (64*2);
+ }
+ }
+ k++; /* high QMF band */
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: DecodeHuffmanScalar
+ *
+ * Description: decode one Huffman symbol from bitstream
+ *
+ * Inputs: pointers to Huffman table and info struct
+ * left-aligned bit buffer with >= huffTabInfo->maxBits bits
+ *
+ * Outputs: decoded symbol in *val
+ *
+ * Return: number of bits in symbol
+ *
+ * Notes: assumes canonical Huffman codes:
+ * first CW always 0, we have "count" CW's of length "nBits" bits
+ * starting CW for codes of length nBits+1 =
+ * (startCW[nBits] + count[nBits]) << 1
+ * if there are no codes at nBits, then we just keep << 1 each time
+ * (since count[nBits] = 0)
+ **********************************************************************************************************************/
+int32_t DecodeHuffmanScalar(const int16_t *huffTab, const HuffInfo_t *huffTabInfo, uint32_t bitBuf,
+ int32_t *val) {
+
+ uint32_t count, start, shift, t;
+ const uint8_t *countPtr;
+ const int16_t *map;
+
+ map = huffTab + huffTabInfo->offset;
+ countPtr = huffTabInfo->count;
+
+ start = 0;
+ count = 0;
+ shift = 32;
+ do {
+ start += count;
+ start <<= 1;
+ map += count;
+ count = *countPtr++;
+ shift--;
+ t = (bitBuf >> shift) - start;
+ } while(t >= count);
+
+ *val = (int32_t) map[t];
+ return (countPtr - huffTabInfo->count);
+}
+/***********************************************************************************************************************
+ * Function: DecodeOneSymbol
+ *
+ * Description: dequantize one Huffman symbol from bitstream,
+ * using table huffTabSBR[huffTabIndex]
+ *
+ * Inputs: index of Huffman table
+ *
+ * Outputs: bitstream advanced by number of bits in codeword
+ *
+ * Return: one decoded symbol
+ **********************************************************************************************************************/
+int32_t DecodeOneSymbol(int32_t huffTabIndex) {
+
+ int32_t nBits, val;
+ uint32_t bitBuf;
+ const HuffInfo_t *hi;
+
+ hi = &(huffTabSBRInfo[huffTabIndex]);
+
+ bitBuf = GetBitsNoAdvance(hi->maxBits) << (32 - hi->maxBits);
+ nBits = DecodeHuffmanScalar(huffTabSBR, hi, bitBuf, &val);
+ AdvanceBitstream(nBits);
+
+ return val;
+}
+
+/* [1.0, sqrt(2)], format = Q29 (one guard bit for decoupling) */
+static const int32_t envDQTab[2] PROGMEM = {0x20000000, 0x2d413ccc};
+
+/***********************************************************************************************************************
+ * Function: DequantizeEnvelope
+ *
+ * Description: dequantize envelope scalefactors
+ *
+ * Inputs: number of scalefactors to process
+ * amplitude resolution flag for this frame (0 or 1)
+ * quantized envelope scalefactors
+ *
+ * Outputs: dequantized envelope scalefactors
+ *
+ * Return: extra int32_t bits in output (6 + expMax)
+ * in other words, output format = Q(FBITS_OUT_DQ_ENV - (6 + expMax))
+ *
+ * Notes: dequantized scalefactors have at least 2 GB
+ **********************************************************************************************************************/
+int32_t DequantizeEnvelope(int32_t nBands, int32_t ampRes, int8_t *envQuant, int32_t *envDequant) {
+
+ int32_t exp, expMax, i, scalei;
+
+ if(nBands <= 0) return 0;
+
+ /* scan for largest dequant value (do separately from envelope decoding to keep code cleaner) */
+ expMax = 0;
+ for(i = 0; i < nBands; i++) {
+ if(envQuant[i] > expMax) expMax = envQuant[i];
+ }
+
+ /* dequantized envelope gains
+ * envDequant = 64*2^(envQuant / alpha) = 2^(6 + envQuant / alpha)
+ * if ampRes == 0, alpha = 2 and range of envQuant = [0, 127]
+ * if ampRes == 1, alpha = 1 and range of envQuant = [0, 63]
+ * also if coupling is on, envDequant is scaled by something in range [0, 2]
+ * so range of envDequant = [2^6, 2^69] (no coupling), [2^6, 2^70] (with coupling)
+ *
+ * typical range (from observation) of envQuant/alpha = [0, 27] --> largest envQuant ~= 2^33
+ * output: Q(29 - (6 + expMax))
+ *
+ * reference: 14496-3:2001(E)/4.6.18.3.5 and 14496-4:200X/FPDAM8/5.6.5.1.2.1.5
+ */
+ if(ampRes) {
+ do {
+ exp = *envQuant++;
+ scalei = MIN(expMax - exp, (int32_t)31);
+ *envDequant++ = envDQTab[0] >> scalei;
+ } while(--nBands);
+
+ return (6 + expMax);
+ }
+ else {
+ expMax >>= 1;
+ do {
+ exp = *envQuant++;
+ scalei = MIN(expMax - (exp >> 1), (int32_t)31);
+ *envDequant++ = envDQTab[exp & 0x01] >> scalei;
+ } while(--nBands);
+
+ return (6 + expMax);
+ }
+
+}
+/***********************************************************************************************************************
+ * Function: DequantizeNoise
+ *
+ * Description: dequantize noise scalefactors
+ *
+ * Inputs: number of scalefactors to process
+ * quantized noise scalefactors
+ *
+ * Outputs: dequantized noise scalefactors, format = Q(FBITS_OUT_DQ_NOISE)
+ *
+ * Return: none
+ *
+ * Notes: dequantized scalefactors have at least 2 GB
+ **********************************************************************************************************************/
+void DequantizeNoise(int32_t nBands, int8_t *noiseQuant, int32_t *noiseDequant) {
+
+ int32_t exp, scalei;
+
+ if(nBands <= 0) return;
+
+ /* dequantize noise floor gains (4.6.18.3.5):
+ * noiseDequant = 2^(NOISE_FLOOR_OFFSET - noiseQuant)
+ *
+ * range of noiseQuant = [0, 30] (see 4.6.18.3.6), NOISE_FLOOR_OFFSET = 6
+ * so range of noiseDequant = [2^-24, 2^6]
+ */
+ do {
+ exp = *noiseQuant++;
+ scalei = NOISE_FLOOR_OFFSET - exp + FBITS_OUT_DQ_NOISE; /* 6 + 24 - exp, exp = [0,30] */
+
+ if(scalei < 0)
+ *noiseDequant++ = 0;
+ else if(scalei < 30)
+ *noiseDequant++ = 1 << scalei;
+ else
+ *noiseDequant++ = 0x3fffffff; /* leave 2 GB */
+
+ } while(--nBands);
+}
+/***********************************************************************************************************************
+ * Function: DecodeSBREnvelope
+ *
+ * Description: decode delta Huffman coded envelope scalefactors from bitstream
+ *
+ * Inputs: initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ *
+ * Outputs: dequantized env scalefactors for left channel (before decoupling)
+ * dequantized env scalefactors for right channel (if coupling off)
+ * or raw decoded env scalefactors for right channel (if coupling on)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodeSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch) {
+
+ int32_t huffIndexTime, huffIndexFreq, env, envStartBits, band, nBands, sf, lastEnv;
+ int32_t freqRes, freqResPrev, dShift, i;
+
+ if(m_PSInfoSBR->couplingFlag && ch) {
+ dShift = 1;
+ if(sbrGrid->ampResFrame) {
+ huffIndexTime = HuffTabSBR_tEnv30b;
+ huffIndexFreq = HuffTabSBR_fEnv30b;
+ envStartBits = 5;
+ }
+ else {
+ huffIndexTime = HuffTabSBR_tEnv15b;
+ huffIndexFreq = HuffTabSBR_fEnv15b;
+ envStartBits = 6;
+ }
+ }
+ else {
+ dShift = 0;
+ if(sbrGrid->ampResFrame) {
+ huffIndexTime = HuffTabSBR_tEnv30;
+ huffIndexFreq = HuffTabSBR_fEnv30;
+ envStartBits = 6;
+ }
+ else {
+ huffIndexTime = HuffTabSBR_tEnv15;
+ huffIndexFreq = HuffTabSBR_fEnv15;
+ envStartBits = 7;
+ }
+ }
+
+ /* range of envDataQuant[] = [0, 127] (see comments in DequantizeEnvelope() for reference) */
+ for(env = 0; env < sbrGrid->numEnv; env++) {
+ nBands = (sbrGrid->freqRes[env] ? sbrFreq->nHigh : sbrFreq->nLow);
+ freqRes = (sbrGrid->freqRes[env]);
+ freqResPrev = (env == 0 ? sbrGrid->freqResPrev : sbrGrid->freqRes[env - 1]);
+ lastEnv = (env == 0 ? sbrGrid->numEnvPrev - 1 : env - 1);
+ if(lastEnv < 0) lastEnv = 0; /* first frame */
+
+ ASSERT(nBands <= MAX_QMF_BANDS);
+
+ if(sbrChan->deltaFlagEnv[env] == 0) {
+ /* delta coding in freq */
+ sf = GetBits(envStartBits) << dShift;
+ sbrChan->envDataQuant[env][0] = sf;
+ for(band = 1; band < nBands; band++) {
+ sf = DecodeOneSymbol(huffIndexFreq) << dShift;
+ sbrChan->envDataQuant[env][band] = sf + sbrChan->envDataQuant[env][band - 1];
+ }
+ }
+ else if(freqRes == freqResPrev) {
+ /* delta coding in time - same freq resolution for both frames */
+ for(band = 0; band < nBands; band++) {
+ sf = DecodeOneSymbol(huffIndexTime) << dShift;
+ sbrChan->envDataQuant[env][band] = sf + sbrChan->envDataQuant[lastEnv][band];
+ }
+ }
+ else if(freqRes == 0 && freqResPrev == 1) {
+ /* delta coding in time - low freq resolution for new frame, high freq resolution for old frame */
+ for(band = 0; band < nBands; band++) {
+ sf = DecodeOneSymbol(huffIndexTime) << dShift;
+ sbrChan->envDataQuant[env][band] = sf;
+ for(i = 0; i < sbrFreq->nHigh; i++) {
+ if(sbrFreq->freqHigh[i] == sbrFreq->freqLow[band]) {
+ sbrChan->envDataQuant[env][band] += sbrChan->envDataQuant[lastEnv][i];
+ break;
+ }
+ }
+ }
+ }
+ else if(freqRes == 1 && freqResPrev == 0) {
+ /* delta coding in time - high freq resolution for new frame, low freq resolution for old frame */
+ for(band = 0; band < nBands; band++) {
+ sf = DecodeOneSymbol(huffIndexTime) << dShift;
+ sbrChan->envDataQuant[env][band] = sf;
+ for(i = 0; i < sbrFreq->nLow; i++) {
+ if(sbrFreq->freqLow[i] <= sbrFreq->freqHigh[band]
+ && sbrFreq->freqHigh[band] < sbrFreq->freqLow[i + 1]) {
+ sbrChan->envDataQuant[env][band] += sbrChan->envDataQuant[lastEnv][i];
+ break;
+ }
+ }
+ }
+ }
+
+ /* skip coupling channel */
+ if(ch != 1 || m_PSInfoSBR->couplingFlag != 1)
+ m_PSInfoSBR->envDataDequantScale[ch][env] = DequantizeEnvelope(nBands, sbrGrid->ampResFrame,
+ sbrChan->envDataQuant[env], m_PSInfoSBR->envDataDequant[ch][env]);
+ }
+ sbrGrid->numEnvPrev = sbrGrid->numEnv;
+ sbrGrid->freqResPrev = sbrGrid->freqRes[sbrGrid->numEnv - 1];
+}
+/***********************************************************************************************************************
+ * Function: DecodeSBRNoise
+ *
+ * Description: decode delta Huffman coded noise scalefactors from bitstream
+ *
+ * Inputs: initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel
+ * index of current channel (0 for SCE, 0 or 1 for CPE)
+ *
+ * Outputs: dequantized noise scalefactors for left channel (before decoupling)
+ * dequantized noise scalefactors for right channel (if coupling off)
+ * or raw decoded noise scalefactors for right channel (if coupling on)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void DecodeSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch) {
+
+ int32_t huffIndexTime, huffIndexFreq, noiseFloor, band, dShift, sf, lastNoiseFloor;
+
+ if(m_PSInfoSBR->couplingFlag && ch) {
+ dShift = 1;
+ huffIndexTime = HuffTabSBR_tNoise30b;
+ huffIndexFreq = HuffTabSBR_fNoise30b;
+ }
+ else {
+ dShift = 0;
+ huffIndexTime = HuffTabSBR_tNoise30;
+ huffIndexFreq = HuffTabSBR_fNoise30;
+ }
+
+ for(noiseFloor = 0; noiseFloor < sbrGrid->numNoiseFloors; noiseFloor++) {
+ lastNoiseFloor = (noiseFloor == 0 ? sbrGrid->numNoiseFloorsPrev - 1 : noiseFloor - 1);
+ if(lastNoiseFloor < 0) lastNoiseFloor = 0; /* first frame */
+
+ ASSERT(sbrFreq->numNoiseFloorBands <= MAX_QMF_BANDS);
+
+ if(sbrChan->deltaFlagNoise[noiseFloor] == 0) {
+ /* delta coding in freq */
+ sbrChan->noiseDataQuant[noiseFloor][0] = GetBits(5) << dShift;
+ for(band = 1; band < sbrFreq->numNoiseFloorBands; band++) {
+ sf = DecodeOneSymbol(huffIndexFreq) << dShift;
+ sbrChan->noiseDataQuant[noiseFloor][band] = sf + sbrChan->noiseDataQuant[noiseFloor][band - 1];
+ }
+ }
+ else {
+ /* delta coding in time */
+ for(band = 0; band < sbrFreq->numNoiseFloorBands; band++) {
+ sf = DecodeOneSymbol(huffIndexTime) << dShift;
+ sbrChan->noiseDataQuant[noiseFloor][band] = sf + sbrChan->noiseDataQuant[lastNoiseFloor][band];
+ }
+ }
+
+ /* skip coupling channel */
+ if(ch != 1 || m_PSInfoSBR->couplingFlag != 1)
+ DequantizeNoise(sbrFreq->numNoiseFloorBands, sbrChan->noiseDataQuant[noiseFloor],
+ m_PSInfoSBR->noiseDataDequant[ch][noiseFloor]);
+ }
+ sbrGrid->numNoiseFloorsPrev = sbrGrid->numNoiseFloors;
+}
+
+/* dqTabCouple[i] = 2 / (1 + 2^(12 - i)), format = Q30 */
+static const int32_t dqTabCouple[25] PROGMEM = {
+ 0x0007ff80, 0x000ffe00, 0x001ff802, 0x003fe010, 0x007f8080, 0x00fe03f8, 0x01f81f82, 0x03e0f83e,
+ 0x07878788, 0x0e38e38e, 0x1999999a, 0x2aaaaaab, 0x40000000, 0x55555555, 0x66666666, 0x71c71c72,
+ 0x78787878, 0x7c1f07c2, 0x7e07e07e, 0x7f01fc08, 0x7f807f80, 0x7fc01ff0, 0x7fe007fe, 0x7ff00200,
+ 0x7ff80080,
+};
+
+/***********************************************************************************************************************
+ * Function: UncoupleSBREnvelope
+ *
+ * Description: scale dequantized envelope scalefactors according to channel
+ * coupling rules
+ *
+ * Inputs: initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for right channel including
+ * quantized envelope scalefactors
+ *
+ * Outputs: dequantized envelope data for left channel (after decoupling)
+ * dequantized envelope data for right channel (after decoupling)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UncoupleSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR) {
+
+ int32_t env, band, nBands, scalei, E_1;
+
+ scalei = (sbrGrid->ampResFrame ? 0 : 1);
+ for(env = 0; env < sbrGrid->numEnv; env++) {
+ nBands = (sbrGrid->freqRes[env] ? sbrFreq->nHigh : sbrFreq->nLow);
+ m_PSInfoSBR->envDataDequantScale[1][env] = m_PSInfoSBR->envDataDequantScale[0][env];
+ for(band = 0; band < nBands; band++) {
+ /* clip E_1 to [0, 24] (scalefactors approach 0 or 2) */
+ E_1 = sbrChanR->envDataQuant[env][band] >> scalei;
+ if(E_1 < 0) E_1 = 0;
+ if(E_1 > 24) E_1 = 24;
+
+ /* envDataDequant[0] has 1 GB, so << by 2 is okay */
+ m_PSInfoSBR->envDataDequant[1][env][band] = MULSHIFT32(m_PSInfoSBR->envDataDequant[0][env][band],
+ dqTabCouple[24 - E_1]) << 2;
+ m_PSInfoSBR->envDataDequant[0][env][band] = MULSHIFT32(m_PSInfoSBR->envDataDequant[0][env][band],
+ dqTabCouple[E_1]) << 2;
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: UncoupleSBRNoise
+ *
+ * Description: scale dequantized noise floor scalefactors according to channel
+ * coupling rules
+ *
+ * Inputs: initialized SBRGrid struct for this channel
+ * initialized SBRFreq struct for this SCE/CPE block
+ * initialized SBRChan struct for this channel including
+ * quantized noise scalefactors
+ *
+ * Outputs: dequantized noise data for left channel (after decoupling)
+ * dequantized noise data for right channel (after decoupling)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UncoupleSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR) {
+
+ int32_t noiseFloor, band, Q_1;
+
+ for (noiseFloor = 0; noiseFloor < sbrGrid->numNoiseFloors; noiseFloor++) {
+ for (band = 0; band < sbrFreq->numNoiseFloorBands; band++) {
+ /* Q_1 should be in range [0, 24] according to 4.6.18.3.6, but check to make sure */
+ Q_1 = sbrChanR->noiseDataQuant[noiseFloor][band];
+ if (Q_1 < 0) Q_1 = 0;
+ if (Q_1 > 24) Q_1 = 24;
+
+ /* noiseDataDequant[0] has 1 GB, so << by 2 is okay */
+ m_PSInfoSBR->noiseDataDequant[1][noiseFloor][band] =
+ MULSHIFT32(m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band], dqTabCouple[24 - Q_1]) << 2;
+ m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band] =
+ MULSHIFT32(m_PSInfoSBR->noiseDataDequant[0][noiseFloor][band], dqTabCouple[Q_1]) << 2;
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapNoClip
+ *
+ * Description: apply synthesis window, do overlap-add without clipping,
+ * for winSequence LONG-LONG
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 32-bit PCM, non-interleaved
+ *
+ * Return: none
+ *
+ * Notes: use this function when the decoded PCM is going to the SBR decoder
+ **********************************************************************************************************************/
+void DecWindowOverlapNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev) {
+
+ int32_t in, w0, w1, f0, f1;
+ int32_t *buf1, *over1, *out1;
+ const int32_t *wndPrev, *wndCurr;
+
+ buf0 += (1024 >> 1);
+ buf1 = buf0 - 1;
+ out1 = out0 + 1024 - 1;
+ over1 = over0 + 1024 - 1;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+ if (winTypeCurr == winTypePrev) {
+ /* cut window loads in half since current and overlap sections use same symmetric window */
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *out0++ = in - f0;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ } else {
+ /* different windows for current and overlap parts - should still fit in registers on ARM w/o stack spill */
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *out0++ = in - f0;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ }
+}
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapLongStart
+ *
+ * Description: apply synthesis window, do overlap-add, without clipping
+ * for winSequence LONG-START
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 32-bit PCM, non-interleaved
+ *
+ * Return: none
+ *
+ * Notes: use this function when the decoded PCM is going to the SBR decoder
+ **********************************************************************************************************************/
+void DecWindowOverlapLongStartNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev) {
+
+ int32_t i, in, w0, w1, f0, f1;
+ int32_t *buf1, *over1, *out1;
+ const int32_t *wndPrev, *wndCurr;
+
+ buf0 += (1024 >> 1);
+ buf1 = buf0 - 1;
+ out1 = out0 + 1024 - 1;
+ over1 = over0 + 1024 - 1;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+ i = 448; /* 2 outputs, 2 overlaps per loop */
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *out0++ = in - f0;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ in = *buf1--;
+
+ *over1-- = 0; /* Wn = 0 for n = (2047, 2046, ... 1600) */
+ *over0++ = in >> 1; /* Wn = 1 for n = (1024, 1025, ... 1471) */
+ } while (--i);
+
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+
+ /* do 64 more loops - 2 outputs, 2 overlaps per loop */
+ do {
+ w0 = *wndPrev++;
+ w1 = *wndPrev++;
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *out0++ = in - f0;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ w0 = *wndCurr++; /* W[0], W[1], ... --> W[255], W[254], ... */
+ w1 = *wndCurr++; /* W[127], W[126], ... --> W[128], W[129], ... */
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in); /* Wn = short window for n = (1599, 1598, ... , 1536) */
+ *over0++ = MULSHIFT32(w1, in); /* Wn = short window for n = (1472, 1473, ... , 1535) */
+ } while (over0 < over1);
+}
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapLongStop
+ *
+ * Description: apply synthesis window, do overlap-add, without clipping
+ * for winSequence LONG-STOP
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 32-bit PCM, non-interleaved
+ *
+ * Return: none
+ *
+ * Notes: use this function when the decoded PCM is going to the SBR decoder
+ **********************************************************************************************************************/
+void DecWindowOverlapLongStopNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev) {
+
+ int32_t i, in, w0, w1, f0, f1;
+ int32_t *buf1, *over1, *out1;
+ const int32_t *wndPrev, *wndCurr;
+
+ buf0 += (1024 >> 1);
+ buf1 = buf0 - 1;
+ out1 = out0 + 1024 - 1;
+ over1 = over0 + 1024 - 1;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[1] : sinWindow + sinWindowOffset[1]);
+
+ i = 448; /* 2 outputs, 2 overlaps per loop */
+ do {
+ /* Wn = 0 for n = (0, 1, ... 447) */
+ /* Wn = 1 for n = (576, 577, ... 1023) */
+ in = *buf0++;
+ f1 = in >> 1; /* scale since skipping multiply by Q31 */
+
+ in = *over0;
+ *out0++ = in;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (--i);
+
+ /* do 64 more loops - 2 outputs, 2 overlaps per loop */
+ do {
+ w0 = *wndPrev++; /* W[0], W[1], ...W[63] */
+ w1 = *wndPrev++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *out0++ = in - f0;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+}
+/***********************************************************************************************************************
+ * Function: DecWindowOverlapShort
+ *
+ * Description: apply synthesis window, do overlap-add, without clipping
+ * for winSequence EIGHT-SHORT (does all 8 short blocks)
+ *
+ * Inputs: input buffer (output of type-IV DCT)
+ * overlap buffer (saved from last time)
+ * window type (sin or KBD) for input buffer
+ * window type (sin or KBD) for overlap buffer
+ *
+ * Outputs: one channel, one frame of 32-bit PCM, non-interleaved
+ *
+ * Return: none
+ *
+ * Notes: use this function when the decoded PCM is going to the SBR decoder
+ **********************************************************************************************************************/
+void DecWindowOverlapShortNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev) {
+
+ int32_t i, in, w0, w1, f0, f1;
+ int32_t *buf1, *over1, *out1;
+ const int32_t *wndPrev, *wndCurr;
+
+ wndPrev = (winTypePrev == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+ wndCurr = (winTypeCurr == 1 ? kbdWindow + kbdWindowOffset[0] : sinWindow + sinWindowOffset[0]);
+
+ /* pcm[0-447] = 0 + overlap[0-447] */
+ i = 448;
+ do {
+ f0 = *over0++;
+ f1 = *over0++;
+ *out0++ = f0;
+ *out0++ = f1;
+ i -= 2;
+ } while (i);
+
+ /* pcm[448-575] = Wp[0-127] * block0[0-127] + overlap[448-575] */
+ out1 = out0 + (128 - 1);
+ over1 = over0 + 128 - 1;
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ do {
+ w0 = *wndPrev++; /* W[0], W[1], ...W[63] */
+ w1 = *wndPrev++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *over0;
+ *out0++ = in - f0;
+
+ in = *over1;
+ *out1-- = in + f1;
+
+ w0 = *wndCurr++;
+ w1 = *wndCurr++;
+ in = *buf1--;
+
+ /* save over0/over1 for next short block, in the slots just vacated */
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+
+ /* pcm[576-703] = Wc[128-255] * block0[128-255] + Wc[0-127] * block1[0-127] + overlap[576-703]
+ * pcm[704-831] = Wc[128-255] * block1[128-255] + Wc[0-127] * block2[0-127] + overlap[704-831]
+ * pcm[832-959] = Wc[128-255] * block2[128-255] + Wc[0-127] * block3[0-127] + overlap[832-959]
+ */
+ for (i = 0; i < 3; i++) {
+ out0 += 64;
+ out1 = out0 + 128 - 1;
+ over0 += 64;
+ over1 = over0 + 128 - 1;
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ wndCurr -= 128;
+
+ do {
+ w0 = *wndCurr++; /* W[0], W[1], ...W[63] */
+ w1 = *wndCurr++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *(over0 - 128); /* from last short block */
+ in += *(over0 + 0); /* from last full frame */
+ *out0++ = in - f0;
+
+ in = *(over1 - 128); /* from last short block */
+ in += *(over1 + 0); /* from last full frame */
+ *out1-- = in + f1;
+
+ /* save over0/over1 for next short block, in the slots just vacated */
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ }
+
+ /* pcm[960-1023] = Wc[128-191] * block3[128-191] + Wc[0-63] * block4[0-63] + overlap[960-1023]
+ * over[0-63] = Wc[192-255] * block3[192-255] + Wc[64-127] * block4[64-127]
+ */
+ out0 += 64;
+ over0 -= 832; /* points at overlap[64] */
+ over1 = over0 + 128 - 1; /* points at overlap[191] */
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ wndCurr -= 128;
+ do {
+ w0 = *wndCurr++; /* W[0], W[1], ...W[63] */
+ w1 = *wndCurr++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ in = *(over0 + 768); /* from last short block */
+ in += *(over0 + 896); /* from last full frame */
+ *out0++ = in - f0;
+
+ in = *(over1 + 768); /* from last short block */
+ *(over1 - 128) = in + f1;
+
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in); /* save in overlap[128-191] */
+ *over0++ = MULSHIFT32(w1, in); /* save in overlap[64-127] */
+ } while (over0 < over1);
+
+ /* over0 now points at overlap[128] */
+
+ /* over[64-191] = Wc[128-255] * block4[128-255] + Wc[0-127] * block5[0-127]
+ * over[192-319] = Wc[128-255] * block5[128-255] + Wc[0-127] * block6[0-127]
+ * over[320-447] = Wc[128-255] * block6[128-255] + Wc[0-127] * block7[0-127]
+ * over[448-576] = Wc[128-255] * block7[128-255]
+ */
+ for (i = 0; i < 3; i++) {
+ over0 += 64;
+ over1 = over0 + 128 - 1;
+ buf0 += 64;
+ buf1 = buf0 - 1;
+ wndCurr -= 128;
+ do {
+ w0 = *wndCurr++; /* W[0], W[1], ...W[63] */
+ w1 = *wndCurr++; /* W[127], W[126], ... W[64] */
+ in = *buf0++;
+
+ f0 = MULSHIFT32(w0, in);
+ f1 = MULSHIFT32(w1, in);
+
+ /* from last short block */
+ *(over0 - 128) -= f0;
+ *(over1 - 128)+= f1;
+
+ in = *buf1--;
+ *over1-- = MULSHIFT32(w0, in);
+ *over0++ = MULSHIFT32(w1, in);
+ } while (over0 < over1);
+ }
+
+ /* over[576-1024] = 0 */
+ i = 448;
+ over0 += 64;
+ do {
+ *over0++ = 0;
+ *over0++ = 0;
+ *over0++ = 0;
+ *over0++ = 0;
+ i -= 4;
+ } while (i);
+}
+/***********************************************************************************************************************
+ * Function: PreMultiply64
+ *
+ * Description: pre-twiddle stage of 64-point DCT-IV
+ *
+ * Inputs: buffer of 64 samples
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: minimum 1 GB in, 2 GB out, gains 2 int32_t bits
+ * gbOut = gbIn + 1
+ * output is limited to sqrt(2)/2 plus GB in full GB
+ * uses 3-mul, 3-add butterflies instead of 4-mul, 2-add
+ **********************************************************************************************************************/
+void PreMultiply64(int32_t *zbuf1) {
+
+ int32_t i, ar1, ai1, ar2, ai2, z1, z2;
+ int32_t t, cms2, cps2a, sin2a, cps2b, sin2b;
+ int32_t *zbuf2;
+ const int32_t *csptr;
+
+ zbuf2 = zbuf1 + 64 - 1;
+ csptr = cos4sin4tab64;
+
+ /* whole thing should fit in registers - verify that compiler does this */
+ for (i = 64 >> 2; i != 0; i--) {
+ /* cps2 = (cos+sin), sin2 = sin, cms2 = (cos-sin) */
+ cps2a = *csptr++;
+ sin2a = *csptr++;
+ cps2b = *csptr++;
+ sin2b = *csptr++;
+
+ ar1 = *(zbuf1 + 0);
+ ai2 = *(zbuf1 + 1);
+ ai1 = *(zbuf2 + 0);
+ ar2 = *(zbuf2 - 1);
+
+ /* gain 2 ints bit from MULSHIFT32 by Q30
+ * max per-sample gain (ignoring implicit scaling) = MAX(sin(angle)+cos(angle)) = 1.414
+ * i.e. gain 1 GB since worst case is sin(angle) = cos(angle) = 0.707 (Q30), gain 2 from
+ * extra sign bits, and eat one in adding
+ */
+ t = MULSHIFT32(sin2a, ar1 + ai1);
+ z2 = MULSHIFT32(cps2a, ai1) - t;
+ cms2 = cps2a - 2*sin2a;
+ z1 = MULSHIFT32(cms2, ar1) + t;
+ *zbuf1++ = z1; /* cos*ar1 + sin*ai1 */
+ *zbuf1++ = z2; /* cos*ai1 - sin*ar1 */
+
+ t = MULSHIFT32(sin2b, ar2 + ai2);
+ z2 = MULSHIFT32(cps2b, ai2) - t;
+ cms2 = cps2b - 2*sin2b;
+ z1 = MULSHIFT32(cms2, ar2) + t;
+ *zbuf2-- = z2; /* cos*ai2 - sin*ar2 */
+ *zbuf2-- = z1; /* cos*ar2 + sin*ai2 */
+ }
+}
+/***********************************************************************************************************************
+ * Function: PostMultiply64
+ *
+ * Description: post-twiddle stage of 64-point type-IV DCT
+ *
+ * Inputs: buffer of 64 samples
+ * number of output samples to calculate
+ *
+ * Outputs: processed samples in same buffer
+ *
+ * Return: none
+ *
+ * Notes: minimum 1 GB in, 2 GB out, gains 2 int32_t bits
+ * gbOut = gbIn + 1
+ * output is limited to sqrt(2)/2 plus GB in full GB
+ * nSampsOut is rounded up to next multiple of 4, since we calculate
+ * 4 samples per loop
+ **********************************************************************************************************************/
+void PostMultiply64(int32_t *fft1, int32_t nSampsOut) {
+
+ int32_t i, ar1, ai1, ar2, ai2;
+ int32_t t, cms2, cps2, sin2;
+ int32_t *fft2;
+ const int32_t *csptr;
+
+ csptr = cos1sin1tab64;
+ fft2 = fft1 + 64 - 1;
+
+ /* load coeffs for first pass
+ * cps2 = (cos+sin)/2, sin2 = sin/2, cms2 = (cos-sin)/2
+ */
+ cps2 = *csptr++;
+ sin2 = *csptr++;
+ cms2 = cps2 - 2*sin2;
+
+ for (i = (nSampsOut + 3) >> 2; i != 0; i--) {
+ ar1 = *(fft1 + 0);
+ ai1 = *(fft1 + 1);
+ ar2 = *(fft2 - 1);
+ ai2 = *(fft2 + 0);
+
+ /* gain 2 int32_t bits (multiplying by Q30), max gain = sqrt(2) */
+ t = MULSHIFT32(sin2, ar1 + ai1);
+ *fft2-- = t - MULSHIFT32(cps2, ai1);
+ *fft1++ = t + MULSHIFT32(cms2, ar1);
+
+ cps2 = *csptr++;
+ sin2 = *csptr++;
+
+ ai2 = -ai2;
+ t = MULSHIFT32(sin2, ar2 + ai2);
+ *fft2-- = t - MULSHIFT32(cps2, ai2);
+ cms2 = cps2 - 2*sin2;
+ *fft1++ = t + MULSHIFT32(cms2, ar2);
+ }
+}
+/***********************************************************************************************************************
+ * Function: QMFAnalysisConv
+ *
+ * Description: convolution kernel for analysis QMF
+ *
+ * Inputs: pointer to coefficient table, reordered for sequential access
+ * delay buffer of size 32*10 = 320 real-valued PCM samples
+ * index for delay ring buffer (range = [0, 9])
+ *
+ * Outputs: 64 consecutive 32-bit samples
+ *
+ * Return: none
+ *
+ * Notes: this is carefully written to be efficient on ARM
+ * use the assembly code version in sbrqmfak.s when building for ARM!
+ **********************************************************************************************************************/
+void QMFAnalysisConv(int32_t *cTab, int32_t *delay, int32_t dIdx, int32_t *uBuf) {
+
+ int32_t k, dOff;
+ int32_t *cPtr0, *cPtr1;
+ U64 u64lo, u64hi;
+
+ dOff = dIdx*32 + 31;
+ cPtr0 = cTab;
+ cPtr1 = cTab + 33*5 - 1;
+
+ /* special first pass since we need to flip sign to create cTab[384], cTab[512] */
+ u64lo.w64 = 0;
+ u64hi.w64 = 0;
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, -(*cPtr1--), delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, -(*cPtr1--), delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+
+ uBuf[0] = u64lo.r.hi32;
+ uBuf[32] = u64hi.r.hi32;
+ uBuf++;
+ dOff--;
+
+ /* max gain for any sample in uBuf, after scaling by cTab, ~= 0.99
+ * so we can just sum the uBuf values with no overflow problems
+ */
+ for (k = 1; k <= 31; k++) {
+ u64lo.w64 = 0;
+ u64hi.w64 = 0;
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr0++, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64lo.w64 = MADD64(u64lo.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+ u64hi.w64 = MADD64(u64hi.w64, *cPtr1--, delay[dOff]); dOff -= 32; if (dOff < 0) {dOff += 320;}
+
+ uBuf[0] = u64lo.r.hi32;
+ uBuf[32] = u64hi.r.hi32;
+ uBuf++;
+ dOff--;
+ }
+}
+/***********************************************************************************************************************
+ * Function: QMFAnalysis
+ *
+ * Description: 32-subband analysis QMF (4.6.18.4.1)
+ *
+ * Inputs: 32 consecutive samples of decoded 32-bit PCM, format = Q(fBitsIn)
+ * delay buffer of size 32*10 = 320 PCM samples
+ * number of fraction bits in input PCM
+ * index for delay ring buffer (range = [0, 9])
+ * number of subbands to calculate (range = [0, 32])
+ *
+ * Outputs: qmfaBands complex subband samples, format = Q(FBITS_OUT_QMFA)
+ * updated delay buffer
+ * updated delay index
+ *
+ * Return: guard bit mask
+ *
+ * Notes: output stored as RE{X0}, IM{X0}, RE{X1}, IM{X1}, ... RE{X31}, IM{X31}
+ * output stored in int32_t buffer of size 64*2 = 128
+ * (zero-filled from XBuf[2*qmfaBands] to XBuf[127])
+ **********************************************************************************************************************/
+int32_t QMFAnalysis(int32_t *inbuf, int32_t *delay, int32_t *XBuf, int32_t fBitsIn, int32_t *delayIdx, int32_t qmfaBands) {
+
+ int32_t n, y, shift, gbMask;
+ int32_t *delayPtr, *uBuf, *tBuf;
+
+ /* use XBuf[128] as temp buffer for reordering */
+ uBuf = XBuf; /* first 64 samples */
+ tBuf = XBuf + 64; /* second 64 samples */
+
+ /* overwrite oldest PCM with new PCM
+ * delay[n] has 1 GB after shifting (either << or >>)
+ */
+ delayPtr = delay + (*delayIdx * 32);
+ if (fBitsIn > FBITS_IN_QMFA) {
+ shift = MIN(fBitsIn - FBITS_IN_QMFA, (int32_t)31);
+ for (n = 32; n != 0; n--) {
+ y = (*inbuf) >> shift;
+ inbuf++;
+ *delayPtr++ = y;
+ }
+ } else {
+ shift = MIN(FBITS_IN_QMFA - fBitsIn, (int32_t)30);
+ for (n = 32; n != 0; n--) {
+ y = *inbuf++;
+ y = CLIP_2N_SHIFT30(y, shift);
+ *delayPtr++ = y;
+ }
+ }
+
+ QMFAnalysisConv((int32_t *)cTabA, delay, *delayIdx, uBuf);
+
+ /* uBuf has at least 2 GB right now (1 from clipping to Q(FBITS_IN_QMFA), one from
+ * the scaling by cTab (MULSHIFT32(*delayPtr--, *cPtr++), with net gain of < 1.0)
+ */
+ tBuf[2*0 + 0] = uBuf[0];
+ tBuf[2*0 + 1] = uBuf[1];
+ for (n = 1; n < 31; n++) {
+ tBuf[2*n + 0] = -uBuf[64-n];
+ tBuf[2*n + 1] = uBuf[n+1];
+ }
+ tBuf[2*31 + 1] = uBuf[32];
+ tBuf[2*31 + 0] = -uBuf[33];
+
+ /* fast in-place DCT-IV - only need 2*qmfaBands output samples */
+ PreMultiply64(tBuf); /* 2 GB in, 3 GB out */
+ FFT32C(tBuf); /* 3 GB in, 1 GB out */
+ PostMultiply64(tBuf, qmfaBands*2); /* 1 GB in, 2 GB out */
+
+ gbMask = 0;
+ for (n = 0; n < qmfaBands; n++) {
+ XBuf[2*n+0] = tBuf[ n + 0]; /* implicit scaling of 2 in our output Q format */
+ gbMask |= FASTABS(XBuf[2*n+0]);
+ XBuf[2*n+1] = -tBuf[63 - n];
+ gbMask |= FASTABS(XBuf[2*n+1]);
+ }
+
+ /* fill top section with zeros for HF generation */
+ for ( ; n < 64; n++) {
+ XBuf[2*n+0] = 0;
+ XBuf[2*n+1] = 0;
+ }
+
+ *delayIdx = (*delayIdx == NUM_QMF_DELAY_BUFS - 1 ? 0 : *delayIdx + 1);
+
+ /* minimum of 2 GB in output */
+ return gbMask;
+}
+/***********************************************************************************************************************
+ * Function: QMFSynthesisConv
+ *
+ * Description: final convolution kernel for synthesis QMF
+ *
+ * Inputs: pointer to coefficient table, reordered for sequential access
+ * delay buffer of size 64*10 = 640 complex samples (1280 ints)
+ * index for delay ring buffer (range = [0, 9])
+ * number of QMF subbands to process (range = [0, 64])
+ * number of channels
+ *
+ * Outputs: 64 consecutive 16-bit PCM samples, interleaved by factor of nChans
+ *
+ * Return: none
+ *
+ * Notes: this is carefully written to be efficient on ARM
+ * use the assembly code version in sbrqmfsk.s when building for ARM!
+ **********************************************************************************************************************/
+void QMFSynthesisConv(int32_t *cPtr, int32_t *delay, int32_t dIdx, int16_t *outbuf, int32_t nChans) {
+
+ int32_t k, dOff0, dOff1;
+ U64 sum64;
+
+ dOff0 = (dIdx)*128;
+ dOff1 = dOff0 - 1;
+ if (dOff1 < 0)
+ dOff1 += 1280;
+
+ /* scaling note: total gain of coefs (cPtr[0]-cPtr[9] for any k) is < 2.0, so 1 GB in delay values is adequate */
+ for (k = 0; k <= 63; k++) {
+ sum64.w64 = 0;
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff0]); dOff0 -= 256; if (dOff0 < 0) {dOff0 += 1280;}
+ sum64.w64 = MADD64(sum64.w64, *cPtr++, delay[dOff1]); dOff1 -= 256; if (dOff1 < 0) {dOff1 += 1280;}
+
+ dOff0++;
+ dOff1--;
+ *outbuf = CLIPTOSHORT((sum64.r.hi32 + RND_VAL) >> FBITS_OUT_QMFS);
+ outbuf += nChans;
+ }
+}
+/***********************************************************************************************************************
+ * Function: QMFSynthesis
+ *
+ * Description: 64-subband synthesis QMF (4.6.18.4.2)
+ *
+ * Inputs: 64 consecutive complex subband QMF samples, format = Q(FBITS_IN_QMFS)
+ * delay buffer of size 64*10 = 640 complex samples (1280 ints)
+ * index for delay ring buffer (range = [0, 9])
+ * number of QMF subbands to process (range = [0, 64])
+ * number of channels
+ *
+ * Outputs: 64 consecutive 16-bit PCM samples, interleaved by factor of nChans
+ * updated delay buffer
+ * updated delay index
+ *
+ * Return: none
+ *
+ * Notes: assumes MIN_GBITS_IN_QMFS guard bits in input, either from
+ * QMFAnalysis (if upsampling only) or from MapHF (if SBR on)
+ **********************************************************************************************************************/
+void QMFSynthesis(int32_t *inbuf, int32_t *delay, int32_t *delayIdx, int32_t qmfsBands, int16_t *outbuf, int32_t nChans) {
+
+ int32_t n, a0, a1, b0, b1, dOff0, dOff1, dIdx;
+ int32_t *tBufLo, *tBufHi;
+
+ dIdx = *delayIdx;
+ tBufLo = delay + dIdx*128 + 0;
+ tBufHi = delay + dIdx*128 + 127;
+
+ /* reorder inputs to DCT-IV, only use first qmfsBands (complex) samples
+ */
+ for (n = 0; n < qmfsBands >> 1; n++) {
+ a0 = *inbuf++;
+ b0 = *inbuf++;
+ a1 = *inbuf++;
+ b1 = *inbuf++;
+ *tBufLo++ = a0;
+ *tBufLo++ = a1;
+ *tBufHi-- = b0;
+ *tBufHi-- = b1;
+ }
+ if (qmfsBands & 0x01) {
+ a0 = *inbuf++;
+ b0 = *inbuf++;
+ *tBufLo++ = a0;
+ *tBufHi-- = b0;
+ *tBufLo++ = 0;
+ *tBufHi-- = 0;
+ n++;
+ }
+ for ( ; n < 32; n++) {
+ *tBufLo++ = 0;
+ *tBufHi-- = 0;
+ *tBufLo++ = 0;
+ *tBufHi-- = 0;
+ }
+
+ tBufLo = delay + dIdx*128 + 0;
+ tBufHi = delay + dIdx*128 + 64;
+
+ /* 2 GB in, 3 GB out */
+ PreMultiply64(tBufLo);
+ PreMultiply64(tBufHi);
+
+ /* 3 GB in, 1 GB out */
+ FFT32C(tBufLo);
+ FFT32C(tBufHi);
+
+ /* 1 GB in, 2 GB out */
+ PostMultiply64(tBufLo, 64);
+ PostMultiply64(tBufHi, 64);
+
+ /* could fuse with PostMultiply64 to avoid separate pass */
+ dOff0 = dIdx*128;
+ dOff1 = dIdx*128 + 64;
+ for (n = 32; n != 0; n--) {
+ a0 = (*tBufLo++);
+ a1 = (*tBufLo++);
+ b0 = (*tBufHi++);
+ b1 = -(*tBufHi++);
+
+ delay[dOff0++] = (b0 - a0);
+ delay[dOff0++] = (b1 - a1);
+ delay[dOff1++] = (b0 + a0);
+ delay[dOff1++] = (b1 + a1);
+ }
+
+ QMFSynthesisConv((int32_t *)cTabS, delay, dIdx, outbuf, nChans);
+
+ *delayIdx = (*delayIdx == NUM_QMF_DELAY_BUFS - 1 ? 0 : *delayIdx + 1);
+}
+/***********************************************************************************************************************
+ * Function: UnpackSBRHeader
+ *
+ * Description: unpack SBR header (table 4.56)
+ *
+ * Inputs: BitStreamInfo struct pointing to start of SBR header
+ *
+ * Outputs: initialized SBRHeader struct for this SCE/CPE block
+ *
+ * Return: non-zero if frame reset is triggered, zero otherwise
+ **********************************************************************************************************************/
+int32_t UnpackSBRHeader(SBRHeader *sbrHdr) {
+
+ SBRHeader sbrHdrPrev;
+
+ /* save previous values so we know whether to reset decoder */
+ sbrHdrPrev.startFreq = sbrHdr->startFreq;
+ sbrHdrPrev.stopFreq = sbrHdr->stopFreq;
+ sbrHdrPrev.freqScale = sbrHdr->freqScale;
+ sbrHdrPrev.alterScale = sbrHdr->alterScale;
+ sbrHdrPrev.crossOverBand = sbrHdr->crossOverBand;
+ sbrHdrPrev.noiseBands = sbrHdr->noiseBands;
+
+ sbrHdr->ampRes = GetBits(1);
+ sbrHdr->startFreq = GetBits(4);
+ sbrHdr->stopFreq = GetBits(4);
+ sbrHdr->crossOverBand = GetBits(3);
+ sbrHdr->resBitsHdr = GetBits(2);
+ sbrHdr->hdrExtra1 = GetBits(1);
+ sbrHdr->hdrExtra2 = GetBits(1);
+
+ if (sbrHdr->hdrExtra1) {
+ sbrHdr->freqScale = GetBits(2);
+ sbrHdr->alterScale = GetBits(1);
+ sbrHdr->noiseBands = GetBits(2);
+ } else {
+ /* defaults */
+ sbrHdr->freqScale = 2;
+ sbrHdr->alterScale = 1;
+ sbrHdr->noiseBands = 2;
+ }
+
+ if (sbrHdr->hdrExtra2) {
+ sbrHdr->limiterBands = GetBits(2);
+ sbrHdr->limiterGains = GetBits(2);
+ sbrHdr->interpFreq = GetBits(1);
+ sbrHdr->smoothMode = GetBits(1);
+ } else {
+ /* defaults */
+ sbrHdr->limiterBands = 2;
+ sbrHdr->limiterGains = 2;
+ sbrHdr->interpFreq = 1;
+ sbrHdr->smoothMode = 1;
+ }
+ sbrHdr->count++;
+
+ /* if any of these have changed from previous frame, reset the SBR module */
+ if (sbrHdr->startFreq != sbrHdrPrev.startFreq || sbrHdr->stopFreq != sbrHdrPrev.stopFreq ||
+ sbrHdr->freqScale != sbrHdrPrev.freqScale || sbrHdr->alterScale != sbrHdrPrev.alterScale ||
+ sbrHdr->crossOverBand != sbrHdrPrev.crossOverBand || sbrHdr->noiseBands != sbrHdrPrev.noiseBands
+ )
+ return -1;
+ else
+ return 0;
+}
+
+/* cLog2[i] = ceil(log2(i)) (disregard i == 0) */
+static const uint8_t cLog2[9] = {0, 0, 1, 2, 2, 3, 3, 3, 3};
+/***********************************************************************************************************************
+ * Function: UnpackSBRGrid
+ *
+ * Description: unpack SBR grid (table 4.62)
+ *
+ * Inputs: BitStreamInfo struct pointing to start of SBR grid
+ * initialized SBRHeader struct for this SCE/CPE block
+ *
+ * Outputs: initialized SBRGrid struct for this channel
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UnpackSBRGrid(SBRHeader *sbrHdr, SBRGrid *sbrGrid) {
+
+ int32_t numEnvRaw, env, rel, pBits, border, middleBorder = 0;
+ uint8_t relBordLead[MAX_NUM_ENV], relBordTrail[MAX_NUM_ENV];
+ uint8_t relBorder0[3], relBorder1[3], relBorder[3];
+ uint8_t numRelBorder0, numRelBorder1, numRelBorder, numRelLead = 0, numRelTrail;
+ uint8_t absBordLead = 0, absBordTrail = 0, absBorder;
+
+ sbrGrid->ampResFrame = sbrHdr->ampRes;
+ sbrGrid->frameClass = GetBits(2);
+ switch(sbrGrid->frameClass){
+
+ case SBR_GRID_FIXFIX:
+ numEnvRaw = GetBits(2);
+ sbrGrid->numEnv = (1 << numEnvRaw);
+ if(sbrGrid->numEnv == 1) sbrGrid->ampResFrame = 0;
+
+ ASSERT(sbrGrid->numEnv == 1 || sbrGrid->numEnv == 2 || sbrGrid->numEnv == 4);
+
+ sbrGrid->freqRes[0] = GetBits(1);
+ for(env = 1; env < sbrGrid->numEnv; env++)
+ sbrGrid->freqRes[env] = sbrGrid->freqRes[0];
+
+ absBordLead = 0;
+ absBordTrail = NUM_TIME_SLOTS;
+ numRelLead = sbrGrid->numEnv - 1;
+ numRelTrail = 0;
+
+ /* numEnv = 1, 2, or 4 */
+ if(sbrGrid->numEnv == 1)
+ border = NUM_TIME_SLOTS / 1;
+ else if(sbrGrid->numEnv == 2)
+ border = NUM_TIME_SLOTS / 2;
+ else
+ border = NUM_TIME_SLOTS / 4;
+
+ for(rel = 0; rel < numRelLead; rel++)
+ relBordLead[rel] = border;
+
+ middleBorder = (sbrGrid->numEnv >> 1);
+
+ break;
+
+ case SBR_GRID_FIXVAR:
+ absBorder = GetBits(2) + NUM_TIME_SLOTS;
+ numRelBorder = GetBits(2);
+ sbrGrid->numEnv = numRelBorder + 1;
+ for(rel = 0; rel < numRelBorder; rel++)
+ relBorder[rel] = 2 * GetBits(2) + 2;
+
+ pBits = cLog2[sbrGrid->numEnv + 1];
+ sbrGrid->pointer = GetBits(pBits);
+
+ for(env = sbrGrid->numEnv - 1; env >= 0; env--)
+ sbrGrid->freqRes[env] = GetBits(1);
+
+ absBordLead = 0;
+ absBordTrail = absBorder;
+ numRelLead = 0;
+ numRelTrail = numRelBorder;
+
+ for(rel = 0; rel < numRelTrail; rel++)
+ relBordTrail[rel] = relBorder[rel];
+
+ if(sbrGrid->pointer > 1)
+ middleBorder = sbrGrid->numEnv + 1 - sbrGrid->pointer;
+ else
+ middleBorder = sbrGrid->numEnv - 1;
+
+ break;
+
+ case SBR_GRID_VARFIX:
+ absBorder = GetBits(2);
+ numRelBorder = GetBits(2);
+ sbrGrid->numEnv = numRelBorder + 1;
+ for(rel = 0; rel < numRelBorder; rel++)
+ relBorder[rel] = 2 * GetBits(2) + 2;
+
+ pBits = cLog2[sbrGrid->numEnv + 1];
+ sbrGrid->pointer = GetBits(pBits);
+
+ for(env = 0; env < sbrGrid->numEnv; env++)
+ sbrGrid->freqRes[env] = GetBits(1);
+
+ absBordLead = absBorder;
+ absBordTrail = NUM_TIME_SLOTS;
+ numRelLead = numRelBorder;
+ numRelTrail = 0;
+
+ for(rel = 0; rel < numRelLead; rel++)
+ relBordLead[rel] = relBorder[rel];
+
+ if(sbrGrid->pointer == 0)
+ middleBorder = 1;
+ else if(sbrGrid->pointer == 1)
+ middleBorder = sbrGrid->numEnv - 1;
+ else
+ middleBorder = sbrGrid->pointer - 1;
+
+ break;
+
+ case SBR_GRID_VARVAR:
+ absBordLead = GetBits(2); /* absBorder0 */
+ absBordTrail = GetBits(2) + NUM_TIME_SLOTS; /* absBorder1 */
+ numRelBorder0 = GetBits(2);
+ numRelBorder1 = GetBits(2);
+
+ sbrGrid->numEnv = numRelBorder0 + numRelBorder1 + 1;
+ ASSERT(sbrGrid->numEnv <= 5);
+
+ for(rel = 0; rel < numRelBorder0; rel++)
+ relBorder0[rel] = 2 * GetBits(2) + 2;
+
+ for(rel = 0; rel < numRelBorder1; rel++)
+ relBorder1[rel] = 2 * GetBits(2) + 2;
+
+ pBits = cLog2[numRelBorder0 + numRelBorder1 + 2];
+ sbrGrid->pointer = GetBits(pBits);
+
+ for(env = 0; env < sbrGrid->numEnv; env++)
+ sbrGrid->freqRes[env] = GetBits(1);
+
+ numRelLead = numRelBorder0;
+ numRelTrail = numRelBorder1;
+
+ for(rel = 0; rel < numRelLead; rel++)
+ relBordLead[rel] = relBorder0[rel];
+
+ for(rel = 0; rel < numRelTrail; rel++)
+ relBordTrail[rel] = relBorder1[rel];
+
+ if(sbrGrid->pointer > 1)
+ middleBorder = sbrGrid->numEnv + 1 - sbrGrid->pointer;
+ else
+ middleBorder = sbrGrid->numEnv - 1;
+
+ break;
+ }
+
+ /* build time border vector */
+ sbrGrid->envTimeBorder[0] = absBordLead * SAMPLES_PER_SLOT;
+
+ rel = 0;
+ border = absBordLead;
+ for(env = 1; env <= numRelLead; env++) {
+ border += relBordLead[rel++];
+ sbrGrid->envTimeBorder[env] = border * SAMPLES_PER_SLOT;
+ }
+
+ rel = 0;
+ border = absBordTrail;
+ for(env = sbrGrid->numEnv - 1; env > numRelLead; env--) {
+ border -= relBordTrail[rel++];
+ sbrGrid->envTimeBorder[env] = border * SAMPLES_PER_SLOT;
+ }
+
+ sbrGrid->envTimeBorder[sbrGrid->numEnv] = absBordTrail * SAMPLES_PER_SLOT;
+
+ if(sbrGrid->numEnv > 1) {
+ sbrGrid->numNoiseFloors = 2;
+ sbrGrid->noiseTimeBorder[0] = sbrGrid->envTimeBorder[0];
+ sbrGrid->noiseTimeBorder[1] = sbrGrid->envTimeBorder[middleBorder];
+ sbrGrid->noiseTimeBorder[2] = sbrGrid->envTimeBorder[sbrGrid->numEnv];
+ }
+ else {
+ sbrGrid->numNoiseFloors = 1;
+ sbrGrid->noiseTimeBorder[0] = sbrGrid->envTimeBorder[0];
+ sbrGrid->noiseTimeBorder[1] = sbrGrid->envTimeBorder[1];
+ }
+}
+/***********************************************************************************************************************
+ * Function: UnpackDeltaTimeFreq
+ *
+ * Description: unpack time/freq flags for delta coding of SBR envelopes (table 4.63)
+ *
+ * Inputs: BitStreamInfo struct pointing to start of dt/df flags
+ * number of envelopes
+ * number of noise floors
+ *
+ * Outputs: delta flags for envelope and noise floors
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UnpackDeltaTimeFreq(int32_t numEnv, uint8_t *deltaFlagEnv, int32_t numNoiseFloors, uint8_t *deltaFlagNoise) {
+
+ int32_t env, noiseFloor;
+
+ for (env = 0; env < numEnv; env++)
+ deltaFlagEnv[env] = GetBits(1);
+
+ for (noiseFloor = 0; noiseFloor < numNoiseFloors; noiseFloor++)
+ deltaFlagNoise[noiseFloor] = GetBits(1);
+}
+/***********************************************************************************************************************
+ * Function: UnpackInverseFilterMode
+ *
+ * Description: unpack invf flags for chirp factor calculation (table 4.64)
+ *
+ * Inputs: BitStreamInfo struct pointing to start of invf flags
+ * number of noise floor bands
+ *
+ * Outputs: invf flags for noise floor bands
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UnpackInverseFilterMode(int32_t numNoiseFloorBands, uint8_t *mode) {
+
+ int32_t n;
+
+ for (n = 0; n < numNoiseFloorBands; n++)
+ mode[n] = GetBits(2);
+}
+/***********************************************************************************************************************
+ * Function: UnpackSinusoids
+ *
+ * Description: unpack sinusoid (harmonic) flags for each SBR subband (table 4.67)
+ *
+ * Inputs: BitStreamInfo struct pointing to start of sinusoid flags
+ * number of high resolution SBR subbands (nHigh)
+ *
+ * Outputs: sinusoid flags for each SBR subband, zero-filled above nHigh
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UnpackSinusoids(int32_t nHigh, int32_t addHarmonicFlag, uint8_t *addHarmonic) {
+
+ int32_t n;
+
+ n = 0;
+ if(addHarmonicFlag) {
+ for(; n < nHigh; n++)
+ addHarmonic[n] = GetBits(1);
+ }
+
+ /* zero out unused bands */
+ for(; n < MAX_QMF_BANDS; n++)
+ addHarmonic[n] = 0;
+}
+/***********************************************************************************************************************
+ * Function: CopyCouplingGrid
+ *
+ * Description: copy grid parameters from left to right for channel coupling
+ *
+ * Inputs: initialized SBRGrid struct for left channel
+ *
+ * Outputs: initialized SBRGrid struct for right channel
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CopyCouplingGrid(SBRGrid *sbrGridLeft, SBRGrid *sbrGridRight) {
+
+ int32_t env, noiseFloor;
+
+ sbrGridRight->frameClass = sbrGridLeft->frameClass;
+ sbrGridRight->ampResFrame = sbrGridLeft->ampResFrame;
+ sbrGridRight->pointer = sbrGridLeft->pointer;
+
+ sbrGridRight->numEnv = sbrGridLeft->numEnv;
+ for(env = 0; env < sbrGridLeft->numEnv; env++) {
+ sbrGridRight->envTimeBorder[env] = sbrGridLeft->envTimeBorder[env];
+ sbrGridRight->freqRes[env] = sbrGridLeft->freqRes[env];
+ }
+ sbrGridRight->envTimeBorder[env] = sbrGridLeft->envTimeBorder[env]; /* borders are [0, numEnv] inclusive */
+
+ sbrGridRight->numNoiseFloors = sbrGridLeft->numNoiseFloors;
+ for(noiseFloor = 0; noiseFloor <= sbrGridLeft->numNoiseFloors; noiseFloor++)
+ sbrGridRight->noiseTimeBorder[noiseFloor] = sbrGridLeft->noiseTimeBorder[noiseFloor];
+
+ /* numEnvPrev, numNoiseFloorsPrev, freqResPrev are updated in DecodeSBREnvelope() and DecodeSBRNoise() */
+}
+/***********************************************************************************************************************
+ * Function: CopyCouplingInverseFilterMode
+ *
+ * Description: copy invf flags from left to right for channel coupling
+ *
+ * Inputs: invf flags for left channel
+ * number of noise floor bands
+ *
+ * Outputs: invf flags for right channel
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void CopyCouplingInverseFilterMode(int32_t numNoiseFloorBands, uint8_t *modeLeft, uint8_t *modeRight) {
+
+ int32_t band;
+
+ for(band = 0; band < numNoiseFloorBands; band++)
+ modeRight[band] = modeLeft[band];
+}
+/***********************************************************************************************************************
+ * Function: UnpackSBRSingleChannel
+ *
+ * Description: unpack sideband info (grid, delta flags, invf flags, envelope and
+ * noise floor configuration, sinusoids) for a single channel
+ *
+ * Inputs: BitStreamInfo struct pointing to start of sideband info
+ * initialized PSInfoSBR struct (after parsing SBR header and building
+ * frequency tables)
+ * base output channel (range = [0, nChans-1])
+ *
+ * Outputs: updated PSInfoSBR struct (SBRGrid and SBRChan)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UnpackSBRSingleChannel(int32_t chBase) {
+
+ int32_t bitsLeft;
+ SBRHeader *sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]);
+ SBRGrid *sbrGridL = &(m_PSInfoSBR->sbrGrid[chBase + 0]);
+ SBRFreq *sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]);
+ SBRChan *sbrChanL = &(m_PSInfoSBR->sbrChan[chBase + 0]);
+
+ m_PSInfoSBR->dataExtra = GetBits(1);
+ if(m_PSInfoSBR->dataExtra) m_PSInfoSBR->resBitsData = GetBits(4);
+
+ UnpackSBRGrid(sbrHdr, sbrGridL);
+ UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors, sbrChanL->deltaFlagNoise);
+ UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]);
+
+ DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0);
+ DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0);
+
+ sbrChanL->addHarmonicFlag[1] = GetBits(1);
+ UnpackSinusoids(sbrFreq->nHigh, sbrChanL->addHarmonicFlag[1], sbrChanL->addHarmonic[1]);
+
+ m_PSInfoSBR->extendedDataPresent = GetBits(1);
+ if(m_PSInfoSBR->extendedDataPresent) {
+ m_PSInfoSBR->extendedDataSize = GetBits(4);
+ if(m_PSInfoSBR->extendedDataSize == 15) m_PSInfoSBR->extendedDataSize += GetBits(8);
+
+ bitsLeft = 8 * m_PSInfoSBR->extendedDataSize;
+
+ /* get ID, unpack extension info, do whatever is necessary with it... */
+ while(bitsLeft > 0) {
+ GetBits(8);
+ bitsLeft -= 8;
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: UnpackSBRChannelPair
+ *
+ * Description: unpack sideband info (grid, delta flags, invf flags, envelope and
+ * noise floor configuration, sinusoids) for a channel pair
+ *
+ * Inputs: base output channel (range = [0, nChans-1])
+ *
+ * Outputs: updated PSInfoSBR struct (SBRGrid and SBRChan for both channels)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void UnpackSBRChannelPair(int32_t chBase) {
+
+ int32_t bitsLeft;
+ SBRHeader *sbrHdr = &(m_PSInfoSBR->sbrHdr[chBase]);
+ SBRGrid *sbrGridL = &(m_PSInfoSBR->sbrGrid[chBase + 0]), *sbrGridR = &(m_PSInfoSBR->sbrGrid[chBase + 1]);
+ SBRFreq *sbrFreq = &(m_PSInfoSBR->sbrFreq[chBase]);
+ SBRChan *sbrChanL = &(m_PSInfoSBR->sbrChan[chBase + 0]), *sbrChanR = &(m_PSInfoSBR->sbrChan[chBase + 1]);
+
+ m_PSInfoSBR->dataExtra = GetBits(1);
+ if(m_PSInfoSBR->dataExtra) {
+ m_PSInfoSBR->resBitsData = GetBits(4);
+ m_PSInfoSBR->resBitsData = GetBits(4);
+ }
+
+ m_PSInfoSBR->couplingFlag = GetBits(1);
+ if(m_PSInfoSBR->couplingFlag) {
+ UnpackSBRGrid(sbrHdr, sbrGridL);
+ CopyCouplingGrid(sbrGridL, sbrGridR);
+
+ UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors,
+ sbrChanL->deltaFlagNoise);
+ UnpackDeltaTimeFreq(sbrGridR->numEnv, sbrChanR->deltaFlagEnv, sbrGridR->numNoiseFloors,
+ sbrChanR->deltaFlagNoise);
+
+ UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]);
+ CopyCouplingInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1], sbrChanR->invfMode[1]);
+
+ DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0);
+ DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0);
+ DecodeSBREnvelope(sbrGridR, sbrFreq, sbrChanR, 1);
+ DecodeSBRNoise(sbrGridR, sbrFreq, sbrChanR, 1);
+
+ /* pass RIGHT sbrChan struct */
+ UncoupleSBREnvelope(sbrGridL, sbrFreq, sbrChanR);
+ UncoupleSBRNoise(sbrGridL, sbrFreq, sbrChanR);
+
+ }
+ else {
+ UnpackSBRGrid(sbrHdr, sbrGridL);
+ UnpackSBRGrid(sbrHdr, sbrGridR);
+ UnpackDeltaTimeFreq(sbrGridL->numEnv, sbrChanL->deltaFlagEnv, sbrGridL->numNoiseFloors,
+ sbrChanL->deltaFlagNoise);
+ UnpackDeltaTimeFreq(sbrGridR->numEnv, sbrChanR->deltaFlagEnv, sbrGridR->numNoiseFloors,
+ sbrChanR->deltaFlagNoise);
+ UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanL->invfMode[1]);
+ UnpackInverseFilterMode(sbrFreq->numNoiseFloorBands, sbrChanR->invfMode[1]);
+
+ DecodeSBREnvelope(sbrGridL, sbrFreq, sbrChanL, 0);
+ DecodeSBREnvelope(sbrGridR, sbrFreq, sbrChanR, 1);
+ DecodeSBRNoise(sbrGridL, sbrFreq, sbrChanL, 0);
+ DecodeSBRNoise(sbrGridR, sbrFreq, sbrChanR, 1);
+ }
+
+ sbrChanL->addHarmonicFlag[1] = GetBits(1);
+ UnpackSinusoids(sbrFreq->nHigh, sbrChanL->addHarmonicFlag[1], sbrChanL->addHarmonic[1]);
+
+ sbrChanR->addHarmonicFlag[1] = GetBits(1);
+ UnpackSinusoids(sbrFreq->nHigh, sbrChanR->addHarmonicFlag[1], sbrChanR->addHarmonic[1]);
+
+ m_PSInfoSBR->extendedDataPresent = GetBits(1);
+ if(m_PSInfoSBR->extendedDataPresent) {
+ m_PSInfoSBR->extendedDataSize = GetBits(4);
+ if(m_PSInfoSBR->extendedDataSize == 15) m_PSInfoSBR->extendedDataSize += GetBits(8);
+
+ bitsLeft = 8 * m_PSInfoSBR->extendedDataSize;
+
+ /* get ID, unpack extension info, do whatever is necessary with it... */
+ while(bitsLeft > 0) {
+ GetBits(8);
+ bitsLeft -= 8;
+ }
+ }
+}
+/***********************************************************************************************************************
+ * Function: AACGetErrorMessage
+ *
+ * Description: Gives back a description of the error, which is specified by the error code.
+ *
+ * Inputs: errCode as positive integer, which is the negated error code
+ *
+ * Return: error message as string
+ **********************************************************************************************************************/
+
+const char* AACGetErrorMessage(uint8_t errorCode){
+ const char* e = NULL;
+ int r = 0 - errorCode;
+ switch(r) {
+ case ERR_AAC_NONE: e = "NONE"; break;
+ case ERR_AAC_INDATA_UNDERFLOW: e = "INDATA_UNDERFLOW"; break;
+ case ERR_AAC_NULL_POINTER: e = "NULL_POINTER"; break;
+ case ERR_AAC_INVALID_ADTS_HEADER: e = "INVALID_ADTS_HEADER"; break;
+ case ERR_AAC_INVALID_ADIF_HEADER: e = "INVALID_ADIF_HEADER"; break;
+ case ERR_AAC_INVALID_FRAME: e = "INVALID_FRAME"; break;
+ case ERR_AAC_MPEG4_UNSUPPORTED: e = "MPEG4_UNSUPPORTED"; break;
+ case ERR_AAC_CHANNEL_MAP: e = "CHANNEL_MAP"; break;
+ case ERR_AAC_SYNTAX_ELEMENT: e = "SYNTAX_ELEMENT"; break;
+ case ERR_AAC_DEQUANT: e = "DEQUANT"; break;
+ case ERR_AAC_STEREO_PROCESS: e = "STEREO_PROCESS"; break;
+ case ERR_AAC_PNS: e = "PNS"; break;
+ case ERR_AAC_SHORT_BLOCK_DEINT: e = "SHORT_BLOCK_DEINT"; break;
+ case ERR_AAC_TNS: e = "TNS"; break;
+ case ERR_AAC_IMDCT: e = "IMDCT"; break;
+ case ERR_AAC_SBR_INIT: e = "SBR_INIT"; break;
+ case ERR_AAC_SBR_BITSTREAM: e = "SBR_BITSTREAM"; break;
+ case ERR_AAC_SBR_DATA: e = "SBR_DATA"; break;
+ case ERR_AAC_SBR_PCM_FORMAT: e = "SBR_PCM_FORMAT"; break;
+ case ERR_AAC_SBR_NCHANS_TOO_HIGH: e = "SBR_NCHANS_TOO_HIGH"; break;
+ case ERR_AAC_SBR_SINGLERATE_UNSUPPORTED: e = "BR_SINGLERATE_UNSUPPORTED"; break;
+ case ERR_AAC_NCHANS_TOO_HIGH: e = "NCHANS_TOO_HIGH"; break;
+ case ERR_AAC_RAWBLOCK_PARAMS: e = "RAWBLOCK_PARAMS"; break;
+ default: e = "ERR_UNKNOWN";
+ }
+ return e;
+}
\ No newline at end of file
diff --git a/libraries/ESP32-audioI2S/additional_info/old/Helix AAC Decoder/aac_decoder.h_ b/libraries/ESP32-audioI2S/additional_info/old/Helix AAC Decoder/aac_decoder.h_
new file mode 100644
index 0000000..b7e1e1a
--- /dev/null
+++ b/libraries/ESP32-audioI2S/additional_info/old/Helix AAC Decoder/aac_decoder.h_
@@ -0,0 +1,589 @@
+// based on helix aac decoder
+#pragma once
+//#pragma GCC optimize ("O3")
+//#pragma GCC diagnostic ignored "-Wnarrowing"
+
+#include "Arduino.h"
+
+#define AAC_ENABLE_MPEG4
+
+#if (defined CONFIG_IDF_TARGET_ESP32S3 && defined BOARD_HAS_PSRAM)
+ #define AAC_ENABLE_SBR // needs additional 60KB DRAM,
+#endif
+
+#define ASSERT(x) /* do nothing */
+
+#ifndef MAX
+#define MAX(a,b) std::max(a,b)
+#endif
+
+#ifndef MIN
+#define MIN(a,b) std::min(a,b)
+#endif
+
+
+/* AAC file format */
+enum {
+ AAC_FF_Unknown = 0, /* should be 0 on init */
+ AAC_FF_ADTS = 1,
+ AAC_FF_ADIF = 2,
+ AAC_FF_RAW = 3
+};
+
+/* syntactic element type */
+enum {
+ AAC_ID_INVALID = -1,
+ AAC_ID_SCE = 0,
+ AAC_ID_CPE = 1,
+ AAC_ID_CCE = 2,
+ AAC_ID_LFE = 3,
+ AAC_ID_DSE = 4,
+ AAC_ID_PCE = 5,
+ AAC_ID_FIL = 6,
+ AAC_ID_END = 7
+};
+
+enum {
+ ERR_AAC_NONE = 0,
+ ERR_AAC_INDATA_UNDERFLOW = -1,
+ ERR_AAC_NULL_POINTER = -2,
+ ERR_AAC_INVALID_ADTS_HEADER = -3,
+ ERR_AAC_INVALID_ADIF_HEADER = -4,
+ ERR_AAC_INVALID_FRAME = -5,
+ ERR_AAC_MPEG4_UNSUPPORTED = -6,
+ ERR_AAC_CHANNEL_MAP = -7,
+ ERR_AAC_SYNTAX_ELEMENT = -8,
+ ERR_AAC_DEQUANT = -9,
+ ERR_AAC_STEREO_PROCESS = -10,
+ ERR_AAC_PNS = -11,
+ ERR_AAC_SHORT_BLOCK_DEINT = -12,
+ ERR_AAC_TNS = -13,
+ ERR_AAC_IMDCT = -14,
+ ERR_AAC_NCHANS_TOO_HIGH = -15,
+ ERR_AAC_SBR_INIT = -16,
+ ERR_AAC_SBR_BITSTREAM = -17,
+ ERR_AAC_SBR_DATA = -18,
+ ERR_AAC_SBR_PCM_FORMAT = -19,
+ ERR_AAC_SBR_NCHANS_TOO_HIGH = -20,
+ ERR_AAC_SBR_SINGLERATE_UNSUPPORTED = -21,
+ ERR_AAC_RAWBLOCK_PARAMS = -22,
+ ERR_AAC_UNKNOWN = -9999
+};
+
+enum {
+ SBR_GRID_FIXFIX = 0,
+ SBR_GRID_FIXVAR = 1,
+ SBR_GRID_VARFIX = 2,
+ SBR_GRID_VARVAR = 3
+};
+
+enum {
+ HuffTabSBR_tEnv15 = 0,
+ HuffTabSBR_fEnv15 = 1,
+ HuffTabSBR_tEnv15b = 2,
+ HuffTabSBR_fEnv15b = 3,
+ HuffTabSBR_tEnv30 = 4,
+ HuffTabSBR_fEnv30 = 5,
+ HuffTabSBR_tEnv30b = 6,
+ HuffTabSBR_fEnv30b = 7,
+ HuffTabSBR_tNoise30 = 8,
+ HuffTabSBR_fNoise30 = 5,
+ HuffTabSBR_tNoise30b = 9,
+ HuffTabSBR_fNoise30b = 7
+};
+
+typedef struct _AACDecInfo_t {
+ /* raw decoded data, before rounding to 16-bit PCM (for postprocessing such as SBR) */
+ void *rawSampleBuf[2];
+ int32_t rawSampleBytes;
+ int32_t rawSampleFBits;
+ /* fill data (can be used for processing SBR or other extensions) */
+ uint8_t *fillBuf;
+ int32_t fillCount;
+ int32_t fillExtType;
+ int32_t prevBlockID; /* block information */
+ int32_t currBlockID;
+ int32_t currInstTag;
+ int32_t sbDeinterleaveReqd[2]; // [MAX_NCHANS_ELEM]
+ int32_t adtsBlocksLeft;
+ int32_t bitRate; /* user-accessible info */
+ int32_t nChans;
+ int32_t sampRate;
+ float compressionRatio;
+ int32_t id; /* 0: MPEG-4, 1: MPEG2 */
+ int32_t profile; /* 0: Main profile, 1: LowComplexity (LC), 2: ScalableSamplingRate (SSR), 3: reserved */
+ int32_t format;
+ int32_t sbrEnabled;
+ int32_t tnsUsed;
+ int32_t pnsUsed;
+ int32_t frameCount;
+} AACDecInfo_t;
+
+
+typedef struct _aac_BitStreamInfo_t {
+ uint8_t *bytePtr;
+ uint32_t iCache;
+ int32_t cachedBits;
+ int32_t nBytes;
+} aac_BitStreamInfo_t;
+
+typedef union _U64 {
+ int64_t w64;
+ struct {
+ uint32_t lo32;
+ int32_t hi32;
+ } r;
+} U64;
+
+typedef struct _AACFrameInfo_t {
+ int32_t bitRate;
+ int32_t nChans;
+ int32_t sampRateCore;
+ int32_t sampRateOut;
+ int32_t bitsPerSample;
+ int32_t outputSamps;
+ int32_t profile;
+ int32_t tnsUsed;
+ int32_t pnsUsed;
+} AACFrameInfo_t;
+
+typedef struct _HuffInfo_t {
+ int32_t maxBits; /* number of bits in longest codeword */
+ uint8_t count[20]; /* count[MAX_HUFF_BITS] = number of codes with length i+1 bits */
+ int32_t offset; /* offset into symbol table */
+} HuffInfo_t;
+
+typedef struct _PulseInfo_t {
+ uint8_t pulseDataPresent;
+ uint8_t numPulse;
+ uint8_t startSFB;
+ uint8_t offset[4]; // [MAX_PULSES]
+ uint8_t amp[4]; // [MAX_PULSES]
+} PulseInfo_t;
+
+typedef struct _TNSInfo_t {
+ uint8_t tnsDataPresent;
+ uint8_t numFilt[8]; // [MAX_TNS_FILTERS] max 1 filter each for 8 short windows, or 3 filters for 1 long window
+ uint8_t coefRes[8]; // [MAX_TNS_FILTERS]
+ uint8_t length[8]; // [MAX_TNS_FILTERS]
+ uint8_t order[8]; // [MAX_TNS_FILTERS]
+ uint8_t dir[8]; // [MAX_TNS_FILTERS]
+ int8_t coef[60]; // [MAX_TNS_COEFS] max 3 filters * 20 coefs for 1 long window,
+ // or 1 filter * 7 coefs for each of 8 short windows
+} TNSInfo_t;
+
+typedef struct _GainControlInfo_t {
+ uint8_t gainControlDataPresent;
+ uint8_t maxBand;
+ uint8_t adjNum[3][8]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN]
+ uint8_t alevCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST]
+ uint8_t alocCode[3][8][7]; // [MAX_GAIN_BANDS][MAX_GAIN_WIN][MAX_GAIN_ADJUST]
+} GainControlInfo_t;
+
+typedef struct _ICSInfo_t {
+ uint8_t icsResBit;
+ uint8_t winSequence;
+ uint8_t winShape;
+ uint8_t maxSFB;
+ uint8_t sfGroup;
+ uint8_t predictorDataPresent;
+ uint8_t predictorReset;
+ uint8_t predictorResetGroupNum;
+ uint8_t predictionUsed[41]; // [MAX_PRED_SFB]
+ uint8_t numWinGroup;
+ uint8_t winGroupLen[8]; // [MAX_WIN_GROUPS]
+} ICSInfo_t;
+
+typedef struct _ADTSHeader_t {
+ /* fixed */
+ uint8_t id; /* MPEG bit - should be 1 */
+ uint8_t layer; /* MPEG layer - should be 0 */
+ uint8_t protectBit; /* 0 = CRC word follows, 1 = no CRC word */
+ uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */
+ uint8_t sampRateIdx; /* sample rate index range = [0, 11] */
+ uint8_t privateBit; /* ignore */
+ uint8_t channelConfig; /* 0 = implicit, >0 = use default table */
+ uint8_t origCopy; /* 0 = copy, 1 = original */
+ uint8_t home; /* ignore */
+ /* variable */
+ uint8_t copyBit; /* 1 bit of the 72-bit copyright ID (transmitted as 1 bit per frame) */
+ uint8_t copyStart; /* 1 = this bit starts the 72-bit ID, 0 = it does not */
+ int32_t frameLength; /* length of frame */
+ int32_t bufferFull; /* number of 32-bit words left in enc buffer, 0x7FF = VBR */
+ uint8_t numRawDataBlocks; /* number of raw data blocks in frame */
+ /* CRC */
+ int32_t crcCheckWord; /* 16-bit CRC check word (present if protectBit == 0) */
+} ADTSHeader_t;
+
+typedef struct _ADIFHeader_t {
+ uint8_t copyBit; /* 0 = no copyright ID, 1 = 72-bit copyright ID follows immediately */
+ uint8_t origCopy; /* 0 = copy, 1 = original */
+ uint8_t home; /* ignore */
+ uint8_t bsType; /* bitstream type: 0 = CBR, 1 = VBR */
+ int32_t bitRate; /* bitRate: CBR = bits/sec, VBR = peak bits/frame, 0 = unknown */
+ uint8_t numPCE; /* number of program config elements (max = 16) */
+ int32_t bufferFull; /* bits left in bit reservoir */
+ uint8_t copyID[9]; /* [ADIF_COPYID_SIZE] optional 72-bit copyright ID */
+} ADIFHeader_t;
+
+/* sizeof(ProgConfigElement_t) = 82 bytes (if KEEP_PCE_COMMENTS not defined) */
+typedef struct _ProgConfigElement_t {
+ uint8_t elemInstTag; /* element instance tag */
+ uint8_t profile; /* 0 = main, 1 = LC, 2 = SSR, 3 = reserved */
+ uint8_t sampRateIdx; /* sample rate index range = [0, 11] */
+ uint8_t numFCE; /* number of front channel elements (max = 15) */
+ uint8_t numSCE; /* number of side channel elements (max = 15) */
+ uint8_t numBCE; /* number of back channel elements (max = 15) */
+ uint8_t numLCE; /* number of LFE channel elements (max = 3) */
+ uint8_t numADE; /* number of associated data elements (max = 7) */
+ uint8_t numCCE; /* number of valid channel coupling elements (max = 15) */
+ uint8_t monoMixdown; /* mono mixdown: bit 4 = present flag, bits 3-0 = element number */
+ uint8_t stereoMixdown; /* stereo mixdown: bit 4 = present flag, bits 3-0 = element number */
+ uint8_t matrixMixdown; /* bit 4 = present flag, bit 3 = unused,bits 2-1 = index, bit 0 = pseudo-surround enable */
+ uint8_t fce[15]; /* [MAX_NUM_FCE] front element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */
+ uint8_t sce[15]; /* [MAX_NUM_SCE] side element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */
+ uint8_t bce[15]; /* [MAX_NUM_BCE] back element channel pair: bit 4 = SCE/CPE flag, bits 3-0 = inst tag */
+ uint8_t lce[3]; /* [MAX_NUM_LCE] instance tag for LFE elements */
+ uint8_t ade[7]; /* [MAX_NUM_ADE] instance tag for ADE elements */
+ uint8_t cce[15]; /* [MAX_NUM_BCE] channel coupling elements: bit 4 = switching flag, bits 3-0 = inst tag */
+} ProgConfigElement_t;
+
+typedef struct _SBRHeader {
+ int32_t count;
+
+ uint8_t ampRes;
+ uint8_t startFreq;
+ uint8_t stopFreq;
+ uint8_t crossOverBand;
+ uint8_t resBitsHdr;
+ uint8_t hdrExtra1;
+ uint8_t hdrExtra2;
+
+ uint8_t freqScale;
+ uint8_t alterScale;
+ uint8_t noiseBands;
+
+ uint8_t limiterBands;
+ uint8_t limiterGains;
+ uint8_t interpFreq;
+ uint8_t smoothMode;
+} SBRHeader;
+
+/* need one SBRGrid per channel, updated every frame */
+typedef struct _SBRGrid {
+ uint8_t frameClass;
+ uint8_t ampResFrame;
+ uint8_t pointer;
+
+ uint8_t numEnv; /* L_E */
+ uint8_t envTimeBorder[5 + 1]; // [MAX_NUM_ENV+1] /* t_E */
+ uint8_t freqRes[5]; // [MAX_NUM_ENV]/* r */
+ uint8_t numNoiseFloors; /* L_Q */
+ uint8_t noiseTimeBorder[2 + 1]; // [MAX_NUM_NOISE_FLOORS+1] /* t_Q */
+
+ uint8_t numEnvPrev;
+ uint8_t numNoiseFloorsPrev;
+ uint8_t freqResPrev;
+} SBRGrid;
+
+/* need one SBRFreq per element (SCE/CPE/LFE), updated only on header reset */
+typedef struct _SBRFreq {
+ int32_t kStart; /* k_x */
+ int32_t nMaster;
+ int32_t nHigh;
+ int32_t nLow;
+ int32_t nLimiter; /* N_l */
+ int32_t numQMFBands; /* M */
+ int32_t numNoiseFloorBands; /* Nq */
+ int32_t kStartPrev;
+ int32_t numQMFBandsPrev;
+ uint8_t freqMaster[48 + 1]; // [MAX_QMF_BANDS + 1] /* not necessary to save this after derived tables are generated */
+ uint8_t freqHigh[48 + 1]; // [MAX_QMF_BANDS + 1]
+ uint8_t freqLow[48 / 2 + 1]; // [MAX_QMF_BANDS / 2 + 1] /* nLow = nHigh - (nHigh >> 1) */
+ uint8_t freqNoise[5 + 1]; // [MAX_NUM_NOISE_FLOOR_BANDS+1]
+ uint8_t freqLimiter[48 / 2 + 5];// [MAX_QMF_BANDS / 2 + MAX_NUM_PATCHES] /* max (intermediate) size = nLow + numPatches - 1 */
+
+ uint8_t numPatches;
+ uint8_t patchNumSubbands[5 + 1]; // [MAX_NUM_PATCHES + 1]
+ uint8_t patchStartSubband[5 + 1]; // [MAX_NUM_PATCHES + 1]
+} SBRFreq;
+
+typedef struct _SBRChan {
+ int32_t reset;
+ uint8_t deltaFlagEnv[5]; // [MAX_NUM_ENV]
+ uint8_t deltaFlagNoise[2]; // [MAX_NUM_NOISE_FLOORS]
+ int8_t envDataQuant[5][48]; // [MAX_NUM_ENV][MAX_QMF_BANDS] /* range = [0, 127] */
+ int8_t noiseDataQuant[2][5]; // [MAX_NUM_NOISE_FLOORS][MAX_NUM_NOISE_FLOOR_BANDS]
+
+ uint8_t invfMode[2][5]; // [2][MAX_NUM_NOISE_FLOOR_BANDS] /* invfMode[0/1][band] = prev/curr */
+ int32_t chirpFact[5]; // [MAX_NUM_NOISE_FLOOR_BANDS] /* bwArray */
+ uint8_t addHarmonicFlag[2]; /* addHarmonicFlag[0/1] = prev/curr */
+ uint8_t addHarmonic[2][64]; /* addHarmonic[0/1][band] = prev/curr */
+
+ int32_t gbMask[2]; /* gbMask[0/1] = XBuf[0-31]/XBuf[32-39] */
+ int8_t laPrev;
+
+ int32_t noiseTabIndex;
+ int32_t sinIndex;
+ int32_t gainNoiseIndex;
+ int32_t gTemp[5][48]; // [MAX_NUM_SMOOTH_COEFS][MAX_QMF_BANDS]
+ int32_t qTemp[5][48]; // [MAX_NUM_SMOOTH_COEFS][MAX_QMF_BANDS]
+
+} SBRChan;
+
+
+/* state info struct for baseline (MPEG-4 LC) decoding */
+typedef struct _PSInfoBase_t {
+ int32_t dataCount;
+ uint8_t dataBuf[510]; // [DATA_BUF_SIZE]
+ int32_t fillCount;
+ uint8_t fillBuf[269]; //[FILL_BUF_SIZE]
+ /* state information which is the same throughout whole frame */
+ int32_t nChans;
+ int32_t useImpChanMap;
+ int32_t sampRateIdx;
+ /* state information which can be overwritten by subsequent elements within frame */
+ ICSInfo_t icsInfo[2]; // [MAX_NCHANS_ELEM]
+ int32_t commonWin;
+ int16_t scaleFactors[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS]
+ uint8_t sfbCodeBook[2][15*8]; // [MAX_NCHANS_ELEM][MAX_SF_BANDS]
+ int32_t msMaskPresent;
+ uint8_t msMaskBits[(15 * 8 + 7) >> 3]; // [MAX_MS_MASK_BYTES]
+ int32_t pnsUsed[2]; // [MAX_NCHANS_ELEM]
+ int32_t pnsLastVal;
+ int32_t intensityUsed[2]; // [MAX_NCHANS_ELEM]
+// PulseInfo_t pulseInfo[2]; // [MAX_NCHANS_ELEM]
+ TNSInfo_t tnsInfo[2]; // [MAX_NCHANS_ELEM]
+ int32_t tnsLPCBuf[20]; // [MAX_TNS_ORDER]
+ int32_t tnsWorkBuf[20]; //[MAX_TNS_ORDER]
+ GainControlInfo_t gainControlInfo[2]; // [MAX_NCHANS_ELEM]
+ int32_t gbCurrent[2]; // [MAX_NCHANS_ELEM]
+ int32_t coef[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS]
+#ifdef AAC_ENABLE_SBR
+ int32_t sbrWorkBuf[2][1024]; // [MAX_NCHANS_ELEM][AAC_MAX_NSAMPS];
+#endif
+ /* state information which must be saved for each element and used in next frame */
+ int32_t overlap[2][1024]; // [AAC_MAX_NCHANS][AAC_MAX_NSAMPS]
+ int32_t prevWinShape[2]; // [AAC_MAX_NCHANS]
+} PSInfoBase_t;
+
+typedef struct _PSInfoSBR {
+ /* save for entire file */
+ int32_t frameCount;
+ int32_t sampRateIdx;
+
+ /* state info that must be saved for each channel */
+ SBRHeader sbrHdr[2];
+ SBRGrid sbrGrid[2];
+ SBRFreq sbrFreq[2];
+ SBRChan sbrChan[2];
+
+ /* temp variables, no need to save between blocks */
+ uint8_t dataExtra;
+ uint8_t resBitsData;
+ uint8_t extendedDataPresent;
+ int32_t extendedDataSize;
+
+ int8_t envDataDequantScale[2][5]; // [MAX_NCHANS_ELEM][MAX_NUM_ENV
+ int32_t envDataDequant[2][5][48]; // [MAX_NCHANS_ELEM][MAX_NUM_ENV][MAX_QMF_BANDS
+ int32_t noiseDataDequant[2][2][5]; // [MAX_NCHANS_ELEM][MAX_NUM_NOISE_FLOORS][MAX_NUM_NOISE_FLOOR_BANDS]
+
+ int32_t eCurr[48]; // [MAX_QMF_BANDS]
+ uint8_t eCurrExp[48]; // [MAX_QMF_BANDS]
+ uint8_t eCurrExpMax;
+ int8_t la;
+
+ int32_t crcCheckWord;
+ int32_t couplingFlag;
+ int32_t envBand;
+ int32_t eOMGainMax;
+ int32_t gainMax;
+ int32_t gainMaxFBits;
+ int32_t noiseFloorBand;
+ int32_t qp1Inv;
+ int32_t qqp1Inv;
+ int32_t sMapped;
+ int32_t sBand;
+ int32_t highBand;
+
+ int32_t sumEOrigMapped;
+ int32_t sumECurrGLim;
+ int32_t sumSM;
+ int32_t sumQM;
+ int32_t gLimBoost[48];
+ int32_t qmLimBoost[48];
+ int32_t smBoost[48];
+
+ int32_t smBuf[48];
+ int32_t qmLimBuf[48];
+ int32_t gLimBuf[48];
+ int32_t gLimFbits[48];
+
+ int32_t gFiltLast[48];
+ int32_t qFiltLast[48];
+
+ /* large buffers */
+ int32_t delayIdxQMFA[2]; // [AAC_MAX_NCHANS]
+ int32_t delayQMFA[2][10 * 32]; // [AAC_MAX_NCHANS][DELAY_SAMPS_QMFA]
+ int32_t delayIdxQMFS[2]; // [AAC_MAX_NCHANS]
+ int32_t delayQMFS[2][10 * 128]; // [AAC_MAX_NCHANS][DELAY_SAMPS_QMFS]
+ int32_t XBufDelay[2][8][64][2]; // [AAC_MAX_NCHANS][HF_GEN][64][2]
+ int32_t XBuf[32+8][64][2];
+} PSInfoSBR_t;
+
+bool AACDecoder_AllocateBuffers(void);
+int32_t AACFlushCodec();
+void AACDecoder_FreeBuffers(void);
+bool AACDecoder_IsInit(void);
+int32_t AACFindSyncWord(uint8_t *buf, int32_t nBytes);
+int32_t AACSetRawBlockParams(int32_t nChans, int32_t sampRateCore, int32_t profile, int32_t copyLast = 0);
+int32_t AACDecode(uint8_t *inbuf, int32_t *bytesLeft, int16_t *outbuf);
+int32_t AACGetSampRate();
+int32_t AACGetChannels();
+int32_t AACGetID(); // 0-MPEG4, 1-MPEG2
+uint8_t AACGetProfile(); // 0-Main, 1-LC, 2-SSR, 3-reserved
+uint8_t AACGetFormat(); // 0-unknown 1-ADTS 2-ADIF, 3-RAW
+int32_t AACGetBitsPerSample();
+int32_t AACGetBitrate();
+int32_t AACGetOutputSamps();
+int32_t AACGetBitrate();
+void DecodeLPCCoefs(int32_t order, int32_t res, int8_t *filtCoef, int32_t *a, int32_t *b);
+int32_t FilterRegion(int32_t size, int32_t dir, int32_t order, int32_t *audioCoef, int32_t *a, int32_t *hist);
+int32_t TNSFilter(int32_t ch);
+int32_t DecodeSingleChannelElement();
+int32_t DecodeChannelPairElement();
+int32_t DecodeLFEChannelElement();
+int32_t DecodeDataStreamElement();
+int32_t DecodeProgramConfigElement(uint8_t idx);
+int32_t DecodeFillElement();
+int32_t DecodeNextElement(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail);
+void PreMultiply(int32_t tabidx, int32_t *zbuf1);
+void PostMultiply(int32_t tabidx, int32_t *fft1);
+void PreMultiplyRescale(int32_t tabidx, int32_t *zbuf1, int32_t es);
+void PostMultiplyRescale(int32_t tabidx, int32_t *fft1, int32_t es);
+void DCT4(int32_t tabidx, int32_t *coef, int32_t gb);
+void BitReverse(int32_t *inout, int32_t tabidx);
+void R4FirstPass(int32_t *x, int32_t bg);
+void R8FirstPass(int32_t *x, int32_t bg);
+void R4Core(int32_t *x, int32_t bg, int32_t gp, int32_t *wtab);
+void R4FFT(int32_t tabidx, int32_t *x);
+void UnpackZeros(int32_t nVals, int32_t *coef);
+void UnpackQuads(int32_t cb, int32_t nVals, int32_t *coef);
+void UnpackPairsNoEsc(int32_t cb, int32_t nVals, int32_t *coef);
+void UnpackPairsEsc(int32_t cb, int32_t nVals, int32_t *coef);
+void DecodeSpectrumLong(int32_t ch);
+void DecodeSpectrumShort(int32_t ch);
+void DecWindowOverlap(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev);
+void DecWindowOverlapLongStart(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev);
+void DecWindowOverlapLongStop(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev);
+void DecWindowOverlapShort(int32_t *buf0, int32_t *over0, int16_t *pcm0, int32_t nChans, int32_t winTypeCurr, int32_t winTypePrev);
+int32_t IMDCT(int32_t ch, int32_t chOut, int16_t *outbuf);
+void DecodeICSInfo(ICSInfo_t *icsInfo, int32_t sampRateIdx);
+void DecodeSectionData(int32_t winSequence, int32_t numWinGrp, int32_t maxSFB, uint8_t *sfbCodeBook);
+int32_t DecodeOneScaleFactor();
+void DecodeScaleFactors(int32_t numWinGrp, int32_t maxSFB, int32_t globalGain, uint8_t *sfbCodeBook, int16_t *scaleFactors);
+void DecodePulseInfo(uint8_t ch);
+void DecodeTNSInfo(int32_t winSequence, TNSInfo_t *ti, int8_t *tnsCoef);
+void DecodeGainControlInfo(int32_t winSequence, GainControlInfo_t *gi);
+void DecodeICS(int32_t ch);
+int32_t DecodeNoiselessData(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail, int32_t ch);
+int32_t UnpackADTSHeader(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail);
+int32_t GetADTSChannelMapping(uint8_t *buf, int32_t bitOffset, int32_t bitsAvail);
+int32_t GetNumChannelsADIF(int32_t nPCE);
+int32_t GetSampleRateIdxADIF(int32_t nPCE);
+int32_t UnpackADIFHeader(uint8_t **buf, int32_t *bitOffset, int32_t *bitsAvail);
+int32_t SetRawBlockParams(int32_t copyLast, int32_t nChans, int32_t sampRate, int32_t profile);
+int32_t PrepareRawBlock();
+int32_t DequantBlock(int32_t *inbuf, int32_t nSamps, int32_t scale);
+int32_t AACDequantize(int32_t ch);
+int32_t DeinterleaveShortBlocks(int32_t ch);
+uint32_t Get32BitVal(uint32_t *last);
+int32_t InvRootR(int32_t r);
+int32_t ScaleNoiseVector(int32_t *coef, int32_t nVals, int32_t sf);
+void GenerateNoiseVector(int32_t *coef, int32_t *last, int32_t nVals);
+void CopyNoiseVector(int32_t *coefL, int32_t *coefR, int32_t nVals);
+int32_t PNS(int32_t ch);
+int32_t GetSampRateIdx(int32_t sampRate);
+void StereoProcessGroup(int32_t *coefL, int32_t *coefR, const uint16_t *sfbTab, int32_t msMaskPres, uint8_t *msMaskPtr,
+ int32_t msMaskOffset, int32_t maxSFB, uint8_t *cbRight, int16_t *sfRight, int32_t *gbCurrent);
+int32_t StereoProcess();
+int32_t RatioPowInv(int32_t a, int32_t b, int32_t c);
+int32_t SqrtFix(int32_t q, int32_t fBitsIn, int32_t *fBitsOut);
+int32_t InvRNormalized(int32_t r);
+void BitReverse32(int32_t *inout);
+void R8FirstPass32(int32_t *r0);
+void R4Core32(int32_t *r0);
+void FFT32C(int32_t *x);
+void CVKernel1(int32_t *XBuf, int32_t *accBuf);
+void CVKernel2(int32_t *XBuf, int32_t *accBuf);
+void SetBitstreamPointer(int32_t nBytes, uint8_t *buf);
+inline void RefillBitstreamCache();
+uint32_t GetBits(int32_t nBits);
+uint32_t GetBitsNoAdvance(int32_t nBits);
+void AdvanceBitstream(int32_t nBits);
+int32_t CalcBitsUsed(uint8_t *startBuf, int32_t startOffset);
+void ByteAlignBitstream();
+// SBR
+void InitSBRState();
+int32_t DecodeSBRBitstream(int32_t chBase);
+int32_t DecodeSBRData(int32_t chBase, int16_t *outbuf);
+int32_t FlushCodecSBR();
+void BubbleSort(uint8_t *v, int32_t nItems);
+uint8_t VMin(uint8_t *v, int32_t nItems);
+uint8_t VMax(uint8_t *v, int32_t nItems);
+int32_t CalcFreqMasterScaleZero(uint8_t *freqMaster, int32_t alterScale, int32_t k0, int32_t k2);
+int32_t CalcFreqMaster(uint8_t *freqMaster, int32_t freqScale, int32_t alterScale, int32_t k0, int32_t k2);
+int32_t CalcFreqHigh(uint8_t *freqHigh, uint8_t *freqMaster, int32_t nMaster, int32_t crossOverBand);
+int32_t CalcFreqLow(uint8_t *freqLow, uint8_t *freqHigh, int32_t nHigh);
+int32_t CalcFreqNoise(uint8_t *freqNoise, uint8_t *freqLow, int32_t nLow, int32_t kStart, int32_t k2, int32_t noiseBands);
+int32_t BuildPatches(uint8_t *patchNumSubbands, uint8_t *patchStartSubband, uint8_t *freqMaster, int32_t nMaster, int32_t k0,
+ int32_t kStart, int32_t numQMFBands, int32_t sampRateIdx);
+int32_t FindFreq(uint8_t *freq, int32_t nFreq, uint8_t val);
+void RemoveFreq(uint8_t *freq, int32_t nFreq, int32_t removeIdx);
+int32_t CalcFreqLimiter(uint8_t *freqLimiter, uint8_t *patchNumSubbands, uint8_t *freqLow, int32_t nLow, int32_t kStart,
+ int32_t limiterBands, int32_t numPatches);
+int32_t CalcFreqTables(SBRHeader *sbrHdr, SBRFreq *sbrFreq, int32_t sampRateIdx);
+void EstimateEnvelope(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int32_t env);
+int32_t GetSMapped(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t env, int32_t band, int32_t la);
+void CalcMaxGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, int32_t ch, int32_t env, int32_t lim, int32_t fbitsDQ);
+void CalcNoiseDivFactors(int32_t q, int32_t *qp1Inv, int32_t *qqp1Inv);
+void CalcComponentGains(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch, int32_t env, int32_t lim, int32_t fbitsDQ);
+void ApplyBoost(SBRFreq *sbrFreq, int32_t lim, int32_t fbitsDQ);
+void CalcGain(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch, int32_t env);
+void MapHF(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t env, int32_t hfReset);
+void AdjustHighFreq(SBRHeader *sbrHdr, SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch);
+int32_t CalcCovariance1(int32_t *XBuf, int32_t *p01reN, int32_t *p01imN, int32_t *p12reN, int32_t *p12imN, int32_t *p11reN, int32_t *p22reN);
+int32_t CalcCovariance2(int32_t *XBuf, int32_t *p02reN, int32_t *p02imN);
+void CalcLPCoefs(int32_t *XBuf, int32_t *a0re, int32_t *a0im, int32_t *a1re, int32_t *a1im, int32_t gb);
+void GenerateHighFreq(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch);
+int32_t DecodeHuffmanScalar(const int16_t *huffTab, const HuffInfo_t *huffTabInfo, uint32_t bitBuf, int32_t *val);
+int32_t DecodeOneSymbol(int32_t huffTabIndex);
+int32_t DequantizeEnvelope(int32_t nBands, int32_t ampRes, int8_t *envQuant, int32_t *envDequant);
+void DequantizeNoise(int32_t nBands, int8_t *noiseQuant, int32_t *noiseDequant);
+void DecodeSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch);
+void DecodeSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChan, int32_t ch);
+void UncoupleSBREnvelope(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR);
+void UncoupleSBRNoise(SBRGrid *sbrGrid, SBRFreq *sbrFreq, SBRChan *sbrChanR);
+void DecWindowOverlapNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev);
+void DecWindowOverlapLongStartNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev);
+void DecWindowOverlapLongStopNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev);
+void DecWindowOverlapShortNoClip(int32_t *buf0, int32_t *over0, int32_t *out0, int32_t winTypeCurr, int32_t winTypePrev);
+void PreMultiply64(int32_t *zbuf1);
+void PostMultiply64(int32_t *fft1, int32_t nSampsOut);
+void QMFAnalysisConv(int32_t *cTab, int32_t *delay, int32_t dIdx, int32_t *uBuf);
+int32_t QMFAnalysis(int32_t *inbuf, int32_t *delay, int32_t *XBuf, int32_t fBitsIn, int32_t *delayIdx, int32_t qmfaBands);
+void QMFSynthesisConv(int32_t *cPtr, int32_t *delay, int32_t dIdx, int16_t *outbuf, int32_t nChans);
+void QMFSynthesis(int32_t *inbuf, int32_t *delay, int32_t *delayIdx, int32_t qmfsBands, int16_t *outbuf, int32_t nChans);
+int32_t UnpackSBRHeader(SBRHeader *sbrHdr);
+void UnpackSBRGrid(SBRHeader *sbrHdr, SBRGrid *sbrGrid);
+void UnpackDeltaTimeFreq(int32_t numEnv, uint8_t *deltaFlagEnv, int32_t numNoiseFloors, uint8_t *deltaFlagNoise);
+void UnpackInverseFilterMode(int32_t numNoiseFloorBands, uint8_t *mode);
+void UnpackSinusoids(int32_t nHigh, int32_t addHarmonicFlag, uint8_t *addHarmonic);
+void CopyCouplingGrid(SBRGrid *sbrGridLeft, SBRGrid *sbrGridRight);
+void CopyCouplingInverseFilterMode(int32_t numNoiseFloorBands, uint8_t *modeLeft, uint8_t *modeRight);
+void UnpackSBRSingleChannel(int32_t chBase);
+void UnpackSBRChannelPair(int32_t chBase);
+
+inline uint8_t AACGetSBR(){return 0;}
+inline uint8_t AACGetParametricStereo(){return 0;}
+const char* AACGetErrorMessage(uint8_t errorCode);
diff --git a/libraries/ESP32-audioI2S/additional_info/old/Helix MP3_decoder/mp3_decoder.cpp_ b/libraries/ESP32-audioI2S/additional_info/old/Helix MP3_decoder/mp3_decoder.cpp_
new file mode 100644
index 0000000..2f0331e
--- /dev/null
+++ b/libraries/ESP32-audioI2S/additional_info/old/Helix MP3_decoder/mp3_decoder.cpp_
@@ -0,0 +1,4275 @@
+/*
+ * mp3_decoder.cpp
+ * libhelix_HMP3DECODER
+ *
+ * Created on: 26.10.2018
+ * Updated on: 09.07.2025
+ */
+#include "mp3_decoder.h"
+/* clip to range [-2^n, 2^n - 1] */
+#if 0 //Fast on ARM:
+#define CLIP_2N(y, n) { \
+ int32_t sign = (y) >> 31; \
+ if (sign != (y) >> (n)) { \
+ (y) = sign ^ ((1 << (n)) - 1); \
+ } \
+}
+#else //on xtensa this is faster, due to asm min/max instructions:
+#define CLIP_2N(y, n) { \
+ int32_t x = 1 << n; \
+ if (y < -x) y = -x; \
+ x--; \
+ if (y > x) y = x; \
+}
+#endif
+
+const uint8_t m_SYNCWORDH =0xff;
+const uint8_t m_SYNCWORDL =0xe0;
+const uint8_t m_DQ_FRACBITS_OUT =25; // number of fraction bits in output of dequant
+const uint8_t m_CSHIFT =12; // coefficients have 12 leading sign bits for early-terminating mulitplies
+const uint8_t m_SIBYTES_MPEG1_MONO =17;
+const uint8_t m_SIBYTES_MPEG1_STEREO =32;
+const uint8_t m_SIBYTES_MPEG2_MONO =9;
+const uint8_t m_SIBYTES_MPEG2_STEREO =17;
+const uint8_t m_IMDCT_SCALE =2; // additional scaling (by sqrt(2)) for fast IMDCT36
+const uint8_t m_NGRANS_MPEG1 =2;
+const uint8_t m_NGRANS_MPEG2 =1;
+const uint32_t m_SQRTHALF =0x5a82799a; // sqrt(0.5) in Q31 format
+
+
+MP3FrameInfo_t *m_MP3FrameInfo;
+SFBandTable_t m_SFBandTable;
+StereoMode_t m_sMode; /* mono/stereo mode */
+MPEGVersion_t m_MPEGVersion; /* version ID */
+FrameHeader_t *m_FrameHeader;
+SideInfoSub_t m_SideInfoSub[m_MAX_NGRAN][m_MAX_NCHAN];
+SideInfo_t *m_SideInfo;
+CriticalBandInfo_t m_CriticalBandInfo[m_MAX_NCHAN]; /* filled in dequantizer, used in joint stereo reconstruction */
+DequantInfo_t *m_DequantInfo;
+HuffmanInfo_t *m_HuffmanInfo;
+IMDCTInfo_t *m_IMDCTInfo;
+ScaleFactorInfoSub_t m_ScaleFactorInfoSub[m_MAX_NGRAN][m_MAX_NCHAN];
+ScaleFactorJS_t *m_ScaleFactorJS;
+SubbandInfo_t *m_SubbandInfo;
+MP3DecInfo_t *m_MP3DecInfo;
+
+const uint16_t huffTable[4242] PROGMEM = {
+ /* huffTable01[9] */
+ 0xf003, 0x3112, 0x3101, 0x2011, 0x2011, 0x1000, 0x1000, 0x1000, 0x1000,
+ /* huffTable02[65] */
+ 0xf006, 0x6222, 0x6201, 0x5212, 0x5212, 0x5122, 0x5122, 0x5021, 0x5021, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ /* huffTable03[65] */
+ 0xf006, 0x6222, 0x6201, 0x5212, 0x5212, 0x5122, 0x5122, 0x5021, 0x5021, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2101, 0x2101, 0x2101,
+ 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101, 0x2101,
+ 0x2101, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ /* huffTable05[257] */
+ 0xf008, 0x8332, 0x8322, 0x7232, 0x7232, 0x6132, 0x6132, 0x6132, 0x6132, 0x7312, 0x7312, 0x7301,
+ 0x7301, 0x7031, 0x7031, 0x7222, 0x7222, 0x6212, 0x6212, 0x6212, 0x6212, 0x6122, 0x6122, 0x6122,
+ 0x6122, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ /* huffTable06[129] */
+ 0xf007, 0x7332, 0x7301, 0x6322, 0x6322, 0x6232, 0x6232, 0x6031, 0x6031, 0x5312, 0x5312, 0x5312,
+ 0x5312, 0x5132, 0x5132, 0x5132, 0x5132, 0x5222, 0x5222, 0x5222, 0x5222, 0x5201, 0x5201, 0x5201,
+ 0x5201, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4122, 0x4122, 0x4122,
+ 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021, 0x4021,
+ 0x4021, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000,
+ 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000,
+ /* huffTable07[110] */
+ 0xf006, 0x0041, 0x0052, 0x005b, 0x0060, 0x0063, 0x0068, 0x006b, 0x6212, 0x5122, 0x5122, 0x6201,
+ 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf004, 0x4552, 0x4542, 0x4452, 0x4352, 0x3532, 0x3532,
+ 0x3442, 0x3442, 0x3522, 0x3522, 0x3252, 0x3252, 0x2512, 0x2512, 0x2512, 0x2512, 0xf003, 0x2152,
+ 0x2152, 0x3501, 0x3432, 0x2051, 0x2051, 0x3342, 0x3332, 0xf002, 0x2422, 0x2242, 0x1412, 0x1412,
+ 0xf001, 0x1142, 0x1041, 0xf002, 0x2401, 0x2322, 0x2232, 0x2301, 0xf001, 0x1312, 0x1132, 0xf001,
+ 0x1031, 0x1222,
+ /* huffTable08[280] */
+ 0xf008, 0x0101, 0x010a, 0x010f, 0x8512, 0x8152, 0x0112, 0x0115, 0x8422, 0x8242, 0x8412, 0x7142,
+ 0x7142, 0x8401, 0x8041, 0x8322, 0x8232, 0x8312, 0x8132, 0x8301, 0x8031, 0x6222, 0x6222, 0x6222,
+ 0x6222, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x4212, 0x4212, 0x4212,
+ 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212,
+ 0x4212, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x4122,
+ 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112,
+ 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x2112, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0xf003, 0x3552, 0x3452, 0x2542, 0x2542, 0x1352, 0x1352,
+ 0x1352, 0x1352, 0xf002, 0x2532, 0x2442, 0x1522, 0x1522, 0xf001, 0x1252, 0x1501, 0xf001, 0x1432,
+ 0x1342, 0xf001, 0x1051, 0x1332,
+ /* huffTable09[93] */
+ 0xf006, 0x0041, 0x004a, 0x004f, 0x0052, 0x0057, 0x005a, 0x6412, 0x6142, 0x6322, 0x6232, 0x5312,
+ 0x5312, 0x5132, 0x5132, 0x6301, 0x6031, 0x5222, 0x5222, 0x5201, 0x5201, 0x4212, 0x4212, 0x4212,
+ 0x4212, 0x4122, 0x4122, 0x4122, 0x4122, 0x4021, 0x4021, 0x4021, 0x4021, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3000, 0x3000, 0x3000,
+ 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0xf003, 0x3552, 0x3542, 0x2532, 0x2532, 0x2352, 0x2352,
+ 0x3452, 0x3501, 0xf002, 0x2442, 0x2522, 0x2252, 0x2512, 0xf001, 0x1152, 0x1432, 0xf002, 0x1342,
+ 0x1342, 0x2051, 0x2401, 0xf001, 0x1422, 0x1242, 0xf001, 0x1332, 0x1041,
+ /* huffTable10[320] */
+ 0xf008, 0x0101, 0x010a, 0x010f, 0x0118, 0x011b, 0x0120, 0x0125, 0x8712, 0x8172, 0x012a, 0x012d,
+ 0x0132, 0x8612, 0x8162, 0x8061, 0x0137, 0x013a, 0x013d, 0x8412, 0x8142, 0x8041, 0x8322, 0x8232,
+ 0x8301, 0x7312, 0x7312, 0x7132, 0x7132, 0x7031, 0x7031, 0x7222, 0x7222, 0x6212, 0x6212, 0x6212,
+ 0x6212, 0x6122, 0x6122, 0x6122, 0x6122, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021,
+ 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112,
+ 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf003, 0x3772, 0x3762, 0x3672, 0x3752, 0x3572, 0x3662,
+ 0x2742, 0x2742, 0xf002, 0x2472, 0x2652, 0x2562, 0x2732, 0xf003, 0x2372, 0x2372, 0x2642, 0x2642,
+ 0x3552, 0x3452, 0x2362, 0x2362, 0xf001, 0x1722, 0x1272, 0xf002, 0x2462, 0x2701, 0x1071, 0x1071,
+ 0xf002, 0x1262, 0x1262, 0x2542, 0x2532, 0xf002, 0x1601, 0x1601, 0x2352, 0x2442, 0xf001, 0x1632,
+ 0x1622, 0xf002, 0x2522, 0x2252, 0x1512, 0x1512, 0xf002, 0x1152, 0x1152, 0x2432, 0x2342, 0xf001,
+ 0x1501, 0x1051, 0xf001, 0x1422, 0x1242, 0xf001, 0x1332, 0x1401,
+ /* huffTable11[296] */
+ 0xf008, 0x0101, 0x0106, 0x010f, 0x0114, 0x0117, 0x8722, 0x8272, 0x011c, 0x7172, 0x7172, 0x8712,
+ 0x8071, 0x8632, 0x8362, 0x8061, 0x011f, 0x0122, 0x8512, 0x7262, 0x7262, 0x8622, 0x8601, 0x7612,
+ 0x7612, 0x7162, 0x7162, 0x8152, 0x8432, 0x8051, 0x0125, 0x8422, 0x8242, 0x8412, 0x8142, 0x8401,
+ 0x8041, 0x7322, 0x7322, 0x7232, 0x7232, 0x6312, 0x6312, 0x6312, 0x6312, 0x6132, 0x6132, 0x6132,
+ 0x6132, 0x7301, 0x7301, 0x7031, 0x7031, 0x6222, 0x6222, 0x6222, 0x6222, 0x5122, 0x5122, 0x5122,
+ 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212,
+ 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x5201, 0x5201, 0x5201,
+ 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x5021,
+ 0x5021, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0x2000,
+ 0x2000, 0x2000, 0x2000, 0x2000, 0x2000, 0xf002, 0x2772, 0x2762, 0x2672, 0x2572, 0xf003, 0x2662,
+ 0x2662, 0x2742, 0x2742, 0x2472, 0x2472, 0x3752, 0x3552, 0xf002, 0x2652, 0x2562, 0x1732, 0x1732,
+ 0xf001, 0x1372, 0x1642, 0xf002, 0x2542, 0x2452, 0x2532, 0x2352, 0xf001, 0x1462, 0x1701, 0xf001,
+ 0x1442, 0x1522, 0xf001, 0x1252, 0x1501, 0xf001, 0x1342, 0x1332,
+ /* huffTable12[185] */
+ 0xf007, 0x0081, 0x008a, 0x008f, 0x0092, 0x0097, 0x009a, 0x009d, 0x00a2, 0x00a5, 0x00a8, 0x7622,
+ 0x7262, 0x7162, 0x00ad, 0x00b0, 0x00b3, 0x7512, 0x7152, 0x7432, 0x7342, 0x00b6, 0x7422, 0x7242,
+ 0x7412, 0x6332, 0x6332, 0x6142, 0x6142, 0x6322, 0x6322, 0x6232, 0x6232, 0x7041, 0x7301, 0x6031,
+ 0x6031, 0x5312, 0x5312, 0x5312, 0x5312, 0x5132, 0x5132, 0x5132, 0x5132, 0x5222, 0x5222, 0x5222,
+ 0x5222, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4212, 0x4122, 0x4122, 0x4122,
+ 0x4122, 0x4122, 0x4122, 0x4122, 0x4122, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021,
+ 0x5021, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3101,
+ 0x3101, 0x3101, 0x3101, 0x3101, 0x3101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0xf003, 0x3772, 0x3762,
+ 0x2672, 0x2672, 0x2752, 0x2752, 0x2572, 0x2572, 0xf002, 0x2662, 0x2742, 0x2472, 0x2562, 0xf001,
+ 0x1652, 0x1732, 0xf002, 0x2372, 0x2552, 0x1722, 0x1722, 0xf001, 0x1272, 0x1642, 0xf001, 0x1462,
+ 0x1712, 0xf002, 0x1172, 0x1172, 0x2701, 0x2071, 0xf001, 0x1632, 0x1362, 0xf001, 0x1542, 0x1452,
+ 0xf002, 0x1442, 0x1442, 0x2601, 0x2501, 0xf001, 0x1612, 0x1061, 0xf001, 0x1532, 0x1352, 0xf001,
+ 0x1522, 0x1252, 0xf001, 0x1051, 0x1401,
+ /* huffTable13[497] */
+ 0xf006, 0x0041, 0x0082, 0x00c3, 0x00e4, 0x0105, 0x0116, 0x011f, 0x0130, 0x0139, 0x013e, 0x0143,
+ 0x0146, 0x6212, 0x6122, 0x6201, 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf006, 0x0108, 0x0111, 0x011a, 0x0123, 0x012c, 0x0131,
+ 0x0136, 0x013f, 0x0144, 0x0147, 0x014c, 0x0151, 0x0156, 0x015b, 0x6f12, 0x61f2, 0x60f1, 0x0160,
+ 0x0163, 0x0166, 0x62e2, 0x0169, 0x6e12, 0x61e2, 0x016c, 0x016f, 0x0172, 0x0175, 0x0178, 0x017b,
+ 0x66c2, 0x6d32, 0x017e, 0x6d22, 0x62d2, 0x6d12, 0x67b2, 0x0181, 0x0184, 0x63c2, 0x0187, 0x6b42,
+ 0x51d2, 0x51d2, 0x6d01, 0x60d1, 0x6a82, 0x68a2, 0x6c42, 0x64c2, 0x6b62, 0x66b2, 0x5c32, 0x5c32,
+ 0x5c22, 0x5c22, 0x52c2, 0x52c2, 0x5b52, 0x5b52, 0x65b2, 0x6982, 0x5c12, 0x5c12, 0xf006, 0x51c2,
+ 0x51c2, 0x6892, 0x6c01, 0x50c1, 0x50c1, 0x64b2, 0x6a62, 0x66a2, 0x6972, 0x5b32, 0x5b32, 0x53b2,
+ 0x53b2, 0x6882, 0x6a52, 0x5b22, 0x5b22, 0x65a2, 0x6962, 0x54a2, 0x54a2, 0x6872, 0x6782, 0x5492,
+ 0x5492, 0x6772, 0x6672, 0x42b2, 0x42b2, 0x42b2, 0x42b2, 0x4b12, 0x4b12, 0x4b12, 0x4b12, 0x41b2,
+ 0x41b2, 0x41b2, 0x41b2, 0x5b01, 0x5b01, 0x50b1, 0x50b1, 0x5692, 0x5692, 0x5a42, 0x5a42, 0x5a32,
+ 0x5a32, 0x53a2, 0x53a2, 0x5952, 0x5952, 0x5592, 0x5592, 0x4a22, 0x4a22, 0x4a22, 0x4a22, 0x42a2,
+ 0x42a2, 0x42a2, 0x42a2, 0xf005, 0x4a12, 0x4a12, 0x41a2, 0x41a2, 0x5a01, 0x5862, 0x40a1, 0x40a1,
+ 0x5682, 0x5942, 0x4392, 0x4392, 0x5932, 0x5852, 0x5582, 0x5762, 0x4922, 0x4922, 0x4292, 0x4292,
+ 0x5752, 0x5572, 0x4832, 0x4832, 0x4382, 0x4382, 0x5662, 0x5742, 0x5472, 0x5652, 0x5562, 0x5372,
+ 0xf005, 0x3912, 0x3912, 0x3912, 0x3912, 0x3192, 0x3192, 0x3192, 0x3192, 0x4901, 0x4901, 0x4091,
+ 0x4091, 0x4842, 0x4842, 0x4482, 0x4482, 0x4272, 0x4272, 0x5642, 0x5462, 0x3822, 0x3822, 0x3822,
+ 0x3822, 0x3282, 0x3282, 0x3282, 0x3282, 0x3812, 0x3812, 0x3812, 0x3812, 0xf004, 0x4732, 0x4722,
+ 0x3712, 0x3712, 0x3172, 0x3172, 0x4552, 0x4701, 0x4071, 0x4632, 0x4362, 0x4542, 0x4452, 0x4622,
+ 0x4262, 0x4532, 0xf003, 0x2182, 0x2182, 0x3801, 0x3081, 0x3612, 0x3162, 0x3601, 0x3061, 0xf004,
+ 0x4352, 0x4442, 0x3522, 0x3522, 0x3252, 0x3252, 0x3501, 0x3501, 0x2512, 0x2512, 0x2512, 0x2512,
+ 0x2152, 0x2152, 0x2152, 0x2152, 0xf003, 0x3432, 0x3342, 0x3051, 0x3422, 0x3242, 0x3332, 0x2412,
+ 0x2412, 0xf002, 0x1142, 0x1142, 0x2401, 0x2041, 0xf002, 0x2322, 0x2232, 0x1312, 0x1312, 0xf001,
+ 0x1132, 0x1301, 0xf001, 0x1031, 0x1222, 0xf003, 0x0082, 0x008b, 0x008e, 0x0091, 0x0094, 0x0097,
+ 0x3ce2, 0x3dd2, 0xf003, 0x0093, 0x3eb2, 0x3be2, 0x3f92, 0x39f2, 0x3ae2, 0x3db2, 0x3bd2, 0xf003,
+ 0x3f82, 0x38f2, 0x3cc2, 0x008d, 0x3e82, 0x0090, 0x27f2, 0x27f2, 0xf003, 0x2ad2, 0x2ad2, 0x3da2,
+ 0x3cb2, 0x3bc2, 0x36f2, 0x2f62, 0x2f62, 0xf002, 0x28e2, 0x2f52, 0x2d92, 0x29d2, 0xf002, 0x25f2,
+ 0x27e2, 0x2ca2, 0x2bb2, 0xf003, 0x2f42, 0x2f42, 0x24f2, 0x24f2, 0x3ac2, 0x36e2, 0x23f2, 0x23f2,
+ 0xf002, 0x1f32, 0x1f32, 0x2d82, 0x28d2, 0xf001, 0x1f22, 0x12f2, 0xf002, 0x2e62, 0x2c92, 0x1f01,
+ 0x1f01, 0xf002, 0x29c2, 0x2e52, 0x1ba2, 0x1ba2, 0xf002, 0x2d72, 0x27d2, 0x1e42, 0x1e42, 0xf002,
+ 0x28c2, 0x26d2, 0x1e32, 0x1e32, 0xf002, 0x19b2, 0x19b2, 0x2b92, 0x2aa2, 0xf001, 0x1ab2, 0x15e2,
+ 0xf001, 0x14e2, 0x1c82, 0xf001, 0x1d62, 0x13e2, 0xf001, 0x1e22, 0x1e01, 0xf001, 0x10e1, 0x1d52,
+ 0xf001, 0x15d2, 0x1c72, 0xf001, 0x17c2, 0x1d42, 0xf001, 0x1b82, 0x18b2, 0xf001, 0x14d2, 0x1a92,
+ 0xf001, 0x19a2, 0x1c62, 0xf001, 0x13d2, 0x1b72, 0xf001, 0x1c52, 0x15c2, 0xf001, 0x1992, 0x1a72,
+ 0xf001, 0x17a2, 0x1792, 0xf003, 0x0023, 0x3df2, 0x2de2, 0x2de2, 0x1ff2, 0x1ff2, 0x1ff2, 0x1ff2,
+ 0xf001, 0x1fe2, 0x1fd2, 0xf001, 0x1ee2, 0x1fc2, 0xf001, 0x1ed2, 0x1fb2, 0xf001, 0x1bf2, 0x1ec2,
+ 0xf002, 0x1cd2, 0x1cd2, 0x2fa2, 0x29e2, 0xf001, 0x1af2, 0x1dc2, 0xf001, 0x1ea2, 0x1e92, 0xf001,
+ 0x1f72, 0x1e72, 0xf001, 0x1ef2, 0x1cf2,
+ /* huffTable15[580] */
+ 0xf008, 0x0101, 0x0122, 0x0143, 0x0154, 0x0165, 0x0176, 0x017f, 0x0188, 0x0199, 0x01a2, 0x01ab,
+ 0x01b4, 0x01bd, 0x01c2, 0x01cb, 0x01d4, 0x01d9, 0x01de, 0x01e3, 0x01e8, 0x01ed, 0x01f2, 0x01f7,
+ 0x01fc, 0x0201, 0x0204, 0x0207, 0x020a, 0x020f, 0x0212, 0x0215, 0x021a, 0x021d, 0x0220, 0x8192,
+ 0x0223, 0x0226, 0x0229, 0x022c, 0x022f, 0x8822, 0x8282, 0x8812, 0x8182, 0x0232, 0x0235, 0x0238,
+ 0x023b, 0x8722, 0x8272, 0x8462, 0x8712, 0x8552, 0x8172, 0x023e, 0x8632, 0x8362, 0x8542, 0x8452,
+ 0x8622, 0x8262, 0x8612, 0x0241, 0x8532, 0x7162, 0x7162, 0x8352, 0x8442, 0x7522, 0x7522, 0x7252,
+ 0x7252, 0x7512, 0x7512, 0x7152, 0x7152, 0x8501, 0x8051, 0x7432, 0x7432, 0x7342, 0x7342, 0x7422,
+ 0x7422, 0x7242, 0x7242, 0x7332, 0x7332, 0x6142, 0x6142, 0x6142, 0x6142, 0x7412, 0x7412, 0x7401,
+ 0x7401, 0x6322, 0x6322, 0x6322, 0x6322, 0x6232, 0x6232, 0x6232, 0x6232, 0x7041, 0x7041, 0x7301,
+ 0x7301, 0x6312, 0x6312, 0x6312, 0x6312, 0x6132, 0x6132, 0x6132, 0x6132, 0x6031, 0x6031, 0x6031,
+ 0x6031, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5222, 0x5212, 0x5212, 0x5212,
+ 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122,
+ 0x5122, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5201, 0x5021, 0x5021, 0x5021,
+ 0x5021, 0x5021, 0x5021, 0x5021, 0x5021, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112, 0x3112,
+ 0x3112, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011,
+ 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x3000, 0x3000, 0x3000,
+ 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000,
+ 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0x3000,
+ 0x3000, 0x3000, 0x3000, 0x3000, 0x3000, 0xf005, 0x5ff2, 0x5fe2, 0x5ef2, 0x5fd2, 0x4ee2, 0x4ee2,
+ 0x5df2, 0x5fc2, 0x5cf2, 0x5ed2, 0x5de2, 0x5fb2, 0x4bf2, 0x4bf2, 0x5ec2, 0x5ce2, 0x4dd2, 0x4dd2,
+ 0x4fa2, 0x4fa2, 0x4af2, 0x4af2, 0x4eb2, 0x4eb2, 0x4be2, 0x4be2, 0x4dc2, 0x4dc2, 0x4cd2, 0x4cd2,
+ 0x4f92, 0x4f92, 0xf005, 0x49f2, 0x49f2, 0x4ae2, 0x4ae2, 0x4db2, 0x4db2, 0x4bd2, 0x4bd2, 0x4f82,
+ 0x4f82, 0x48f2, 0x48f2, 0x4cc2, 0x4cc2, 0x4e92, 0x4e92, 0x49e2, 0x49e2, 0x4f72, 0x4f72, 0x47f2,
+ 0x47f2, 0x4da2, 0x4da2, 0x4ad2, 0x4ad2, 0x4cb2, 0x4cb2, 0x4f62, 0x4f62, 0x5ea2, 0x5f01, 0xf004,
+ 0x3bc2, 0x3bc2, 0x36f2, 0x36f2, 0x4e82, 0x48e2, 0x4f52, 0x4d92, 0x35f2, 0x35f2, 0x3e72, 0x3e72,
+ 0x37e2, 0x37e2, 0x3ca2, 0x3ca2, 0xf004, 0x3ac2, 0x3ac2, 0x3bb2, 0x3bb2, 0x49d2, 0x4d82, 0x3f42,
+ 0x3f42, 0x34f2, 0x34f2, 0x3f32, 0x3f32, 0x33f2, 0x33f2, 0x38d2, 0x38d2, 0xf004, 0x36e2, 0x36e2,
+ 0x3f22, 0x3f22, 0x32f2, 0x32f2, 0x4e62, 0x40f1, 0x3f12, 0x3f12, 0x31f2, 0x31f2, 0x3c92, 0x3c92,
+ 0x39c2, 0x39c2, 0xf003, 0x3e52, 0x3ba2, 0x3ab2, 0x35e2, 0x3d72, 0x37d2, 0x3e42, 0x34e2, 0xf003,
+ 0x3c82, 0x38c2, 0x3e32, 0x3d62, 0x36d2, 0x33e2, 0x3b92, 0x39b2, 0xf004, 0x3e22, 0x3e22, 0x3aa2,
+ 0x3aa2, 0x32e2, 0x32e2, 0x3e12, 0x3e12, 0x31e2, 0x31e2, 0x4e01, 0x40e1, 0x3d52, 0x3d52, 0x35d2,
+ 0x35d2, 0xf003, 0x3c72, 0x37c2, 0x3d42, 0x3b82, 0x24d2, 0x24d2, 0x38b2, 0x3a92, 0xf003, 0x39a2,
+ 0x3c62, 0x36c2, 0x3d32, 0x23d2, 0x23d2, 0x22d2, 0x22d2, 0xf003, 0x3d22, 0x3d01, 0x2d12, 0x2d12,
+ 0x2b72, 0x2b72, 0x27b2, 0x27b2, 0xf003, 0x21d2, 0x21d2, 0x3c52, 0x30d1, 0x25c2, 0x25c2, 0x2a82,
+ 0x2a82, 0xf002, 0x28a2, 0x2c42, 0x24c2, 0x2b62, 0xf003, 0x26b2, 0x26b2, 0x3992, 0x3c01, 0x2c32,
+ 0x2c32, 0x23c2, 0x23c2, 0xf003, 0x2a72, 0x2a72, 0x27a2, 0x27a2, 0x26a2, 0x26a2, 0x30c1, 0x3b01,
+ 0xf002, 0x12c2, 0x12c2, 0x2c22, 0x2b52, 0xf002, 0x25b2, 0x2c12, 0x2982, 0x2892, 0xf002, 0x21c2,
+ 0x2b42, 0x24b2, 0x2a62, 0xf002, 0x2b32, 0x2972, 0x13b2, 0x13b2, 0xf002, 0x2792, 0x2882, 0x2b22,
+ 0x2a52, 0xf002, 0x12b2, 0x12b2, 0x25a2, 0x2b12, 0xf002, 0x11b2, 0x11b2, 0x20b1, 0x2962, 0xf002,
+ 0x2692, 0x2a42, 0x24a2, 0x2872, 0xf002, 0x2782, 0x2a32, 0x13a2, 0x13a2, 0xf001, 0x1952, 0x1592,
+ 0xf001, 0x1a22, 0x12a2, 0xf001, 0x1a12, 0x11a2, 0xf002, 0x2a01, 0x20a1, 0x1862, 0x1862, 0xf001,
+ 0x1682, 0x1942, 0xf001, 0x1492, 0x1932, 0xf002, 0x1392, 0x1392, 0x2772, 0x2901, 0xf001, 0x1852,
+ 0x1582, 0xf001, 0x1922, 0x1762, 0xf001, 0x1672, 0x1292, 0xf001, 0x1912, 0x1091, 0xf001, 0x1842,
+ 0x1482, 0xf001, 0x1752, 0x1572, 0xf001, 0x1832, 0x1382, 0xf001, 0x1662, 0x1742, 0xf001, 0x1472,
+ 0x1801, 0xf001, 0x1081, 0x1652, 0xf001, 0x1562, 0x1732, 0xf001, 0x1372, 0x1642, 0xf001, 0x1701,
+ 0x1071, 0xf001, 0x1601, 0x1061,
+ /* huffTable16[651] */
+ 0xf008, 0x0101, 0x010a, 0x0113, 0x8ff2, 0x0118, 0x011d, 0x0120, 0x82f2, 0x0131, 0x8f12, 0x81f2,
+ 0x0134, 0x0145, 0x0156, 0x0167, 0x0178, 0x0189, 0x019a, 0x01a3, 0x01ac, 0x01b5, 0x01be, 0x01c7,
+ 0x01d0, 0x01d9, 0x01de, 0x01e3, 0x01e6, 0x01eb, 0x01f0, 0x8152, 0x01f3, 0x01f6, 0x01f9, 0x01fc,
+ 0x8412, 0x8142, 0x01ff, 0x8322, 0x8232, 0x7312, 0x7312, 0x7132, 0x7132, 0x8301, 0x8031, 0x7222,
+ 0x7222, 0x6212, 0x6212, 0x6212, 0x6212, 0x6122, 0x6122, 0x6122, 0x6122, 0x6201, 0x6201, 0x6201,
+ 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112,
+ 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011,
+ 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x3011, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0xf003, 0x3fe2, 0x3ef2, 0x3fd2, 0x3df2, 0x3fc2, 0x3cf2,
+ 0x3fb2, 0x3bf2, 0xf003, 0x2fa2, 0x2fa2, 0x3af2, 0x3f92, 0x39f2, 0x38f2, 0x2f82, 0x2f82, 0xf002,
+ 0x2f72, 0x27f2, 0x2f62, 0x26f2, 0xf002, 0x2f52, 0x25f2, 0x1f42, 0x1f42, 0xf001, 0x14f2, 0x13f2,
+ 0xf004, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x10f1, 0x2f32, 0x2f32, 0x2f32,
+ 0x2f32, 0x00e2, 0x00f3, 0x00fc, 0x0105, 0xf001, 0x1f22, 0x1f01, 0xf004, 0x00fa, 0x00ff, 0x0104,
+ 0x0109, 0x010c, 0x0111, 0x0116, 0x0119, 0x011e, 0x0123, 0x0128, 0x43e2, 0x012d, 0x0130, 0x0133,
+ 0x0136, 0xf004, 0x0128, 0x012b, 0x012e, 0x4d01, 0x0131, 0x0134, 0x0137, 0x4c32, 0x013a, 0x4c12,
+ 0x40c1, 0x013d, 0x32e2, 0x32e2, 0x4e22, 0x4e12, 0xf004, 0x43d2, 0x4d22, 0x42d2, 0x41d2, 0x4b32,
+ 0x012f, 0x3d12, 0x3d12, 0x44c2, 0x4b62, 0x43c2, 0x47a2, 0x3c22, 0x3c22, 0x42c2, 0x45b2, 0xf004,
+ 0x41c2, 0x4c01, 0x4b42, 0x44b2, 0x4a62, 0x46a2, 0x33b2, 0x33b2, 0x4a52, 0x45a2, 0x3b22, 0x3b22,
+ 0x32b2, 0x32b2, 0x3b12, 0x3b12, 0xf004, 0x31b2, 0x31b2, 0x4b01, 0x40b1, 0x4962, 0x4692, 0x4a42,
+ 0x44a2, 0x4872, 0x4782, 0x33a2, 0x33a2, 0x4a32, 0x4952, 0x3a22, 0x3a22, 0xf004, 0x4592, 0x4862,
+ 0x31a2, 0x31a2, 0x4682, 0x4772, 0x3492, 0x3492, 0x4942, 0x4752, 0x3762, 0x3762, 0x22a2, 0x22a2,
+ 0x22a2, 0x22a2, 0xf003, 0x2a12, 0x2a12, 0x3a01, 0x30a1, 0x3932, 0x3392, 0x3852, 0x3582, 0xf003,
+ 0x2922, 0x2922, 0x2292, 0x2292, 0x3672, 0x3901, 0x2912, 0x2912, 0xf003, 0x2192, 0x2192, 0x3091,
+ 0x3842, 0x3482, 0x3572, 0x3832, 0x3382, 0xf003, 0x3662, 0x3822, 0x2282, 0x2282, 0x3742, 0x3472,
+ 0x2812, 0x2812, 0xf003, 0x2182, 0x2182, 0x2081, 0x2081, 0x3801, 0x3652, 0x2732, 0x2732, 0xf003,
+ 0x2372, 0x2372, 0x3562, 0x3642, 0x2722, 0x2722, 0x2272, 0x2272, 0xf003, 0x3462, 0x3552, 0x2701,
+ 0x2701, 0x1712, 0x1712, 0x1712, 0x1712, 0xf002, 0x1172, 0x1172, 0x2071, 0x2632, 0xf002, 0x2362,
+ 0x2542, 0x2452, 0x2622, 0xf001, 0x1262, 0x1612, 0xf002, 0x1162, 0x1162, 0x2601, 0x2061, 0xf002,
+ 0x1352, 0x1352, 0x2532, 0x2442, 0xf001, 0x1522, 0x1252, 0xf001, 0x1512, 0x1501, 0xf001, 0x1432,
+ 0x1342, 0xf001, 0x1051, 0x1422, 0xf001, 0x1242, 0x1332, 0xf001, 0x1401, 0x1041, 0xf004, 0x4ec2,
+ 0x0086, 0x3ed2, 0x3ed2, 0x39e2, 0x39e2, 0x4ae2, 0x49d2, 0x2ee2, 0x2ee2, 0x2ee2, 0x2ee2, 0x3de2,
+ 0x3de2, 0x3be2, 0x3be2, 0xf003, 0x2eb2, 0x2eb2, 0x2dc2, 0x2dc2, 0x3cd2, 0x3bd2, 0x2ea2, 0x2ea2,
+ 0xf003, 0x2cc2, 0x2cc2, 0x3da2, 0x3ad2, 0x3e72, 0x3ca2, 0x2ac2, 0x2ac2, 0xf003, 0x39c2, 0x3d72,
+ 0x2e52, 0x2e52, 0x1db2, 0x1db2, 0x1db2, 0x1db2, 0xf002, 0x1e92, 0x1e92, 0x2cb2, 0x2bc2, 0xf002,
+ 0x2e82, 0x28e2, 0x2d92, 0x27e2, 0xf002, 0x2bb2, 0x2d82, 0x28d2, 0x2e62, 0xf001, 0x16e2, 0x1c92,
+ 0xf002, 0x2ba2, 0x2ab2, 0x25e2, 0x27d2, 0xf002, 0x1e42, 0x1e42, 0x24e2, 0x2c82, 0xf001, 0x18c2,
+ 0x1e32, 0xf002, 0x1d62, 0x1d62, 0x26d2, 0x2b92, 0xf002, 0x29b2, 0x2aa2, 0x11e2, 0x11e2, 0xf002,
+ 0x14d2, 0x14d2, 0x28b2, 0x29a2, 0xf002, 0x1b72, 0x1b72, 0x27b2, 0x20d1, 0xf001, 0x1e01, 0x10e1,
+ 0xf001, 0x1d52, 0x15d2, 0xf001, 0x1c72, 0x17c2, 0xf001, 0x1d42, 0x1b82, 0xf001, 0x1a92, 0x1c62,
+ 0xf001, 0x16c2, 0x1d32, 0xf001, 0x1c52, 0x15c2, 0xf001, 0x1a82, 0x18a2, 0xf001, 0x1992, 0x1c42,
+ 0xf001, 0x16b2, 0x1a72, 0xf001, 0x1b52, 0x1982, 0xf001, 0x1892, 0x1972, 0xf001, 0x1792, 0x1882,
+ 0xf001, 0x1ce2, 0x1dd2,
+ /* huffTable24[705] */
+ 0xf009, 0x8fe2, 0x8fe2, 0x8ef2, 0x8ef2, 0x8fd2, 0x8fd2, 0x8df2, 0x8df2, 0x8fc2, 0x8fc2, 0x8cf2,
+ 0x8cf2, 0x8fb2, 0x8fb2, 0x8bf2, 0x8bf2, 0x7af2, 0x7af2, 0x7af2, 0x7af2, 0x8fa2, 0x8fa2, 0x8f92,
+ 0x8f92, 0x79f2, 0x79f2, 0x79f2, 0x79f2, 0x78f2, 0x78f2, 0x78f2, 0x78f2, 0x8f82, 0x8f82, 0x8f72,
+ 0x8f72, 0x77f2, 0x77f2, 0x77f2, 0x77f2, 0x7f62, 0x7f62, 0x7f62, 0x7f62, 0x76f2, 0x76f2, 0x76f2,
+ 0x76f2, 0x7f52, 0x7f52, 0x7f52, 0x7f52, 0x75f2, 0x75f2, 0x75f2, 0x75f2, 0x7f42, 0x7f42, 0x7f42,
+ 0x7f42, 0x74f2, 0x74f2, 0x74f2, 0x74f2, 0x7f32, 0x7f32, 0x7f32, 0x7f32, 0x73f2, 0x73f2, 0x73f2,
+ 0x73f2, 0x7f22, 0x7f22, 0x7f22, 0x7f22, 0x72f2, 0x72f2, 0x72f2, 0x72f2, 0x71f2, 0x71f2, 0x71f2,
+ 0x71f2, 0x8f12, 0x8f12, 0x80f1, 0x80f1, 0x9f01, 0x0201, 0x0206, 0x020b, 0x0210, 0x0215, 0x021a,
+ 0x021f, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2,
+ 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2,
+ 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x4ff2, 0x0224, 0x0229, 0x0232,
+ 0x0237, 0x023a, 0x023f, 0x0242, 0x0245, 0x024a, 0x024d, 0x0250, 0x0253, 0x0256, 0x0259, 0x025c,
+ 0x025f, 0x0262, 0x0265, 0x0268, 0x026b, 0x026e, 0x0271, 0x0274, 0x0277, 0x027a, 0x027d, 0x0280,
+ 0x0283, 0x0288, 0x028b, 0x028e, 0x0291, 0x0294, 0x0297, 0x029a, 0x029f, 0x94b2, 0x02a4, 0x02a7,
+ 0x02aa, 0x93b2, 0x9882, 0x02af, 0x92b2, 0x02b2, 0x02b5, 0x9692, 0x94a2, 0x02b8, 0x9782, 0x9a32,
+ 0x93a2, 0x9952, 0x9592, 0x9a22, 0x92a2, 0x91a2, 0x9862, 0x9682, 0x9772, 0x9942, 0x9492, 0x9932,
+ 0x9392, 0x9852, 0x9582, 0x9922, 0x9762, 0x9672, 0x9292, 0x9912, 0x9192, 0x9842, 0x9482, 0x9752,
+ 0x9572, 0x9832, 0x9382, 0x9662, 0x9822, 0x9282, 0x9812, 0x9742, 0x9472, 0x9182, 0x02bb, 0x9652,
+ 0x9562, 0x9712, 0x02be, 0x8372, 0x8372, 0x9732, 0x9722, 0x8272, 0x8272, 0x8642, 0x8642, 0x8462,
+ 0x8462, 0x8552, 0x8552, 0x8172, 0x8172, 0x8632, 0x8632, 0x8362, 0x8362, 0x8542, 0x8542, 0x8452,
+ 0x8452, 0x8622, 0x8622, 0x8262, 0x8262, 0x8612, 0x8612, 0x8162, 0x8162, 0x9601, 0x9061, 0x8532,
+ 0x8532, 0x8352, 0x8352, 0x8442, 0x8442, 0x8522, 0x8522, 0x8252, 0x8252, 0x8512, 0x8512, 0x9501,
+ 0x9051, 0x7152, 0x7152, 0x7152, 0x7152, 0x8432, 0x8432, 0x8342, 0x8342, 0x7422, 0x7422, 0x7422,
+ 0x7422, 0x7242, 0x7242, 0x7242, 0x7242, 0x7332, 0x7332, 0x7332, 0x7332, 0x7412, 0x7412, 0x7412,
+ 0x7412, 0x7142, 0x7142, 0x7142, 0x7142, 0x8401, 0x8401, 0x8041, 0x8041, 0x7322, 0x7322, 0x7322,
+ 0x7322, 0x7232, 0x7232, 0x7232, 0x7232, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312, 0x6312,
+ 0x6312, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x6132, 0x7301, 0x7301, 0x7301,
+ 0x7301, 0x7031, 0x7031, 0x7031, 0x7031, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222, 0x6222,
+ 0x6222, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5212,
+ 0x5212, 0x5212, 0x5212, 0x5212, 0x5212, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122,
+ 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x5122, 0x6201, 0x6201, 0x6201,
+ 0x6201, 0x6201, 0x6201, 0x6201, 0x6201, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021, 0x6021,
+ 0x6021, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112,
+ 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112,
+ 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4112, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4101,
+ 0x4101, 0x4101, 0x4101, 0x4101, 0x4101, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011,
+ 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011,
+ 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011, 0x4011,
+ 0x4011, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000,
+ 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000,
+ 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0xf002, 0x2ee2, 0x2ed2,
+ 0x2de2, 0x2ec2, 0xf002, 0x2ce2, 0x2dd2, 0x2eb2, 0x2be2, 0xf002, 0x2dc2, 0x2cd2, 0x2ea2, 0x2ae2,
+ 0xf002, 0x2db2, 0x2bd2, 0x2cc2, 0x2e92, 0xf002, 0x29e2, 0x2da2, 0x2ad2, 0x2cb2, 0xf002, 0x2bc2,
+ 0x2e82, 0x28e2, 0x2d92, 0xf002, 0x29d2, 0x2e72, 0x27e2, 0x2ca2, 0xf002, 0x2ac2, 0x2bb2, 0x2d82,
+ 0x28d2, 0xf003, 0x3e01, 0x30e1, 0x2d01, 0x2d01, 0x16e2, 0x16e2, 0x16e2, 0x16e2, 0xf002, 0x2e62,
+ 0x2c92, 0x19c2, 0x19c2, 0xf001, 0x1e52, 0x1ab2, 0xf002, 0x15e2, 0x15e2, 0x2ba2, 0x2d72, 0xf001,
+ 0x17d2, 0x14e2, 0xf001, 0x1c82, 0x18c2, 0xf002, 0x2e42, 0x2e22, 0x1e32, 0x1e32, 0xf001, 0x1d62,
+ 0x16d2, 0xf001, 0x13e2, 0x1b92, 0xf001, 0x19b2, 0x1aa2, 0xf001, 0x12e2, 0x1e12, 0xf001, 0x11e2,
+ 0x1d52, 0xf001, 0x15d2, 0x1c72, 0xf001, 0x17c2, 0x1d42, 0xf001, 0x1b82, 0x18b2, 0xf001, 0x14d2,
+ 0x1a92, 0xf001, 0x19a2, 0x1c62, 0xf001, 0x16c2, 0x1d32, 0xf001, 0x13d2, 0x1d22, 0xf001, 0x12d2,
+ 0x1d12, 0xf001, 0x1b72, 0x17b2, 0xf001, 0x11d2, 0x1c52, 0xf001, 0x15c2, 0x1a82, 0xf001, 0x18a2,
+ 0x1992, 0xf001, 0x1c42, 0x14c2, 0xf001, 0x1b62, 0x16b2, 0xf002, 0x20d1, 0x2c01, 0x1c32, 0x1c32,
+ 0xf001, 0x13c2, 0x1a72, 0xf001, 0x17a2, 0x1c22, 0xf001, 0x12c2, 0x1b52, 0xf001, 0x15b2, 0x1c12,
+ 0xf001, 0x1982, 0x1892, 0xf001, 0x11c2, 0x1b42, 0xf002, 0x20c1, 0x2b01, 0x1b32, 0x1b32, 0xf002,
+ 0x20b1, 0x2a01, 0x1a12, 0x1a12, 0xf001, 0x1a62, 0x16a2, 0xf001, 0x1972, 0x1792, 0xf002, 0x20a1,
+ 0x2901, 0x1091, 0x1091, 0xf001, 0x1b22, 0x1a52, 0xf001, 0x15a2, 0x1b12, 0xf001, 0x11b2, 0x1962,
+ 0xf001, 0x1a42, 0x1872, 0xf001, 0x1801, 0x1081, 0xf001, 0x1701, 0x1071,
+};
+/* pow(2,-i/4) * pow(j,4/3) for i=0..3 j=0..15, Q25 format */
+const int32_t pow43_14[4][16] PROGMEM = { /* Q28 */
+{ 0x00000000, 0x10000000, 0x285145f3, 0x453a5cdb, 0x0cb2ff53, 0x111989d6,
+ 0x15ce31c8, 0x1ac7f203, 0x20000000, 0x257106b9, 0x2b16b4a3, 0x30ed74b4,
+ 0x36f23fa5, 0x3d227bd3, 0x437be656, 0x49fc823c, },
+
+{ 0x00000000, 0x0d744fcd, 0x21e71f26, 0x3a36abd9, 0x0aadc084, 0x0e610e6e,
+ 0x12560c1d, 0x168523cf, 0x1ae89f99, 0x1f7c03a4, 0x243bae49, 0x29249c67,
+ 0x2e34420f, 0x33686f85, 0x38bf3dff, 0x3e370182, },
+
+{ 0x00000000, 0x0b504f33, 0x1c823e07, 0x30f39a55, 0x08facd62, 0x0c176319,
+ 0x0f6b3522, 0x12efe2ad, 0x16a09e66, 0x1a79a317, 0x1e77e301, 0x2298d5b4,
+ 0x26da56fc, 0x2b3a902a, 0x2fb7e7e7, 0x3450f650, },
+
+{ 0x00000000, 0x09837f05, 0x17f910d7, 0x2929c7a9, 0x078d0dfa, 0x0a2ae661,
+ 0x0cf73154, 0x0fec91cb, 0x1306fe0a, 0x16434a6c, 0x199ee595, 0x1d17ae3d,
+ 0x20abd76a, 0x2459d551, 0x28204fbb, 0x2bfe1808, },
+};
+
+/* pow(j,4/3) for j=16..63, Q23 format */
+const int32_t pow43[48] PROGMEM = {
+ 0x1428a2fa, 0x15db1bd6, 0x1796302c, 0x19598d85, 0x1b24e8bb, 0x1cf7fcfa,
+ 0x1ed28af2, 0x20b4582a, 0x229d2e6e, 0x248cdb55, 0x26832fda, 0x28800000,
+ 0x2a832287, 0x2c8c70a8, 0x2e9bc5d8, 0x30b0ff99, 0x32cbfd4a, 0x34eca001,
+ 0x3712ca62, 0x393e6088, 0x3b6f47e0, 0x3da56717, 0x3fe0a5fc, 0x4220ed72,
+ 0x44662758, 0x46b03e7c, 0x48ff1e87, 0x4b52b3f3, 0x4daaebfd, 0x5007b497,
+ 0x5268fc62, 0x54ceb29c, 0x5738c721, 0x59a72a59, 0x5c19cd35, 0x5e90a129,
+ 0x610b9821, 0x638aa47f, 0x660db90f, 0x6894c90b, 0x6b1fc80c, 0x6daeaa0d,
+ 0x70416360, 0x72d7e8b0, 0x75722ef9, 0x78102b85, 0x7ab1d3ec, 0x7d571e09,
+};
+
+const uint32_t polyCoef[264] PROGMEM = {
+ /* shuffled vs. original from 0, 1, ... 15 to 0, 15, 2, 13, ... 14, 1 */
+ 0x00000000, 0x00000074, 0x00000354, 0x0000072c, 0x00001fd4, 0x00005084, 0x000066b8, 0x000249c4,
+ 0x00049478, 0xfffdb63c, 0x000066b8, 0xffffaf7c, 0x00001fd4, 0xfffff8d4, 0x00000354, 0xffffff8c,
+ 0xfffffffc, 0x00000068, 0x00000368, 0x00000644, 0x00001f40, 0x00004ad0, 0x00005d1c, 0x00022ce0,
+ 0x000493c0, 0xfffd9960, 0x00006f78, 0xffffa9cc, 0x0000203c, 0xfffff7e4, 0x00000340, 0xffffff84,
+ 0xfffffffc, 0x00000060, 0x00000378, 0x0000056c, 0x00001e80, 0x00004524, 0x000052a0, 0x00020ffc,
+ 0x000491a0, 0xfffd7ca0, 0x00007760, 0xffffa424, 0x00002080, 0xfffff6ec, 0x00000328, 0xffffff74,
+ 0xfffffffc, 0x00000054, 0x00000384, 0x00000498, 0x00001d94, 0x00003f7c, 0x00004744, 0x0001f32c,
+ 0x00048e18, 0xfffd6008, 0x00007e70, 0xffff9e8c, 0x0000209c, 0xfffff5ec, 0x00000310, 0xffffff68,
+ 0xfffffffc, 0x0000004c, 0x0000038c, 0x000003d0, 0x00001c78, 0x000039e4, 0x00003b00, 0x0001d680,
+ 0x00048924, 0xfffd43ac, 0x000084b0, 0xffff990c, 0x00002094, 0xfffff4e4, 0x000002f8, 0xffffff5c,
+ 0xfffffffc, 0x00000044, 0x00000390, 0x00000314, 0x00001b2c, 0x0000345c, 0x00002ddc, 0x0001ba04,
+ 0x000482d0, 0xfffd279c, 0x00008a20, 0xffff93a4, 0x0000206c, 0xfffff3d4, 0x000002dc, 0xffffff4c,
+ 0xfffffffc, 0x00000040, 0x00000390, 0x00000264, 0x000019b0, 0x00002ef0, 0x00001fd4, 0x00019dc8,
+ 0x00047b1c, 0xfffd0be8, 0x00008ecc, 0xffff8e64, 0x00002024, 0xfffff2c0, 0x000002c0, 0xffffff3c,
+ 0xfffffff8, 0x00000038, 0x0000038c, 0x000001bc, 0x000017fc, 0x0000299c, 0x000010e8, 0x000181d8,
+ 0x0004720c, 0xfffcf09c, 0x000092b4, 0xffff894c, 0x00001fc0, 0xfffff1a4, 0x000002a4, 0xffffff2c,
+ 0xfffffff8, 0x00000034, 0x00000380, 0x00000120, 0x00001618, 0x00002468, 0x00000118, 0x00016644,
+ 0x000467a4, 0xfffcd5cc, 0x000095e0, 0xffff8468, 0x00001f44, 0xfffff084, 0x00000284, 0xffffff18,
+ 0xfffffff8, 0x0000002c, 0x00000374, 0x00000090, 0x00001400, 0x00001f58, 0xfffff068, 0x00014b14,
+ 0x00045bf0, 0xfffcbb88, 0x00009858, 0xffff7fbc, 0x00001ea8, 0xffffef60, 0x00000268, 0xffffff04,
+ 0xfffffff8, 0x00000028, 0x0000035c, 0x00000008, 0x000011ac, 0x00001a70, 0xffffded8, 0x00013058,
+ 0x00044ef8, 0xfffca1d8, 0x00009a1c, 0xffff7b54, 0x00001dfc, 0xffffee3c, 0x0000024c, 0xfffffef0,
+ 0xfffffff4, 0x00000024, 0x00000340, 0xffffff8c, 0x00000f28, 0x000015b0, 0xffffcc70, 0x0001161c,
+ 0x000440bc, 0xfffc88d8, 0x00009b3c, 0xffff7734, 0x00001d38, 0xffffed18, 0x0000022c, 0xfffffedc,
+ 0xfffffff4, 0x00000020, 0x00000320, 0xffffff1c, 0x00000c68, 0x0000111c, 0xffffb92c, 0x0000fc6c,
+ 0x00043150, 0xfffc708c, 0x00009bb8, 0xffff7368, 0x00001c64, 0xffffebf4, 0x00000210, 0xfffffec4,
+ 0xfffffff0, 0x0000001c, 0x000002f4, 0xfffffeb4, 0x00000974, 0x00000cb8, 0xffffa518, 0x0000e350,
+ 0x000420b4, 0xfffc5908, 0x00009b9c, 0xffff6ff4, 0x00001b7c, 0xffffead0, 0x000001f4, 0xfffffeac,
+ 0xfffffff0, 0x0000001c, 0x000002c4, 0xfffffe58, 0x00000648, 0x00000884, 0xffff9038, 0x0000cad0,
+ 0x00040ef8, 0xfffc425c, 0x00009af0, 0xffff6ce0, 0x00001a88, 0xffffe9b0, 0x000001d4, 0xfffffe94,
+ 0xffffffec, 0x00000018, 0x0000028c, 0xfffffe04, 0x000002e4, 0x00000480, 0xffff7a90, 0x0000b2fc,
+ 0x0003fc28, 0xfffc2c90, 0x000099b8, 0xffff6a3c, 0x00001988, 0xffffe898, 0x000001bc, 0xfffffe7c,
+ 0x000001a0, 0x0000187c, 0x000097fc, 0x0003e84c, 0xffff6424, 0xffffff4c, 0x00000248, 0xffffffec,
+};
+
+/* format = Q30, range = [0.0981, 1.9976]
+ *
+ * n = 16;
+ * k = 0;
+ * for(i=0; i<5; i++, n=n/2) {
+ * for(p=0; pbytePtr = buf;
+ bsi->iCache = 0; /* 4-byte uint32_t */
+ bsi->cachedBits = 0; /* i.e. zero bits in cache */
+ bsi->nBytes = nBytes;
+}
+//----------------------------------------------------------------------------------------------------------------------
+void RefillBitstreamCache(BitStreamInfo_t *bsi) {
+ int32_t nBytes = bsi->nBytes;
+ /* optimize for common case, independent of machine endian-ness */
+ if (nBytes >= 4) {
+ bsi->iCache = (*bsi->bytePtr++) << 24;
+ bsi->iCache |= (*bsi->bytePtr++) << 16;
+ bsi->iCache |= (*bsi->bytePtr++) << 8;
+ bsi->iCache |= (*bsi->bytePtr++);
+ bsi->cachedBits = 32;
+ bsi->nBytes -= 4;
+ } else {
+ bsi->iCache = 0;
+ while (nBytes--) {
+ bsi->iCache |= (*bsi->bytePtr++);
+ bsi->iCache <<= 8;
+ }
+ bsi->iCache <<= ((3 - bsi->nBytes) * 8);
+ bsi->cachedBits = 8 * bsi->nBytes;
+ bsi->nBytes = 0;
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------
+uint32_t GetBits(BitStreamInfo_t *bsi, int32_t nBits) {
+ uint32_t data, lowBits;
+
+ nBits &= 0x1f; /* nBits mod 32 to avoid unpredictable results like >> by negative amount */
+ data = bsi->iCache >> (31 - nBits); /* unsigned >> so zero-extend */
+ data >>= 1; /* do as >> 31, >> 1 so that nBits = 0 works okay (returns 0) */
+ bsi->iCache <<= nBits; /* left-justify cache */
+ bsi->cachedBits -= nBits; /* how many bits have we drawn from the cache so far */
+ if (bsi->cachedBits < 0) {/* if we cross anint32_t boundary, refill the cache */
+ lowBits = -bsi->cachedBits;
+ RefillBitstreamCache(bsi);
+ data |= bsi->iCache >> (32 - lowBits); /* get the low-order bits */
+ bsi->cachedBits -= lowBits; /* how many bits have we drawn from the cache so far */
+ bsi->iCache <<= lowBits; /* left-justify cache */
+ }
+ return data;
+}
+//----------------------------------------------------------------------------------------------------------------------
+int32_t CalcBitsUsed(BitStreamInfo_t *bsi, uint8_t *startBuf, int32_t startOffset){
+ int32_t bitsUsed;
+ bitsUsed = (bsi->bytePtr - startBuf) * 8;
+ bitsUsed -= bsi->cachedBits;
+ bitsUsed -= startOffset;
+ return bitsUsed;
+}
+//----------------------------------------------------------------------------------------------------------------------
+int32_t CheckPadBit(){
+ return (m_FrameHeader->paddingBit ? 1 : 0);
+}
+//----------------------------------------------------------------------------------------------------------------------
+int32_t UnpackFrameHeader(uint8_t *buf){
+ int32_t verIdx;
+ /* validate pointers and sync word */
+ if ((buf[0] & m_SYNCWORDH) != m_SYNCWORDH || (buf[1] & m_SYNCWORDL) != m_SYNCWORDL){return -1;}
+ /* read header fields - use bitmasks instead of GetBits() for speed, since format never varies */
+ verIdx = (buf[1] >> 3) & 0x03;
+ m_MPEGVersion = (MPEGVersion_t) (verIdx == 0 ? MPEG25 : ((verIdx & 0x01) ? MPEG1 : MPEG2));
+ m_FrameHeader->layer = 4 - ((buf[1] >> 1) & 0x03); /* easy mapping of index to layer number, 4 = error */
+ m_FrameHeader->crc = 1 - ((buf[1] >> 0) & 0x01);
+ m_FrameHeader->brIdx = (buf[2] >> 4) & 0x0f;
+ m_FrameHeader->srIdx = (buf[2] >> 2) & 0x03;
+ m_FrameHeader->paddingBit = (buf[2] >> 1) & 0x01;
+ m_FrameHeader->privateBit = (buf[2] >> 0) & 0x01;
+ m_sMode = (StereoMode_t) ((buf[3] >> 6) & 0x03); /* maps to correct enum (see definition) */
+ m_FrameHeader->modeExt = (buf[3] >> 4) & 0x03;
+ m_FrameHeader->copyFlag = (buf[3] >> 3) & 0x01;
+ m_FrameHeader->origFlag = (buf[3] >> 2) & 0x01;
+ m_FrameHeader->emphasis = (buf[3] >> 0) & 0x03;
+ /* check parameters to avoid indexing tables with bad values */
+ if (m_FrameHeader->srIdx == 3 || m_FrameHeader->layer == 4 || m_FrameHeader->brIdx == 15) {return -1;}
+ /* for readability (we reference sfBandTable many times in decoder) */
+ m_SFBandTable = sfBandTable[m_MPEGVersion][m_FrameHeader->srIdx];
+ if (m_sMode != Joint) /* just to be safe (dequant, stproc check fh->modeExt) */
+ m_FrameHeader->modeExt = 0;
+ /* init user-accessible data */
+ m_MP3DecInfo->nChans = (m_sMode == Mono ? 1 : 2);
+ m_MP3DecInfo->samprate = samplerateTab[m_MPEGVersion][m_FrameHeader->srIdx];
+ m_MP3DecInfo->nGrans = (m_MPEGVersion == MPEG1 ? m_NGRANS_MPEG1 : m_NGRANS_MPEG2);
+ m_MP3DecInfo->nGranSamps = ((int32_t) samplesPerFrameTab[m_MPEGVersion][m_FrameHeader->layer - 1])/m_MP3DecInfo->nGrans;
+ m_MP3DecInfo->layer = m_FrameHeader->layer;
+
+ /* get bitrate and nSlots from table, unless brIdx == 0 (free mode) in which case caller must figure it out himself
+ * question - do we want to overwrite mp3DecInfo->bitrate with 0 each time if it's free mode, and
+ * copy the pre-calculated actual free bitrate into it in mp3dec.c (according to the spec,
+ * this shouldn't be necessary, since it should be either all frames free or none free)
+ */
+ if (m_FrameHeader->brIdx) {
+ m_MP3DecInfo->bitrate=((int32_t) bitrateTab[m_MPEGVersion][m_FrameHeader->layer - 1][m_FrameHeader->brIdx]) * 1000;
+ /* nSlots = total frame bytes (from table) - sideInfo bytes - header - CRC (if present) + pad (if present) */
+ m_MP3DecInfo->nSlots= (int32_t) slotTab[m_MPEGVersion][m_FrameHeader->srIdx][m_FrameHeader->brIdx]
+ - (int32_t) sideBytesTab[m_MPEGVersion][(m_sMode == Mono ? 0 : 1)] - 4
+ - (m_FrameHeader->crc ? 2 : 0) + (m_FrameHeader->paddingBit ? 1 : 0);
+ }
+ /* load crc word, if enabled, and return length of frame header (in bytes) */
+ if (m_FrameHeader->crc) {
+ m_FrameHeader->CRCWord = ((int32_t) buf[4] << 8 | (int32_t) buf[5] << 0);
+ return 6;
+ } else {
+ m_FrameHeader->CRCWord = 0;
+ return 4;
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------
+int32_t UnpackSideInfo( uint8_t *buf) {
+ int32_t gr, ch, bd, nBytes;
+ BitStreamInfo_t bitStreamInfo, *bsi;
+
+ SideInfoSub_t *sis;
+ /* validate pointers and sync word */
+ bsi = &bitStreamInfo;
+ if (m_MPEGVersion == MPEG1) {
+ /* MPEG 1 */
+ nBytes=(m_sMode == Mono ? m_SIBYTES_MPEG1_MONO : m_SIBYTES_MPEG1_STEREO);
+ SetBitstreamPointer(bsi, nBytes, buf);
+ m_SideInfo->mainDataBegin = GetBits(bsi, 9);
+ m_SideInfo->privateBits= GetBits(bsi, (m_sMode == Mono ? 5 : 3));
+ for (ch = 0; ch < m_MP3DecInfo->nChans; ch++)
+ for (bd = 0; bd < m_MAX_SCFBD; bd++) m_SideInfo->scfsi[ch][bd] = GetBits(bsi, 1);
+ } else {
+ /* MPEG 2, MPEG 2.5 */
+ nBytes=(m_sMode == Mono ? m_SIBYTES_MPEG2_MONO : m_SIBYTES_MPEG2_STEREO);
+ SetBitstreamPointer(bsi, nBytes, buf);
+ m_SideInfo->mainDataBegin = GetBits(bsi, 8);
+ m_SideInfo->privateBits = GetBits(bsi, (m_sMode == Mono ? 1 : 2));
+ }
+ for (gr = 0; gr < m_MP3DecInfo->nGrans; gr++) {
+ for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) {
+ sis = &m_SideInfoSub[gr][ch]; /* side info subblock for this granule, channel */
+ sis->part23Length = GetBits(bsi, 12);
+ sis->nBigvals = GetBits(bsi, 9);
+ sis->globalGain = GetBits(bsi, 8);
+ sis->sfCompress = GetBits(bsi, (m_MPEGVersion == MPEG1 ? 4 : 9));
+ sis->winSwitchFlag = GetBits(bsi, 1);
+ if (sis->winSwitchFlag) {
+ /* this is a start, stop, short, or mixed block */
+ sis->blockType = GetBits(bsi, 2); /* 0 = normal, 1 = start, 2 = short, 3 = stop */
+ sis->mixedBlock = GetBits(bsi, 1); /* 0 = not mixed, 1 = mixed */
+ sis->tableSelect[0] = GetBits(bsi, 5);
+ sis->tableSelect[1] = GetBits(bsi, 5);
+ sis->tableSelect[2] = 0; /* unused */
+ sis->subBlockGain[0] = GetBits(bsi, 3);
+ sis->subBlockGain[1] = GetBits(bsi, 3);
+ sis->subBlockGain[2] = GetBits(bsi, 3);
+ if (sis->blockType == 0) {
+ /* this should not be allowed, according to spec */
+ sis->nBigvals = 0;
+ sis->part23Length = 0;
+ sis->sfCompress = 0;
+ } else if (sis->blockType == 2 && sis->mixedBlock == 0) {
+ /* short block, not mixed */
+ sis->region0Count = 8;
+ } else {
+ /* start, stop, or short-mixed */
+ sis->region0Count = 7;
+ }
+ sis->region1Count = 20 - sis->region0Count;
+ } else {
+ /* this is a normal block */
+ sis->blockType = 0;
+ sis->mixedBlock = 0;
+ sis->tableSelect[0] = GetBits(bsi, 5);
+ sis->tableSelect[1] = GetBits(bsi, 5);
+ sis->tableSelect[2] = GetBits(bsi, 5);
+ sis->region0Count = GetBits(bsi, 4);
+ sis->region1Count = GetBits(bsi, 3);
+ }
+ sis->preFlag = (m_MPEGVersion == MPEG1 ? GetBits(bsi, 1) : 0);
+ sis->sfactScale = GetBits(bsi, 1);
+ sis->count1TableSelect = GetBits(bsi, 1);
+ }
+ }
+ m_MP3DecInfo->mainDataBegin = m_SideInfo->mainDataBegin; /* needed by main decode loop */
+ assert(nBytes == CalcBitsUsed(bsi, buf, 0) >> 3);
+ return nBytes;
+}
+/***********************************************************************************************************************
+ * Function: UnpackSFMPEG1
+ *
+ * Description: unpack MPEG 1 scalefactors from bitstream
+ *
+ * Inputs: BitStreamInfo, SideInfoSub, ScaleFactorInfoSub structs for this
+ * granule/channel
+ * vector of scfsi flags from side info, length = 4 (MAX_SCFBD)
+ * index of current granule
+ * ScaleFactorInfoSub from granule 0 (for granule 1, if scfsi[i] is set,
+ * then we just replicate the scale factors from granule 0 in the
+ * i'th set of scalefactor bands)
+ *
+ * Outputs: updated BitStreamInfo struct
+ * scalefactors in sfis (short and/or long arrays, as appropriate)
+ *
+ * Return: none
+ *
+ * Notes: set order of short blocks to s[band][window] instead of s[window][band]
+ * so that we index through consectutive memory locations when unpacking
+ * (make sure dequantizer follows same convention)
+ * Illegal Intensity Position = 7 (always) for MPEG1 scale factors
+ **********************************************************************************************************************/
+void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis,
+ ScaleFactorInfoSub_t *sfis, int32_t *scfsi, int32_t gr, ScaleFactorInfoSub_t *sfisGr0){
+ int32_t sfb;
+ int32_t slen0, slen1;
+ /* these can be 0, so make sure GetBits(bsi, 0) returns 0 (no >> 32 or anything) */
+ slen0 = (int32_t)m_SFLenTab[sis->sfCompress][0];
+ slen1 = (int32_t)m_SFLenTab[sis->sfCompress][1];
+ if (sis->blockType == 2){
+ /* short block, type 2 (implies winSwitchFlag == 1) */
+ if (sis->mixedBlock){
+ /* do long block portion */
+ for(sfb = 0; sfb < 8; sfb++)
+ sfis->l[sfb]=(char)GetBits(bsi, slen0);
+ sfb=3;
+ }
+ else {
+ /* all short blocks */
+ sfb=0;
+ }
+ for ( ; sfb < 6; sfb++){
+ sfis->s[sfb][0] = (char)GetBits(bsi, slen0);
+ sfis->s[sfb][1] = (char)GetBits(bsi, slen0);
+ sfis->s[sfb][2] = (char)GetBits(bsi, slen0);
+ }
+ for ( ; sfb < 12; sfb++) {
+ sfis->s[sfb][0] = (char)GetBits(bsi, slen1);
+ sfis->s[sfb][1] = (char)GetBits(bsi, slen1);
+ sfis->s[sfb][2] = (char)GetBits(bsi, slen1);
+ }
+ /* last sf band not transmitted */
+ sfis->s[12][0] = sfis->s[12][1] = sfis->s[12][2] = 0;
+ }
+ else{
+ /* long blocks, type 0, 1, or 3 */
+ if(gr == 0) {
+ /* first granule */
+ for (sfb = 0; sfb < 11; sfb++)
+ sfis->l[sfb] = (char)GetBits(bsi, slen0);
+ for (sfb = 11; sfb < 21; sfb++)
+ sfis->l[sfb] = (char)GetBits(bsi, slen1);
+ return;
+ }
+ else{
+ /* second granule
+ * scfsi: 0 = different scalefactors for each granule,
+ * 1 = copy sf's from granule 0 into granule 1
+ * for block type == 2, scfsi is always 0
+ */
+ sfb = 0;
+ if(scfsi[0]) for( ; sfb < 6 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb];
+ else for( ; sfb < 6 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen0);
+ if(scfsi[1]) for( ; sfb <11 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb];
+ else for( ; sfb <11 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen0);
+ if(scfsi[2]) for( ; sfb <16 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb];
+ else for( ; sfb <16 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen1);
+ if(scfsi[3]) for( ; sfb <21 ; sfb++) sfis->l[sfb] = sfisGr0->l[sfb];
+ else for( ; sfb <21 ; sfb++) sfis->l[sfb] = (char)GetBits(bsi, slen1);
+ }
+ /* last sf band not transmitted */
+ sfis->l[21] = 0;
+ sfis->l[22] = 0;
+ }
+}
+/***********************************************************************************************************************
+ * Function: UnpackSFMPEG2
+ *
+ * Description: unpack MPEG 2 scalefactors from bitstream
+ *
+ * Inputs: BitStreamInfo, SideInfoSub, ScaleFactorInfoSub structs for this
+ * granule/channel
+ * index of current granule and channel
+ * ScaleFactorInfoSub from this granule
+ * modeExt field from frame header, to tell whether intensity stereo is on
+ * ScaleFactorJS struct for storing IIP info used in Dequant()
+ *
+ * Outputs: updated BitStreamInfo struct
+ * scalefactors in sfis (short and/or long arrays, as appropriate)
+ * updated intensityScale and preFlag flags
+ *
+ * Return: none
+ *
+ * Notes: Illegal Intensity Position = (2^slen) - 1 for MPEG2 scale factors
+ **********************************************************************************************************************/
+void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis,
+ ScaleFactorInfoSub_t *sfis, int32_t gr, int32_t ch, int32_t modeExt, ScaleFactorJS_t *sfjs){
+
+ int32_t i, sfb, sfcIdx, btIdx, nrIdx;// iipTest;
+ int32_t slen[4], nr[4];
+ int32_t sfCompress, preFlag, intensityScale;
+ (void)gr;
+ sfCompress = sis->sfCompress;
+ preFlag = 0;
+ intensityScale = 0;
+
+ /* stereo mode bits (1 = on): bit 1 = mid-side on/off, bit 0 = intensity on/off */
+ if (! ((modeExt & 0x01) && (ch == 1)) ) {
+ /* in other words: if ((modeExt & 0x01) == 0 || ch == 0) */
+ if (sfCompress < 400) {
+ /* max slen = floor[(399/16) / 5] = 4 */
+ slen[0] = (sfCompress >> 4) / 5;
+ slen[1]= (sfCompress >> 4) % 5;
+ slen[2]= (sfCompress & 0x0f) >> 2;
+ slen[3]= (sfCompress & 0x03);
+ sfcIdx = 0;
+ }
+ else if(sfCompress < 500){
+ /* max slen = floor[(99/4) / 5] = 4 */
+ sfCompress -= 400;
+ slen[0] = (sfCompress >> 2) / 5;
+ slen[1]= (sfCompress >> 2) % 5;
+ slen[2]= (sfCompress & 0x03);
+ slen[3]= 0;
+ sfcIdx = 1;
+ }
+ else{
+ /* max slen = floor[11/3] = 3 (sfCompress = 9 bits in MPEG2) */
+ sfCompress -= 500;
+ slen[0] = sfCompress / 3;
+ slen[1] = sfCompress % 3;
+ slen[2] = slen[3] = 0;
+ if (sis->mixedBlock) {
+ /* adjust for long/short mix logic (see comment above in NRTab[] definition) */
+ slen[2] = slen[1];
+ slen[1] = slen[0];
+ }
+ preFlag = 1;
+ sfcIdx = 2;
+ }
+ }
+ else{
+ /* intensity stereo ch = 1 (right) */
+ intensityScale = sfCompress & 0x01;
+ sfCompress >>= 1;
+ if (sfCompress < 180) {
+ /* max slen = floor[35/6] = 5 (from mod 36) */
+ slen[0] = (sfCompress / 36);
+ slen[1] = (sfCompress % 36) / 6;
+ slen[2] = (sfCompress % 36) % 6;
+ slen[3] = 0;
+ sfcIdx = 3;
+ }
+ else if (sfCompress < 244){
+ /* max slen = floor[63/16] = 3 */
+ sfCompress -= 180;
+ slen[0] = (sfCompress & 0x3f) >> 4;
+ slen[1] = (sfCompress & 0x0f) >> 2;
+ slen[2] = (sfCompress & 0x03);
+ slen[3] = 0;
+ sfcIdx = 4;
+ }
+ else{
+ /* max slen = floor[11/3] = 3 (max sfCompress >> 1 = 511/2 = 255) */
+ sfCompress -= 244;
+ slen[0] = (sfCompress / 3);
+ slen[1] = (sfCompress % 3);
+ slen[2] = slen[3] = 0;
+ sfcIdx = 5;
+ }
+ }
+ /* set index based on block type: (0,1,3) --> 0, (2 non-mixed) --> 1, (2 mixed) ---> 2 */
+ btIdx = 0;
+ if (sis->blockType == 2)
+ btIdx = (sis->mixedBlock ? 2 : 1);
+ for (i = 0; i < 4; i++)
+ nr[i] = (int32_t)NRTab[sfcIdx][btIdx][i];
+
+ /* save intensity stereo scale factor info */
+ if( (modeExt & 0x01) && (ch == 1) ) {
+ for (i = 0; i < 4; i++) {
+ sfjs->slen[i] = slen[i];
+ sfjs->nr[i] = nr[i];
+ }
+ sfjs->intensityScale = intensityScale;
+ }
+ sis->preFlag = preFlag;
+
+ /* short blocks */
+ if(sis->blockType == 2) {
+ if(sis->mixedBlock) {
+ /* do long block portion */
+ //iipTest = (1 << slen[0]) - 1;
+ for (sfb=0; sfb < 6; sfb++) {
+ sfis->l[sfb] = (char)GetBits(bsi, slen[0]);
+ }
+ sfb = 3; /* start sfb for short */
+ nrIdx = 1;
+ }
+ else{
+ /* all short blocks, so start nr, sfb at 0 */
+ sfb = 0;
+ nrIdx = 0;
+ }
+
+ /* remaining short blocks, sfb just keeps incrementing */
+ for( ; nrIdx <= 3; nrIdx++) {
+ //iipTest = (1 << slen[nrIdx]) - 1;
+ for(i=0; i < nr[nrIdx]; i++, sfb++) {
+ sfis->s[sfb][0] = (char)GetBits(bsi, slen[nrIdx]);
+ sfis->s[sfb][1] = (char)GetBits(bsi, slen[nrIdx]);
+ sfis->s[sfb][2] = (char)GetBits(bsi, slen[nrIdx]);
+ }
+ }
+ /* last sf band not transmitted */
+ sfis->s[12][0] = sfis->s[12][1] = sfis->s[12][2] = 0;
+ }
+ else{
+ /* long blocks */
+ sfb = 0;
+ for (nrIdx = 0; nrIdx <= 3; nrIdx++) {
+ //iipTest = (1 << slen[nrIdx]) - 1;
+ for(i=0; i < nr[nrIdx]; i++, sfb++) {
+ sfis->l[sfb] = (char)GetBits(bsi, slen[nrIdx]);
+ }
+ }
+ /* last sf band not transmitted */
+ sfis->l[21] = sfis->l[22] = 0;
+
+ }
+}
+/***********************************************************************************************************************
+ * Function: UnpackScaleFactors
+ *
+ * Description: parse the fields of the MP3 scale factor data section
+ *
+ * Inputs: MP3DecInfo structure filled by UnpackFrameHeader() and UnpackSideInfo()
+ * buffer pointing to the MP3 scale factor data
+ * pointer to bit offset (0-7) indicating starting bit in buf[0]
+ * number of bits available in data buffer
+ * index of current granule and channel
+ *
+ * Outputs: updated platform-specific ScaleFactorInfo struct
+ * updated bitOffset
+ *
+ * Return: length (in bytes) of scale factor data, -1 if null input pointers
+ **********************************************************************************************************************/
+int32_t UnpackScaleFactors( uint8_t *buf, int32_t *bitOffset, int32_t bitsAvail, int32_t gr, int32_t ch){
+ int32_t bitsUsed;
+ uint8_t *startBuf;
+ BitStreamInfo_t bitStreamInfo, *bsi;
+
+ /* init GetBits reader */
+ startBuf = buf;
+ bsi = &bitStreamInfo;
+ SetBitstreamPointer(bsi, (bitsAvail + *bitOffset + 7) / 8, buf);
+ if (*bitOffset)
+ GetBits(bsi, *bitOffset);
+
+ if (m_MPEGVersion == MPEG1)
+ UnpackSFMPEG1(bsi, &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch],
+ m_SideInfo->scfsi[ch], gr, &m_ScaleFactorInfoSub[0][ch]);
+ else
+ UnpackSFMPEG2(bsi, &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch],
+ gr, ch, m_FrameHeader->modeExt, m_ScaleFactorJS);
+
+ m_MP3DecInfo->part23Length[gr][ch] = m_SideInfoSub[gr][ch].part23Length;
+
+ bitsUsed = CalcBitsUsed(bsi, buf, *bitOffset);
+ buf += (bitsUsed + *bitOffset) >> 3;
+ *bitOffset = (bitsUsed + *bitOffset) & 0x07;
+
+ return (buf - startBuf);
+}
+/*****************************************************************************************************************************************************
+ * M P 3 D E C
+ ****************************************************************************************************************************************************/
+
+/*****************************************************************************************************************************************************
+ * Function: MP3FindSyncWord
+ *
+ * Description: locate the next byte-alinged sync word in the raw mp3 stream
+ *
+ * Inputs: buffer to search for sync word
+ * max number of bytes to search in buffer
+ *
+ * Outputs: none
+ *
+ * Return: offset to first sync word (bytes from start of buf)
+ * -1 if sync not found after searching nBytes
+ ****************************************************************************************************************************************************/
+int32_t MP3FindSyncWord(uint8_t *buf, int32_t nBytes) {
+
+ // Auxiliary function for extracting bits, byte 'value', 'start_bit' is that bit from the left (0-7), 'num_bits' is the number of bits
+ // auto extract_bits = [&](uint8_t byte, uint8_t start_bit, uint8_t num_bits) {
+ // return (byte >> (8 - start_bit - num_bits)) & ((1 << num_bits) - 1);
+ // };
+ const uint8_t SYNCWORDH = 0xff;
+ const uint8_t SYNCWORDL = 0xe0;
+
+ typedef struct {
+ uint8_t mpeg_version; // 0=MPEG2.5, 1=reserved, 2=MPEG2, 3=MPEG1
+ uint8_t layer; // 0=reserved, 1=Layer III, 2=Layer II, 3=Layer I
+ bool crc_protected;
+ uint8_t bitrate_idx;
+ uint8_t sample_rate_idx;
+ bool padding;
+ uint8_t channel_mode;
+ uint32_t frame_length; // In Bytes
+ uint16_t sample_rate_hz; // Die tatsächliche Abtastrate in Hz
+ uint16_t bitrate_kbps; // Die tatsächliche Bitrate in kbps
+ uint16_t samples_per_frame;
+ } Mp3FrameHeader;
+
+ // SamplingFrequenz-Lookup tables(Beispiel für MPEG1, MPEG2, MPEG2.5)
+ const uint16_t sampling_rates[3][4] = {
+ {44100, 48000, 32000, 0}, // MPEG1
+ {22050, 24000, 16000, 0}, // MPEG2
+ {11025, 12000, 8000, 0} // MPEG2.5
+ };
+
+ typedef enum { /* map to 0,1,2 to make table indexing easier */
+ MPEG1 = 0,
+ MPEG2 = 1,
+ MPEG25 = 2
+ } MPEGVersion_t;
+
+ const uint16_t mpeg1_layer1_bitrates[16] = {
+ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0
+ };
+
+ const uint16_t mpeg1_layer3_bitrates[16] = { // Bitraten-Lookup tables (example for MPEG1 Layer III)
+ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 // Attention: These tables must be complete and correct!
+ };
+ // Define bitrate tables for MPEG1 Layer II and MPEG2/2.5 Layer II
+ // These tables are examples and need to be complete based on the MPEG standard
+ const uint16_t mpeg1_layer2_bitrates[] = {
+ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0
+ };
+ const uint16_t mpeg2_layer2_bitrates[] = {
+ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0
+ };
+ const uint16_t mpeg2_layer3_bitrates[] = {
+ 0, // "Free format" oder ungültig
+ 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160,
+ 0 // Ungültig
+ };
+
+ // Funktion zum Parsen des Headers und Überprüfen der Gültigkeit
+ auto parseMp3Header = [&](const uint8_t* header_data, Mp3FrameHeader* header_info) {
+ // Byte 0: Syncword H (bereits geprüft)
+ // Byte 1: Syncword L, MPEG Version, Layer
+ // Byte 2: Bitrate, Sampling Frequency, Padding, Private
+ // Byte 3: Channel Mode, Mode Extension, Copyright, Original, Emphasis
+
+ // Syncword has already been checked, here we start with the other bits
+ header_info->mpeg_version = (header_data[1] >> 3) & 0b11; // Bits 12, 13 (A, B)
+ header_info->layer = (header_data[1] >> 1) & 0b11; // Bits 14, 15 (C, D)
+ header_info->crc_protected = !((header_data[1] >> 0) & 0b1); // Bit 16 (Schutzbit)
+
+ header_info->bitrate_idx = (header_data[2] >> 4) & 0b1111; // Bits 17-20
+ header_info->sample_rate_idx = (header_data[2] >> 2) & 0b11; // Bits 21-22
+ header_info->padding = (header_data[2] >> 1) & 0b1; // Bit 23
+
+ header_info->channel_mode = (header_data[3] >> 6) & 0b11; // Bits 24-25
+
+ // Gültigkeitsprüfungen
+ if (header_info->mpeg_version == 1) { // Reserved
+ log_d("Reserved MPEG version\n");
+ return false;
+ }
+ if (header_info->layer == 0) { // Reserved
+ log_d("Reserved Layer\n");
+ return false;
+ }
+ // Modified part: Support for Layer II and Layer III
+ if (header_info->layer != 1 && header_info->layer != 2 && header_info->layer != 3 ) { // Allow Layer I (3) Layer II (2) and Layer III (1)
+ printf("\n"); for(int i = 0; i<10; i++) printf("0x%02x ", header_data[i]); printf("\n"); // Use header_data instead of buf
+ log_d("Not Layer I or II or III\n");
+ return false;
+ }
+
+ if (header_info->bitrate_idx == 0 || header_info->bitrate_idx == 15) { // Invalid bit rate
+ log_d("Invalid bitrate index\n");
+ return false;
+ }
+ if (header_info->sample_rate_idx == 3) { // Invalid sampling frequency
+ log_d("Invalid sampling rate index\n");
+ return false;
+ }
+
+ // Determine the actual bit rate and sampling frequency
+ uint16_t bitrate_kbps = 0;
+ uint16_t sample_rate_hz = 0;
+
+ // Mapping from MPEG version to sampling rate table
+ uint8_t sr_table_idx;
+ if (header_info->mpeg_version == 3) sr_table_idx = 0; // MPEG 1 (0b11)
+ else if (header_info->mpeg_version == 2) sr_table_idx = 1; // MPEG 2 (0b10)
+ else sr_table_idx = 2; // MPEG 2.5 (da mpeg_version == 0) - Although Google TTS is likely MPEG 2.0
+
+ sample_rate_hz = sampling_rates[sr_table_idx][header_info->sample_rate_idx];
+
+ // Bitraten-Mapping für verschiedene MPEG-Versionen und Layer
+ if (header_info->mpeg_version == 3) { // MPEG 1
+ if (header_info->layer == 1) { // Layer III
+ bitrate_kbps = mpeg1_layer3_bitrates[header_info->bitrate_idx];
+ } else if (header_info->layer == 2) { // Layer II
+ bitrate_kbps = mpeg1_layer2_bitrates[header_info->bitrate_idx];
+ } else if (header_info->layer == 3) { // Layer I
+ bitrate_kbps = mpeg1_layer1_bitrates[header_info->bitrate_idx];
+ }
+ } else if (header_info->mpeg_version == 2 || header_info->mpeg_version == 0) { // MPEG 2 or MPEG 2.5
+ if (header_info->layer == 1) { // Layer III
+ bitrate_kbps = mpeg2_layer3_bitrates[header_info->bitrate_idx];
+ } else if (header_info->layer == 2) { // Layer II
+ bitrate_kbps = mpeg2_layer2_bitrates[header_info->bitrate_idx];
+ }
+ // If you also want to support MPEG 2/2.5 Layer I, you'd add another else if here
+ // e.g., else if (header_info->layer == 3) { bitrate_kbps = mpeg2_layer1_bitrates[header_info->bitrate_idx]; }
+ }
+
+ if (bitrate_kbps == 0 || sample_rate_hz == 0) {
+ log_d("Could not determine valid bitrate or sample rate\n");
+ return false;
+ }
+
+ // Calculate frame length based on layer
+ // FrameSize = (1152 * BitRate / SampleRate) + Padding (for Layer III)
+ // FrameSize = (144 * BitRate / SampleRate) + Padding (for Layer I)
+ // FrameSize = (576 * BitRate / SampleRate) + Padding (for Layer II, MPEG 2.0/2.5)
+ // Note: For MPEG 1 Layer II, it's (1152 * BitRate / SampleRate) + Padding
+ // Need to be careful with the constant depending on MPEG version and layer
+ if (header_info->layer == 1) { // Layer III
+ header_info->frame_length = (144 * bitrate_kbps * 1000) / sample_rate_hz; // Assuming MPEG1 Layer III
+ if (header_info->mpeg_version == 2 || header_info->mpeg_version == 0) { // MPEG2/2.5 Layer III
+ header_info->frame_length = (72 * bitrate_kbps * 1000) / sample_rate_hz; // Correct constant for MPEG2/2.5 Layer III
+ }
+ } else if (header_info->layer == 2) { // Layer II
+ if (header_info->mpeg_version == 3) { // MPEG 1 Layer II
+ header_info->frame_length = (144 * bitrate_kbps * 1000) / sample_rate_hz;
+ } else if (header_info->mpeg_version == 2 || header_info->mpeg_version == 0) { // MPEG 2/2.5 Layer II
+ header_info->frame_length = (144 * bitrate_kbps * 1000) / sample_rate_hz;
+ }
+ } else if (header_info->layer == 3) { // Layer I
+ // For Layer I, the formula is (Bitrate * 12 / SampleRate) + Padding (in Bytes)
+ // Note: Bitrate is in kbps, so multiply by 1000 to get bps
+ if (header_info->mpeg_version == 3) { // MPEG 1 Layer I
+ header_info->frame_length = (bitrate_kbps * 1000 / 8 * 12) / sample_rate_hz; // Correct
+ } else if (header_info->mpeg_version == 2 || header_info->mpeg_version == 0) { // MPEG 2/2.5 Layer I (if supported)
+ // You'd add the specific calculation for MPEG2/2.5 Layer I here
+ // For MPEG 2/2.5 Layer I, samples per frame is 576, so the constant is 6
+ header_info->frame_length = (bitrate_kbps * 1000 / 8 * 6) / sample_rate_hz; // Hypothetical, verify constant
+ }
+ }
+
+ if (header_info->padding) {
+ header_info->frame_length += 1; // Füge 1 Byte für Padding hinzu
+ }
+
+ if (header_info->frame_length == 0) {
+ log_d("Calculated frame length is zero\n");
+ return false;
+ }
+ header_info->sample_rate_hz = sample_rate_hz;
+ header_info->bitrate_kbps = bitrate_kbps;
+
+ // Determine samples_per_frame based on the version and layer
+ if (header_info->mpeg_version == 3) { // MPEG-1
+ if (header_info->layer == 1 || header_info->layer == 2) { // Layer III oder Layer II
+ header_info->samples_per_frame = 1152;
+ } else if (header_info->layer == 3) { // Layer I
+ header_info->samples_per_frame = 384;
+ } else {
+ header_info->samples_per_frame = 0; // Should be caught by previous checks
+ return false;
+ }
+ } else if (header_info->mpeg_version == 2 || header_info->mpeg_version == 0) { // MPEG-2 oder MPEG-2.5
+ if (header_info->layer == 1) { // Layer III
+ header_info->samples_per_frame = 576;
+ } else if (header_info->layer == 2) { // Layer II
+ header_info->samples_per_frame = 1152;
+ } else if (header_info->layer == 3) { // Layer I
+ header_info->samples_per_frame = 576; // Correct for MPEG-2/2.5 Layer I
+ } else {
+ header_info->samples_per_frame = 0; // Should be caught by previous checks
+ return false;
+ }
+ } else { // header_info->mpeg_version == 1 (Reserved)
+ header_info->samples_per_frame = 0;
+ return false;
+ }
+ return true; // Header ist gültig
+ };
+
+ const uint8_t mp3FHsize = 4; // frame header size
+
+ // Lambda for the fast syncword search
+ auto findSync = [&](uint8_t* search_buf, uint16_t offset, uint16_t len) {
+ for (int32_t i = 0; i < len - 1; i++) {
+ // Prüfe auf die 11 oder 12 Sync-Bits
+ if ((search_buf[i + offset] == SYNCWORDH) &&
+ ((search_buf[i + offset + 1] & SYNCWORDL) == SYNCWORDL)) {
+ return i;
+ }
+ }
+ return (int32_t)-1;
+ };
+
+ int32_t current_pos = 0;
+
+ while (nBytes >= mp3FHsize) { // Make sure that there are enough bytes for a header
+ int32_t sync_offset = findSync(buf, current_pos, nBytes);
+
+ if (sync_offset == -1) {
+ log_d("No syncword found in remaining buffer\n");
+ return -1; // No more syncword found
+ }
+
+ current_pos += sync_offset;
+ nBytes -= sync_offset;
+
+ if (nBytes < mp3FHsize) {
+ log_d("Not enough bytes for a full header after syncword\n");
+ return -1; // Not enough data for a full header
+ }
+
+ Mp3FrameHeader header;
+ if (parseMp3Header(&buf[current_pos], &header)) {
+ // This is where the crucial step comes: Check the next frame
+ if (current_pos + header.frame_length + mp3FHsize <= current_pos + nBytes) {
+ // Check whether there is a syncword at the expected next frame start and a valid header is (optional but very robust)
+ Mp3FrameHeader next_header;
+ if (((buf[current_pos + header.frame_length] == SYNCWORDH) && ((buf[current_pos + header.frame_length + 1] & SYNCWORDL) == SYNCWORDL)) &&
+ parseMp3Header(&buf[current_pos + header.frame_length], &next_header)) {
+ log_d("Found reliable MP3 frame at pos: %d, length: %lu\n", current_pos, header.frame_length);
+
+ // s_samplerate = header.sample_rate_hz; // (angenommen in der Struktur vorhanden)
+ // s_bitRate = header.bitrate_kbps; // (angenommen in der Struktur vorhanden)
+ // s_mpeg_version = header.mpeg_version;
+ // s_layer = header.layer;
+ // s_channel_mode = header.channel_mode;
+ // s_samples_per_frame = header.samples_per_frame;
+
+ // // Für s_channels (1 für Mono, 2 für Stereo)
+ // if (header.channel_mode == 0b11) { // 0b11 ist Mono
+ // s_channels = 1;
+ // } else { // Alle anderen Modi (Stereo, Joint Stereo, Dual Channel) sind 2 Kanäle
+ // s_channels = 2;
+ // }
+ return current_pos;
+ } else {
+ log_d("Header valid, but next frame does not validate. False positive. Moving on.\n");
+ }
+ } else {
+ log_d("Header valid, but not enough data for next frame check. Possibly end of stream or false positive.\n");
+ // If not enough data for the next frame, it could still be the right one.
+ // This is a compromise.If in doubt, continue to search or return the current one.
+ // For robustness: search.
+ }
+ } else {
+ log_d("Found syncword but header is invalid. Moving to next possible syncword.\n");
+ }
+
+ // If the current header was invalid or the next frame did not validate the current "SyncWord" and continue to search
+ current_pos += 1; // go a byte on and look for the Syncword again
+ nBytes -= 1;
+ }
+
+ return -1; // no valid MP3 frame found
+}
+/*****************************************************************************************************************************************************
+ * Function: MP3FindFreeSync
+ *
+ * Description: figure out number of bytes between adjacent sync words in "free" mode
+ *
+ * Inputs: buffer to search for next sync word
+ * the 4-byte frame header starting at the current sync word
+ * max number of bytes to search in buffer
+ *
+ * Outputs: none
+ *
+ * Return: offset to next sync word, minus any pad byte (i.e. nSlots)
+ * -1 if sync not found after searching nBytes
+ *
+ * Notes: this checks that the first 22 bits of the next frame header are the
+ * same as the current frame header, but it's still not foolproof
+ * (could accidentally find a sequence in the bitstream which
+ * appears to match but is not actually the next frame header)
+ * this could be made more error-resilient by checking several frames
+ * in a row and verifying that nSlots is the same in each case
+ * since free mode requires CBR (see spec) we generally only call
+ * this function once (first frame) then store the result (nSlots)
+ * and just use it from then on
+ ****************************************************************************************************************************************************/
+int32_t MP3FindFreeSync(uint8_t *buf, uint8_t firstFH[4], int32_t nBytes){
+ int32_t offset = 0;
+ uint8_t *bufPtr = buf;
+
+ /* loop until we either:
+ * - run out of nBytes (FindMP3SyncWord() returns -1)
+ * - find the next valid frame header (sync word, version, layer, CRC flag, bitrate, and sample rate
+ * in next header must match current header)
+ */
+ while (1) {
+ offset = MP3FindSyncWord(bufPtr, nBytes);
+ bufPtr += offset;
+ if (offset < 0) {
+ return -1;
+ } else if ((bufPtr[0] == firstFH[0]) && (bufPtr[1] == firstFH[1])
+ && ((bufPtr[2] & 0xfc) == (firstFH[2] & 0xfc))) {
+ /* want to return number of bytes per frame,
+ * NOT counting the padding byte, so subtract one if padFlag == 1 */
+ if ((firstFH[2] >> 1) & 0x01)
+ bufPtr--;
+ return bufPtr - buf;
+ }
+ bufPtr += 3;
+ nBytes -= (offset + 3);
+ };
+
+ return -1;
+}
+/***********************************************************************************************************************
+ * Function: MP3GetLastFrameInfo
+ *
+ * Description: get info about last MP3 frame decoded (number of sampled decoded,
+ * sample rate, bitrate, etc.)
+ *
+ * Inputs:
+ *
+ * Outputs: filled-in MP3FrameInfo struct
+ *
+ * Return: none
+ *
+ * Notes: call this right after calling MP3Decode
+ **********************************************************************************************************************/
+void MP3GetLastFrameInfo() {
+ if (m_MP3DecInfo->layer != 3){
+ m_MP3FrameInfo->bitrate=0;
+ m_MP3FrameInfo->nChans=0;
+ m_MP3FrameInfo->samprate=0;
+ m_MP3FrameInfo->bitsPerSample=0;
+ m_MP3FrameInfo->outputSamps=0;
+ m_MP3FrameInfo->layer=0;
+ m_MP3FrameInfo->version=0;
+ }
+ else{
+ m_MP3FrameInfo->bitrate=m_MP3DecInfo->bitrate;
+ m_MP3FrameInfo->nChans=m_MP3DecInfo->nChans;
+ m_MP3FrameInfo->samprate=m_MP3DecInfo->samprate;
+ m_MP3FrameInfo->bitsPerSample=16;
+ m_MP3FrameInfo->outputSamps = (int32_t) samplesPerFrameTab[m_MPEGVersion][m_MP3DecInfo->layer-1];
+ m_MP3FrameInfo->layer=m_MP3DecInfo->layer;
+ m_MP3FrameInfo->version=m_MPEGVersion;
+ }
+}
+int32_t MP3GetSampRate(){return m_MP3FrameInfo->samprate;}
+int32_t MP3GetChannels(){return m_MP3FrameInfo->nChans;}
+int32_t MP3GetBitsPerSample(){return m_MP3FrameInfo->bitsPerSample;}
+int32_t MP3GetBitrate(){return m_MP3FrameInfo->bitrate;}
+int32_t MP3GetOutputSamps(){return m_MP3FrameInfo->outputSamps;}
+
+const char* MP3GetLayer(){
+ const char* layer_str = layer_table[m_MP3FrameInfo->layer]; // 0: Reserviert, 1: Layer III, 2: Layer II, 3: Layer I
+ return layer_str;
+}
+
+const char* MP3GetMPEGVersion(){
+ const char* mpeg_version_str = mpeg_version_table[m_MP3FrameInfo->version]; // 0: MPEG-2.5, 1: Reserviert, 2: MPEG-2 (ISO/IEC 13818-3), 3: MPEG-1 (ISO/IEC 11172-3)
+ return mpeg_version_str;
+}
+/***********************************************************************************************************************
+ * Function: MP3GetNextFrameInfo
+ *
+ * Description: parse MP3 frame header
+ *
+ * Inputs: pointer to buffer containing valid MP3 frame header (located using
+ * MP3FindSyncWord(), above)
+ *
+ * Outputs: filled-in MP3FrameInfo struct
+ *
+ * Return: error code, defined in mp3dec.h (0 means no error, < 0 means error)
+ **********************************************************************************************************************/
+int32_t MP3GetNextFrameInfo(uint8_t *buf) {
+
+ if (UnpackFrameHeader( buf) == -1 || m_MP3DecInfo->layer != 3){
+ MP3_ERROR("MP3 invalid frameheader");
+ return MP3_ERR;
+ }
+ MP3GetLastFrameInfo();
+
+ return MP3_NONE;
+}
+/***********************************************************************************************************************
+ * Function: MP3ClearBadFrame
+ *
+ * Description: zero out pcm buffer if error decoding MP3 frame
+ *
+ * Inputs: mp3DecInfo struct with correct frame size parameters filled in
+ * pointer pcm output buffer
+ *
+ * Outputs: zeroed out pcm buffer
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void MP3ClearBadFrame(int16_t *outbuf) {
+ int32_t i;
+ for (i = 0; i < m_MP3DecInfo->nGrans * m_MP3DecInfo->nGranSamps * m_MP3DecInfo->nChans; i++)
+ outbuf[i] = 0;
+}
+/***********************************************************************************************************************
+ * Function: MP3Decode
+ *
+ * Description: decode one frame of MP3 data
+ *
+ * Inputs: number of valid bytes remaining in inbuf
+ * pointer to outbuf, big enough to hold one frame of decoded PCM samples
+ * flag indicating whether MP3 data is normal MPEG format (useSize = 0)
+ * or reformatted as "self-contained" frames (useSize = 1)
+ *
+ * Outputs: PCM data in outbuf, interleaved LRLRLR... if stereo
+ * number of output samples = nGrans * nGranSamps * nChans
+ * updated inbuf pointer, updated bytesLeft
+ *
+ * Return: error code, defined in mp3dec.h (0 means no error, < 0 means error)
+ *
+ * Notes: switching useSize on and off between frames in the same stream
+ * is not supported (bit reservoir is not maintained if useSize on)
+ **********************************************************************************************************************/
+int32_t MP3Decode( uint8_t *inbuf, int32_t *bytesLeft, int16_t *outbuf){
+
+ // int pos = MP3FindSyncWord(inbuf, *bytesLeft);
+ // if(pos > 0){*bytesLeft -= pos; MP3_INFO("skip %i bytes", pos); return MP3_NONE; }
+ // if(pos < 0){MP3_INFO("skip %i max bytes", *bytesLeft); *bytesLeft = 0; return MP3_NONE; }
+
+ int32_t offset, bitOffset, mainBits, gr, ch, fhBytes, siBytes, freeFrameBytes;
+ int32_t prevBitOffset, sfBlockBits, huffBlockBits;
+ uint8_t *mainPtr;
+ static uint8_t underflowCounter = 0; // http://macslons-irish-pub-radio.stream.laut.fm/macslons-irish-pub-radio
+
+ /* unpack frame header */
+ fhBytes = UnpackFrameHeader(inbuf);
+ if (fhBytes < 0){
+ MP3_ERROR("MP3 invalid frameheader"); /* don't clear outbuf since we don't know size (failed to parse header) */
+ return MP3_ERR;
+ }
+ inbuf += fhBytes;
+ /* unpack side info */
+ siBytes = UnpackSideInfo( inbuf);
+ if (siBytes < 0) {
+ MP3ClearBadFrame(outbuf);
+ MP3_ERROR("MP3 invalid sideinfo");
+ return MP3_ERR;
+ }
+ inbuf += siBytes;
+ *bytesLeft -= (fhBytes + siBytes);
+
+ /* if free mode, need to calculate bitrate and nSlots manually, based on frame size */
+ if (m_MP3DecInfo->bitrate == 0 || m_MP3DecInfo->freeBitrateFlag) {
+ if(!m_MP3DecInfo->freeBitrateFlag){
+ /* first time through, need to scan for next sync word and figure out frame size */
+ m_MP3DecInfo->freeBitrateFlag=1;
+ m_MP3DecInfo->freeBitrateSlots=MP3FindFreeSync(inbuf, inbuf - fhBytes - siBytes, *bytesLeft);
+ if(m_MP3DecInfo->freeBitrateSlots < 0){
+ MP3ClearBadFrame(outbuf);
+ m_MP3DecInfo->freeBitrateFlag = 0;
+ MP3_ERROR("MP3, ca'nt find free bitrate slot");
+ return MP3_ERR;
+ }
+ freeFrameBytes=m_MP3DecInfo->freeBitrateSlots + fhBytes + siBytes;
+ m_MP3DecInfo->bitrate=(freeFrameBytes * m_MP3DecInfo->samprate * 8)
+ / (m_MP3DecInfo->nGrans * m_MP3DecInfo->nGranSamps);
+ }
+ m_MP3DecInfo->nSlots = m_MP3DecInfo->freeBitrateSlots + CheckPadBit(); /* add pad byte, if required */
+ }
+
+ /* useSize != 0 means we're getting reformatted (RTP) packets (see RFC 3119)
+ * - calling function assembles "self-contained" MP3 frames by shifting any main_data
+ * from the bit reservoir (in previous frames) to AFTER the sync word and side info
+ * - calling function should set mainDataBegin to 0, and tell us exactly how large this
+ * frame is (in bytesLeft)
+ */
+ // if (useSize) {
+ // m_MP3DecInfo->nSlots = *bytesLeft;
+ // if (m_MP3DecInfo->mainDataBegin != 0 || m_MP3DecInfo->nSlots <= 0) {
+ // /* error - non self-contained frame, or missing frame (size <= 0), could do loss concealment here */
+ // MP3ClearBadFrame(outbuf);
+ // MP3_ERROR("MP3, invalid frameheader");
+ // return MP3_ERR;
+ // }
+
+ // /* can operate in-place on reformatted frames */
+ // m_MP3DecInfo->mainDataBytes = m_MP3DecInfo->nSlots;
+ // mainPtr = inbuf;
+ // inbuf += m_MP3DecInfo->nSlots;
+ // *bytesLeft -= (m_MP3DecInfo->nSlots);
+ // } else {
+ /* out of data - assume last or truncated frame */
+ if (m_MP3DecInfo->nSlots > *bytesLeft) {
+ MP3ClearBadFrame(outbuf);
+ MP3_ERROR("MP3, indata underflow");
+ return MP3_ERR;
+ }
+ /* fill main data buffer with enough new data for this frame */
+ if (m_MP3DecInfo->mainDataBytes >= m_MP3DecInfo->mainDataBegin) {
+ /* adequate "old" main data available (i.e. bit reservoir) */
+ underflowCounter = 0;
+ memmove(m_MP3DecInfo->mainBuf,
+ m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBytes - m_MP3DecInfo->mainDataBegin,
+ m_MP3DecInfo->mainDataBegin);
+ memcpy (m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBegin, inbuf,
+ m_MP3DecInfo->nSlots);
+
+ m_MP3DecInfo->mainDataBytes = m_MP3DecInfo->mainDataBegin + m_MP3DecInfo->nSlots;
+ inbuf += m_MP3DecInfo->nSlots;
+ *bytesLeft -= (m_MP3DecInfo->nSlots);
+ mainPtr = m_MP3DecInfo->mainBuf;
+ } else {
+ /* not enough data in bit reservoir from previous frames (perhaps starting in middle of file) */
+ underflowCounter ++;
+ memcpy(m_MP3DecInfo->mainBuf + m_MP3DecInfo->mainDataBytes, inbuf, m_MP3DecInfo->nSlots);
+ m_MP3DecInfo->mainDataBytes += m_MP3DecInfo->nSlots;
+ inbuf += m_MP3DecInfo->nSlots;
+ *bytesLeft -= (m_MP3DecInfo->nSlots);
+ if(underflowCounter < 4){
+ return MP3_NONE;
+ }
+ MP3ClearBadFrame( outbuf);
+ MP3_ERROR("MP3, maindata underflow");
+ return MP3_ERR;
+ }
+// }
+ bitOffset = 0;
+ mainBits = m_MP3DecInfo->mainDataBytes * 8;
+
+ /* decode one complete frame */
+ for (gr = 0; gr < m_MP3DecInfo->nGrans; gr++) {
+ for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) {
+ /* unpack scale factors and compute size of scale factor block */
+ prevBitOffset = bitOffset;
+ offset = UnpackScaleFactors( mainPtr, &bitOffset,
+ mainBits, gr, ch);
+ sfBlockBits = 8 * offset - prevBitOffset + bitOffset;
+ huffBlockBits = m_MP3DecInfo->part23Length[gr][ch] - sfBlockBits;
+ mainPtr += offset;
+ mainBits -= sfBlockBits;
+
+ if (offset < 0 || mainBits < huffBlockBits) {
+ MP3ClearBadFrame(outbuf);
+ MP3_ERROR("MP3, invalid scalefact");
+ return MP3_ERR;
+ }
+ /* decode Huffman code words */
+ prevBitOffset = bitOffset;
+ offset = DecodeHuffman( mainPtr, &bitOffset, huffBlockBits, gr, ch);
+ if (offset < 0) {
+ MP3ClearBadFrame( outbuf);
+ MP3_ERROR("MP3, invalid Huffman code words");
+ return MP3_ERR;
+ }
+ mainPtr += offset;
+ mainBits -= (8 * offset - prevBitOffset + bitOffset);
+ }
+ /* dequantize coefficients, decode stereo, reorder int16_t blocks */
+ if (MP3Dequantize( gr) < 0) {
+ MP3ClearBadFrame(outbuf);
+ MP3_ERROR("MP3, invalid dequantize coefficients");
+ return MP3_ERR;
+ }
+
+ /* alias reduction, inverse MDCT, overlap-add, frequency inversion */
+ for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) {
+ if (IMDCT( gr, ch) < 0) {
+ MP3ClearBadFrame(outbuf);
+ MP3_ERROR("MP3, invalid inverse MDCT");
+ return MP3_ERR;
+ }
+ }
+ /* subband transform - if stereo, interleaves pcm LRLRLR */
+ if (Subband(
+ outbuf + gr * m_MP3DecInfo->nGranSamps * m_MP3DecInfo->nChans)
+ < 0) {
+ MP3ClearBadFrame(outbuf);
+ MP3_ERROR("MP3, invalid subband");
+ return MP3_ERR;
+ }
+ }
+ MP3GetLastFrameInfo();
+ return MP3_NONE;
+}
+
+/***********************************************************************************************************************
+ * Function: MP3Decoder_ClearBuffer
+ *
+ * Description: clear all the memory needed for the MP3 decoder
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: none
+ *
+ **********************************************************************************************************************/
+void MP3Decoder_ClearBuffer(void) {
+
+ /* important to do this - DSP primitives assume a bunch of state variables are 0 on first use */
+ memset( m_MP3DecInfo, 0, sizeof(MP3DecInfo_t)); //Clear MP3DecInfo
+ memset(&m_ScaleFactorInfoSub, 0, sizeof(ScaleFactorInfoSub_t)*(m_MAX_NGRAN *m_MAX_NCHAN)); //Clear ScaleFactorInfo
+ memset( m_SideInfo, 0, sizeof(SideInfo_t)); //Clear SideInfo
+ memset( m_FrameHeader, 0, sizeof(FrameHeader_t)); //Clear FrameHeader
+ memset( m_HuffmanInfo, 0, sizeof(HuffmanInfo_t)); //Clear HuffmanInfo
+ memset( m_DequantInfo, 0, sizeof(DequantInfo_t)); //Clear DequantInfo
+ memset( m_IMDCTInfo, 0, sizeof(IMDCTInfo_t)); //Clear IMDCTInfo
+ memset( m_SubbandInfo, 0, sizeof(SubbandInfo_t)); //Clear SubbandInfo
+ memset(&m_CriticalBandInfo, 0, sizeof(CriticalBandInfo_t)*m_MAX_NCHAN); //Clear CriticalBandInfo
+ memset( m_ScaleFactorJS, 0, sizeof(ScaleFactorJS_t)); //Clear ScaleFactorJS
+ memset(&m_SideInfoSub, 0, sizeof(SideInfoSub_t)*(m_MAX_NGRAN *m_MAX_NCHAN)); //Clear SideInfoSub
+ memset(&m_SFBandTable, 0, sizeof(SFBandTable_t)); //Clear SFBandTable
+ memset( m_MP3FrameInfo, 0, sizeof(MP3FrameInfo_t)); //Clear MP3FrameInfo
+
+ return;
+
+}
+/***********************************************************************************************************************
+ * Function: MP3Decoder_AllocateBuffers
+ *
+ * Description: allocate all the memory needed for the MP3 decoder
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: pointer to MP3DecInfo structure (initialized with pointers to all
+ * the internal buffers needed for decoding)
+ *
+ * Notes: if one or more mallocs fail, function frees any buffers already
+ * allocated before returning
+ *
+ **********************************************************************************************************************/
+
+#ifdef CONFIG_IDF_TARGET_ESP32S3
+ // ESP32-S3: If there is PSRAM, prefer it
+ #define __malloc_heap_psram(size) \
+ heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL)
+#else
+ // ESP32, PSRAM is too slow, prefer SRAM
+ #define __malloc_heap_psram(size) \
+ heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL, MALLOC_CAP_DEFAULT|MALLOC_CAP_SPIRAM)
+#endif
+
+bool MP3Decoder_AllocateBuffers(void) {
+ if(!m_MP3DecInfo) {m_MP3DecInfo = (MP3DecInfo_t*) __malloc_heap_psram(sizeof(MP3DecInfo_t) );}
+ if(!m_FrameHeader) {m_FrameHeader = (FrameHeader_t*) __malloc_heap_psram(sizeof(FrameHeader_t) );}
+ if(!m_SideInfo) {m_SideInfo = (SideInfo_t*) __malloc_heap_psram(sizeof(SideInfo_t) );}
+ if(!m_ScaleFactorJS) {m_ScaleFactorJS = (ScaleFactorJS_t*) __malloc_heap_psram(sizeof(ScaleFactorJS_t));}
+ if(!m_HuffmanInfo) {m_HuffmanInfo = (HuffmanInfo_t*) __malloc_heap_psram(sizeof(HuffmanInfo_t) );}
+ if(!m_DequantInfo) {m_DequantInfo = (DequantInfo_t*) __malloc_heap_psram(sizeof(DequantInfo_t) );}
+ if(!m_IMDCTInfo) {m_IMDCTInfo = (IMDCTInfo_t*) __malloc_heap_psram(sizeof(IMDCTInfo_t) );}
+ if(!m_SubbandInfo) {m_SubbandInfo = (SubbandInfo_t*) __malloc_heap_psram(sizeof(SubbandInfo_t) );}
+ if(!m_MP3FrameInfo) {m_MP3FrameInfo = (MP3FrameInfo_t*) __malloc_heap_psram(sizeof(MP3FrameInfo_t) );}
+
+ if(!m_MP3DecInfo || !m_FrameHeader || !m_SideInfo || !m_ScaleFactorJS || !m_HuffmanInfo ||
+ !m_DequantInfo || !m_IMDCTInfo || !m_SubbandInfo || !m_MP3FrameInfo) {
+ MP3Decoder_FreeBuffers();
+ log_e("not enough memory to allocate mp3decoder buffers");
+ return false;
+ }
+ MP3Decoder_ClearBuffer();
+ return true;
+}
+/***********************************************************************************************************************
+ * Function: MP3Decoder_IsInit
+ *
+ * Description: returns MP3 decoder initialization status
+ *
+ * Inputs: none
+ *
+ * Outputs: none
+ *
+ * Return: true if buffers allocated, otherwise false
+
+ **********************************************************************************************************************/
+bool MP3Decoder_IsInit(void) {
+ if(!m_MP3DecInfo || !m_FrameHeader || !m_SideInfo || !m_ScaleFactorJS || !m_HuffmanInfo ||
+ !m_DequantInfo || !m_IMDCTInfo || !m_SubbandInfo || !m_MP3FrameInfo) {
+ return false;
+ }
+ return true;
+}
+/***********************************************************************************************************************
+ * Function: MP3Decoder_FreeBuffers
+ *
+ * Description: frees all the memory used by the MP3 decoder
+ *
+ * Inputs: pointer to initialized MP3DecInfo structure
+ *
+ * Outputs: none
+ *
+ * Return: none
+ *
+ * Notes: safe to call even if some buffers were not allocated
+ **********************************************************************************************************************/
+void MP3Decoder_FreeBuffers()
+{
+// uint32_t i = ESP.getFreeHeap();
+
+ if(m_MP3DecInfo) {free(m_MP3DecInfo); m_MP3DecInfo=NULL;}
+ if(m_FrameHeader) {free(m_FrameHeader); m_FrameHeader=NULL;}
+ if(m_SideInfo) {free(m_SideInfo); m_SideInfo=NULL;}
+ if(m_ScaleFactorJS ) {free(m_ScaleFactorJS); m_ScaleFactorJS=NULL;}
+ if(m_HuffmanInfo) {free(m_HuffmanInfo); m_HuffmanInfo=NULL;}
+ if(m_DequantInfo) {free(m_DequantInfo); m_DequantInfo=NULL;}
+ if(m_IMDCTInfo) {free(m_IMDCTInfo); m_IMDCTInfo=NULL;}
+ if(m_SubbandInfo) {free(m_SubbandInfo); m_SubbandInfo=NULL;}
+ if(m_MP3FrameInfo) {free(m_MP3FrameInfo); m_MP3FrameInfo=NULL;}
+
+// log_i("MP3Decoder: %lu bytes memory was freed", ESP.getFreeHeap() - i);
+}
+
+/***********************************************************************************************************************
+ * H U F F M A N N
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: DecodeHuffmanPairs
+ *
+ * Description: decode 2-way vector Huffman codes in the "bigValues" region of spectrum
+ *
+ * Inputs: valid BitStreamInfo struct, pointing to start of pair-wise codes
+ * pointer to xy buffer to received decoded values
+ * number of codewords to decode
+ * index of Huffman table to use
+ * number of bits remaining in bitstream
+ *
+ * Outputs: pairs of decoded coefficients in vwxy
+ * updated BitStreamInfo struct
+ *
+ * Return: number of bits used, or -1 if out of bits
+ *
+ * Notes: assumes that nVals is an even number
+ * si_huff.bit tests every Huffman codeword in every table (though not
+ * necessarily all linBits outputs for x,y > 15)
+ **********************************************************************************************************************/
+// no improvement with section=data
+
+int32_t DecodeHuffmanPairs(int32_t *xy, int32_t nVals, int32_t tabIdx, int32_t bitsLeft, uint8_t *buf, int32_t bitOffset){
+ int32_t i, x, y;
+ int32_t cachedBits, padBits, len, startBits, linBits, maxBits, minBits;
+ HuffTabType_t tabType;
+ uint16_t cw, *tBase, *tCurr;
+ uint32_t cache;
+
+ if (nVals <= 0)
+ return 0;
+
+ if (bitsLeft < 0)
+ return -1;
+ startBits = bitsLeft;
+
+ tBase = (uint16_t *) (huffTable + huffTabOffset[tabIdx]);
+ linBits = huffTabLookup[tabIdx].linBits;
+ tabType = (HuffTabType_t)huffTabLookup[tabIdx].tabType;
+
+// assert(!(nVals & 0x01));
+// assert(tabIdx < m_HUFF_PAIRTABS);
+// assert(tabIdx >= 0);
+// assert(tabType != invalidTab);
+
+ if((nVals & 0x01)){log_d("assert(!(nVals & 0x01))"); return -1;}
+ if(!(tabIdx < m_HUFF_PAIRTABS)){log_d("assert(tabIdx < m_HUFF_PAIRTABS)"); return -1;}
+ if(!(tabIdx >= 0)){log_d("(tabIdx >= 0)"); return -1;}
+ if(!(tabType != invalidTab)){log_d("(tabType != invalidTab)"); return -1;}
+
+
+ /* initially fill cache with any partial byte */
+ cache = 0;
+ cachedBits = (8 - bitOffset) & 0x07;
+ if (cachedBits)
+ cache = (uint32_t) (*buf++) << (32 - cachedBits);
+ bitsLeft -= cachedBits;
+
+ if (tabType == noBits) {
+ /* table 0, no data, x = y = 0 */
+ for (i = 0; i < nVals; i += 2) {
+ xy[i + 0] = 0;
+ xy[i + 1] = 0;
+ }
+ return 0;
+ } else if (tabType == oneShot) {
+ /* single lookup, no escapes */
+
+ maxBits = (int32_t)( (((uint16_t)(pgm_read_word(&tBase[0])) >> 0) & 0x000f));
+ tBase++;
+ padBits = 0;
+ while (nVals > 0) {
+ /* refill cache - assumes cachedBits <= 16 */
+ if (bitsLeft >= 16) {
+ /* load 2 new bytes into left-justified cache */
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ cache |= (uint32_t) (*buf++) << (16 - cachedBits);
+ cachedBits += 16;
+ bitsLeft -= 16;
+ } else {
+ /* last time through, pad cache with zeros and drain cache */
+ if (cachedBits + bitsLeft <= 0){
+ return -1;
+ }
+ if (bitsLeft > 0)
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ if (bitsLeft > 8)
+ cache |= (uint32_t) (*buf++) << (16 - cachedBits);
+ cachedBits += bitsLeft;
+ bitsLeft = 0;
+
+ cache &= (int32_t) 0x80000000 >> (cachedBits - 1);
+ padBits = 11;
+ cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */
+ }
+
+ /* largest maxBits = 9, plus 2 for sign bits, so make sure cache has at least 11 bits */
+ while (nVals > 0 && cachedBits >= 11) {
+ cw = pgm_read_word(&tBase[cache >> (32 - maxBits)]);
+
+ len=(int32_t)( (((uint16_t)(cw)) >> 12) & 0x000f);
+ cachedBits -= len;
+ cache <<= len;
+
+ x=(int32_t)( (((uint16_t)(cw)) >> 4) & 0x000f);
+ if (x) {
+ (x) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ y=(int32_t)( (((uint16_t)(cw)) >> 8) & 0x000f);
+ if (y) {
+ (y) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ /* ran out of bits - should never have consumed padBits */
+ if (cachedBits < padBits){
+ break; // https://bestof80s.stream.laut.fm/best_of_80s (after advertising)
+ // return -1;
+ }
+
+ *xy++ = x;
+ *xy++ = y;
+ nVals -= 2;
+ }
+ }
+ bitsLeft += (cachedBits - padBits);
+ return (startBits - bitsLeft);
+ } else if (tabType == loopLinbits || tabType == loopNoLinbits) {
+ tCurr = tBase;
+ padBits = 0;
+ while (nVals > 0) {
+ /* refill cache - assumes cachedBits <= 16 */
+ if (bitsLeft >= 16) {
+ /* load 2 new bytes into left-justified cache */
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ cache |= (uint32_t) (*buf++) << (16 - cachedBits);
+ cachedBits += 16;
+ bitsLeft -= 16;
+ } else {
+ /* last time through, pad cache with zeros and drain cache */
+ if (cachedBits + bitsLeft <= 0){
+ return -1;
+ }
+ if (bitsLeft > 0)
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ if (bitsLeft > 8)
+ cache |= (uint32_t) (*buf++) << (16 - cachedBits);
+ cachedBits += bitsLeft;
+ bitsLeft = 0;
+
+ cache &= (int32_t) 0x80000000 >> (cachedBits - 1);
+ padBits = 11;
+ cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */
+ }
+
+ /* largest maxBits = 9, plus 2 for sign bits, so make sure cache has at least 11 bits */
+ while (nVals > 0 && cachedBits >= 11) {
+ maxBits = (int32_t)( (((uint16_t)(pgm_read_word(&tCurr[0]))) >> 0) & 0x000f);
+ cw = pgm_read_word(&tCurr[(cache >> (32 - maxBits)) + 1]);
+ len=(int32_t)( (((uint16_t)(cw)) >> 12) & 0x000f);
+ if (!len) {
+ cachedBits -= maxBits;
+ cache <<= maxBits;
+ tCurr += cw;
+ continue;
+ }
+ cachedBits -= len;
+ cache <<= len;
+
+ x=(int32_t)( (((uint16_t)(cw)) >> 4) & 0x000f);
+ y=(int32_t)( (((uint16_t)(cw)) >> 8) & 0x000f);
+
+ if (x == 15 && tabType == loopLinbits) {
+ minBits = linBits + 1 + (y ? 1 : 0);
+ if (cachedBits + bitsLeft < minBits)
+ return -1;
+ while (cachedBits < minBits) {
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ cachedBits += 8;
+ bitsLeft -= 8;
+ }
+ if (bitsLeft < 0) {
+ cachedBits += bitsLeft;
+ bitsLeft = 0;
+ cache &= (int32_t) 0x80000000 >> (cachedBits - 1);
+ }
+ x += (int32_t) (cache >> (32 - linBits));
+ cachedBits -= linBits;
+ cache <<= linBits;
+ }
+ if (x) {
+ (x) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ if (y == 15 && tabType == loopLinbits) {
+ minBits = linBits + 1;
+ if (cachedBits + bitsLeft < minBits)
+ break; // https://bestof80s.stream.laut.fm/best_of_80s (after advertising)
+ // return -1;
+ while (cachedBits < minBits) {
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ cachedBits += 8;
+ bitsLeft -= 8;
+ }
+ if (bitsLeft < 0) {
+ cachedBits += bitsLeft;
+ bitsLeft = 0;
+ cache &= (int32_t) 0x80000000 >> (cachedBits - 1);
+ }
+ y += (int32_t) (cache >> (32 - linBits));
+ cachedBits -= linBits;
+ cache <<= linBits;
+ }
+ if (y) {
+ (y) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ /* ran out of bits - should never have consumed padBits */
+ if (cachedBits < padBits){
+ break; // https://bestof80s.stream.laut.fm/best_of_80s (after advertising)
+ // return -1;
+ }
+
+ *xy++ = x;
+ *xy++ = y;
+ nVals -= 2;
+ tCurr = tBase;
+ }
+ }
+ bitsLeft += (cachedBits - padBits);
+ return (startBits - bitsLeft);
+ }
+
+ /* error in bitstream - trying to access unused Huffman table */
+ return -1;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeHuffmanQuads
+ *
+ * Description: decode 4-way vector Huffman codes in the "count1" region of spectrum
+ *
+ * Inputs: valid BitStreamInfo struct, pointing to start of quadword codes
+ * pointer to vwxy buffer to received decoded values
+ * maximum number of codewords to decode
+ * index of quadword table (0 = table A, 1 = table B)
+ * number of bits remaining in bitstream
+ *
+ * Outputs: quadruples of decoded coefficients in vwxy
+ * updated BitStreamInfo struct
+ *
+ * Return: index of the first "zero_part" value (index of the first sample
+ * of the quad word after which all samples are 0)
+ *
+ * Notes: si_huff.bit tests every vwxy output in both quad tables
+ **********************************************************************************************************************/
+// no improvement with section=data
+int32_t DecodeHuffmanQuads(int32_t *vwxy, int32_t nVals, int32_t tabIdx, int32_t bitsLeft, uint8_t *buf, int32_t bitOffset){
+ int32_t i, v, w, x, y;
+ int32_t len, maxBits, cachedBits, padBits;
+ uint32_t cache;
+ uint8_t cw, *tBase;
+
+ if(bitsLeft<=0) return 0;
+
+ tBase = (uint8_t *) quadTable + quadTabOffset[tabIdx];
+ maxBits = quadTabMaxBits[tabIdx];
+
+ /* initially fill cache with any partial byte */
+ cache = 0;
+ cachedBits=(8-bitOffset) & 0x07;
+ if(cachedBits)cache=(uint32_t)(*buf++) << (32 - cachedBits);
+ bitsLeft -= cachedBits;
+
+ i = padBits = 0;
+ while (i < (nVals - 3)) {
+ /* refill cache - assumes cachedBits <= 16 */
+ if (bitsLeft >= 16) {
+ /* load 2 new bytes into left-justified cache */
+ cache |= (uint32_t) (*buf++) << (24 - cachedBits);
+ cache |= (uint32_t) (*buf++) << (16 - cachedBits);
+ cachedBits += 16;
+ bitsLeft -= 16;
+ } else {
+ /* last time through, pad cache with zeros and drain cache */
+ if(cachedBits+bitsLeft <= 0) return i;
+ if(bitsLeft>0) cache |= (uint32_t)(*buf++)<<(24-cachedBits);
+ if (bitsLeft > 8) cache |= (uint32_t)(*buf++)<<(16 - cachedBits);
+ cachedBits += bitsLeft;
+ bitsLeft = 0;
+
+ cache &= (int32_t) 0x80000000 >> (cachedBits - 1);
+ padBits = 10;
+ cachedBits += padBits; /* okay if this is > 32 (0's automatically shifted in from right) */
+ }
+
+ /* largest maxBits = 6, plus 4 for sign bits, so make sure cache has at least 10 bits */
+ while(i < (nVals - 3) && cachedBits >= 10){
+ cw = pgm_read_byte(&tBase[cache >> (32 - maxBits)]);
+ len=(int32_t)( (((uint8_t)(cw)) >> 4) & 0x0f);
+ cachedBits -= len;
+ cache <<= len;
+
+ v=(int32_t)( (((uint8_t)(cw)) >> 3) & 0x01);
+ if (v) {
+ (v) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+ w=(int32_t)( (((uint8_t)(cw)) >> 2) & 0x01);
+ if (w) {
+ (w) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ x=(int32_t)( (((uint8_t)(cw)) >> 1) & 0x01);
+ if (x) {
+ (x) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ y=(int32_t)( (((uint8_t)(cw)) >> 0) & 0x01);
+ if (y) {
+ (y) |= ((cache) & 0x80000000);
+ cache <<= 1;
+ cachedBits--;
+ }
+
+ /* ran out of bits - okay (means we're done) */
+ if (cachedBits < padBits)
+ return i;
+
+ *vwxy++ = v;
+ *vwxy++ = w;
+ *vwxy++ = x;
+ *vwxy++ = y;
+ i += 4;
+ }
+ }
+
+ /* decoded max number of quad values */
+ return i;
+}
+
+/***********************************************************************************************************************
+ * Function: DecodeHuffman
+ *
+ * Description: decode one granule, one channel worth of Huffman codes
+ *
+ * Inputs: MP3DecInfo structure filled by UnpackFrameHeader(), UnpackSideInfo(),
+ * and UnpackScaleFactors() (for this granule)
+ * buffer pointing to start of Huffman data in MP3 frame
+ * pointer to bit offset (0-7) indicating starting bit in buf[0]
+ * number of bits in the Huffman data section of the frame
+ * (could include padding bits)
+ * index of current granule and channel
+ *
+ * Outputs: decoded coefficients in hi->huffDecBuf[ch] (hi pointer in mp3DecInfo)
+ * updated bitOffset
+ *
+ * Return: length (in bytes) of Huffman codes
+ * bitOffset also returned in parameter (0 = MSB, 7 = LSB of
+ * byte located at buf + offset)
+ * -1 if null input pointers, huffBlockBits < 0, or decoder runs
+ * out of bits prematurely (invalid bitstream)
+ **********************************************************************************************************************/
+// .data about 1ms faster per frame
+int32_t DecodeHuffman(uint8_t *buf, int32_t *bitOffset, int32_t huffBlockBits, int32_t gr, int32_t ch){
+
+ int32_t r1Start, r2Start, rEnd[4]; /* region boundaries */
+ int32_t i, w, bitsUsed, bitsLeft;
+ uint8_t *startBuf = buf;
+
+ SideInfoSub_t *sis;
+ sis = &m_SideInfoSub[gr][ch];
+ //hi = (HuffmanInfo_t*) (m_MP3DecInfo->HuffmanInfoPS);
+
+ if (huffBlockBits < 0){
+ return -1;
+ }
+
+ /* figure out region boundaries (the first 2*bigVals coefficients divided into 3 regions) */
+ if (sis->winSwitchFlag && sis->blockType == 2) {
+ if (sis->mixedBlock == 0) {
+ r1Start = m_SFBandTable.s[(sis->region0Count + 1) / 3] * 3;
+ } else {
+ if (m_MPEGVersion == MPEG1) {
+ r1Start = m_SFBandTable.l[sis->region0Count + 1];
+ } else {
+ /* see MPEG2 spec for explanation */
+ w = m_SFBandTable.s[4] - m_SFBandTable.s[3];
+ r1Start = m_SFBandTable.l[6] + 2 * w;
+ }
+ }
+ r2Start = m_MAX_NSAMP; /* short blocks don't have region 2 */
+ } else {
+ r1Start = m_SFBandTable.l[sis->region0Count + 1];
+ r2Start = m_SFBandTable.l[sis->region0Count + 1 + sis->region1Count + 1];
+ }
+
+ /* offset rEnd index by 1 so first region = rEnd[1] - rEnd[0], etc. */
+ rEnd[3] = (m_MAX_NSAMP < (2 * sis->nBigvals) ? m_MAX_NSAMP : (2 * sis->nBigvals));
+ rEnd[2] = (r2Start < rEnd[3] ? r2Start : rEnd[3]);
+ rEnd[1] = (r1Start < rEnd[3] ? r1Start : rEnd[3]);
+ rEnd[0] = 0;
+
+ /* rounds up to first all-zero pair (we don't check last pair for (x,y) == (non-zero, zero)) */
+ m_HuffmanInfo->nonZeroBound[ch] = rEnd[3];
+
+ /* decode Huffman pairs (rEnd[i] are always even numbers) */
+ bitsLeft = huffBlockBits;
+ for (i = 0; i < 3; i++) {
+ bitsUsed = DecodeHuffmanPairs(m_HuffmanInfo->huffDecBuf[ch] + rEnd[i],
+ rEnd[i + 1] - rEnd[i], sis->tableSelect[i], bitsLeft, buf,
+ *bitOffset);
+ if (bitsUsed < 0 || bitsUsed > bitsLeft){ /* error - overran end of bitstream */
+ return -1;
+ }
+ /* update bitstream position */
+ buf += (bitsUsed + *bitOffset) >> 3;
+ *bitOffset = (bitsUsed + *bitOffset) & 0x07;
+ bitsLeft -= bitsUsed;
+ }
+
+ /* decode Huffman quads (if any) */
+ m_HuffmanInfo->nonZeroBound[ch] += DecodeHuffmanQuads(m_HuffmanInfo->huffDecBuf[ch] + rEnd[3],
+ m_MAX_NSAMP - rEnd[3], sis->count1TableSelect, bitsLeft, buf,
+ *bitOffset);
+
+ assert(m_HuffmanInfo->nonZeroBound[ch] <= m_MAX_NSAMP);
+ for (i = m_HuffmanInfo->nonZeroBound[ch]; i < m_MAX_NSAMP; i++)
+ m_HuffmanInfo->huffDecBuf[ch][i] = 0;
+
+ /* If bits used for 576 samples < huffBlockBits, then the extras are considered
+ * to be stuffing bits (throw away, but need to return correct bitstream position)
+ */
+ buf += (bitsLeft + *bitOffset) >> 3;
+ *bitOffset = (bitsLeft + *bitOffset) & 0x07;
+
+ return (buf - startBuf);
+}
+
+/***********************************************************************************************************************
+ * D E Q U A N T
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: MP3Dequantize
+ *
+ * Description: dequantize coefficients, decode stereo, reorder short blocks
+ * (one granule-worth)
+ *
+ * Inputs: index of current granule
+ *
+ * Outputs: dequantized and reordered coefficients in hi->huffDecBuf
+ * (one granule-worth, all channels), format = Q26
+ * operates in-place on huffDecBuf but also needs di->workBuf
+ * updated hi->nonZeroBound index for both channels
+ *
+ * Return: 0 on success, -1 if null input pointers
+ *
+ * Notes: In calling output Q(DQ_FRACBITS_OUT), we assume an implicit bias
+ * of 2^15. Some (floating-point) reference implementations factor this
+ * into the 2^(0.25 * gain) scaling explicitly. But to avoid precision
+ * loss, we don't do that. Instead take it into account in the final
+ * round to PCM (>> by 15 less than we otherwise would have).
+ * Equivalently, we can think of the dequantized coefficients as
+ * Q(DQ_FRACBITS_OUT - 15) with no implicit bias.
+ **********************************************************************************************************************/
+int32_t MP3Dequantize(int32_t gr){
+ int32_t i, ch, nSamps, mOut[2];
+ CriticalBandInfo_t *cbi;
+ cbi = &m_CriticalBandInfo[0];
+ mOut[0] = mOut[1] = 0;
+
+ /* dequantize all the samples in each channel */
+ for (ch = 0; ch < m_MP3DecInfo->nChans; ch++) {
+ m_HuffmanInfo->gb[ch] = DequantChannel(m_HuffmanInfo->huffDecBuf[ch], m_DequantInfo->workBuf,
+ &m_HuffmanInfo->nonZeroBound[ch], &m_SideInfoSub[gr][ch], &m_ScaleFactorInfoSub[gr][ch], &cbi[ch]);
+ }
+
+ /* joint stereo processing assumes one guard bit in input samples
+ * it's extremely rare not to have at least one gb, so if this is the case
+ * just make a pass over the data and clip to [-2^30+1, 2^30-1]
+ * in practice this may never happen
+ */
+ if (m_FrameHeader->modeExt && (m_HuffmanInfo->gb[0] < 1 || m_HuffmanInfo->gb[1] < 1)) {
+ for (i = 0; i < m_HuffmanInfo->nonZeroBound[0]; i++) {
+ if (m_HuffmanInfo->huffDecBuf[0][i] < -0x3fffffff) m_HuffmanInfo->huffDecBuf[0][i] = -0x3fffffff;
+ if (m_HuffmanInfo->huffDecBuf[0][i] > 0x3fffffff) m_HuffmanInfo->huffDecBuf[0][i] = 0x3fffffff;
+ }
+ for (i = 0; i < m_HuffmanInfo->nonZeroBound[1]; i++) {
+ if (m_HuffmanInfo->huffDecBuf[1][i] < -0x3fffffff) m_HuffmanInfo->huffDecBuf[1][i] = -0x3fffffff;
+ if (m_HuffmanInfo->huffDecBuf[1][i] > 0x3fffffff) m_HuffmanInfo->huffDecBuf[1][i] = 0x3fffffff;
+ }
+ }
+
+ /* do mid-side stereo processing, if enabled */
+ if (m_FrameHeader->modeExt >> 1) {
+ if (m_FrameHeader->modeExt & 0x01) {
+ /* intensity stereo enabled - run mid-side up to start of right zero region */
+ if (cbi[1].cbType == 0)
+ nSamps = m_SFBandTable.l[cbi[1].cbEndL + 1];
+ else
+ nSamps = 3 * m_SFBandTable.s[cbi[1].cbEndSMax + 1];
+ } else {
+ /* intensity stereo disabled - run mid-side on whole spectrum */
+ nSamps = (m_HuffmanInfo->nonZeroBound[0] > m_HuffmanInfo->nonZeroBound[1] ?
+ m_HuffmanInfo->nonZeroBound[0] : m_HuffmanInfo->nonZeroBound[1]);
+ }
+ MidSideProc(m_HuffmanInfo->huffDecBuf, nSamps, mOut);
+ }
+
+ /* do intensity stereo processing, if enabled */
+ if (m_FrameHeader->modeExt & 0x01) {
+ nSamps = m_HuffmanInfo->nonZeroBound[0];
+ if (m_MPEGVersion == MPEG1) {
+ IntensityProcMPEG1(m_HuffmanInfo->huffDecBuf, nSamps, &m_ScaleFactorInfoSub[gr][1], &m_CriticalBandInfo[0],
+ m_FrameHeader->modeExt >> 1, m_SideInfoSub[gr][1].mixedBlock, mOut);
+ } else {
+ IntensityProcMPEG2(m_HuffmanInfo->huffDecBuf, nSamps, &m_ScaleFactorInfoSub[gr][1], &m_CriticalBandInfo[0],
+ m_ScaleFactorJS, m_FrameHeader->modeExt >> 1, m_SideInfoSub[gr][1].mixedBlock, mOut);
+ }
+ }
+
+ /* adjust guard bit count and nonZeroBound if we did any stereo processing */
+ if (m_FrameHeader->modeExt) {
+ m_HuffmanInfo->gb[0] = CLZ(mOut[0]) - 1;
+ m_HuffmanInfo->gb[1] = CLZ(mOut[1]) - 1;
+ nSamps = (m_HuffmanInfo->nonZeroBound[0] > m_HuffmanInfo->nonZeroBound[1] ?
+ m_HuffmanInfo->nonZeroBound[0] : m_HuffmanInfo->nonZeroBound[1]);
+ m_HuffmanInfo->nonZeroBound[0] = nSamps;
+ m_HuffmanInfo->nonZeroBound[1] = nSamps;
+ }
+
+ /* output format Q(DQ_FRACBITS_OUT) */
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * D Q C H A N
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: DequantBlock
+ *
+ * Description: Ken's highly-optimized, low memory dequantizer performing the operation
+ * y = pow(x, 4.0/3.0) * pow(2, 25 - scale/4.0)
+ *
+ * Inputs: input buffer of decode Huffman codewords (signed-magnitude)
+ * output buffer of same length (in-place (outbuf = inbuf) is allowed)
+ * number of samples
+ *
+ * Outputs: dequantized samples in Q25 format
+ *
+ * Return: bitwise-OR of the unsigned outputs (for guard bit calculations)
+ **********************************************************************************************************************/
+int32_t DequantBlock(int32_t *inbuf, int32_t *outbuf, int32_t num, int32_t scale){
+ int32_t tab4[4];
+ int32_t scalef, scalei, shift;
+ int32_t sx, x, y;
+ int32_t mask = 0;
+ const int32_t *tab16;
+ const uint32_t *coef;
+
+ tab16 = pow43_14[scale & 0x3];
+ scalef = pow14[scale & 0x3];
+ scalei =((scale >> 2) < 31 ? (scale >> 2) : 31 );
+ //scalei = MIN(scale >> 2, 31); /* smallest input scale = -47, so smallest scalei = -12 */
+
+ /* cache first 4 values */
+ shift = (scalei + 3 < 31 ? scalei + 3 : 31);
+ shift = (shift > 0 ? shift : 0);
+
+ tab4[0] = 0;
+ tab4[1] = tab16[1] >> shift;
+ tab4[2] = tab16[2] >> shift;
+ tab4[3] = tab16[3] >> shift;
+
+ do {
+ sx = *inbuf++;
+ x = sx & 0x7fffffff; /* sx = sign|mag */
+ if (x < 4) {
+ y = tab4[x];
+ } else if (x < 16) {
+ y = tab16[x];
+ y = (scalei < 0) ? y << -scalei : y >> scalei;
+ } else {
+ if (x < 64) {
+ y = pow43[x-16];
+ /* fractional scale */
+ y = MULSHIFT32(y, scalef);
+ shift = scalei - 3;
+ } else {
+ /* normalize to [0x40000000, 0x7fffffff] */
+ x <<= 17;
+ shift = 0;
+ if (x < 0x08000000)
+ x <<= 4, shift += 4;
+ if (x < 0x20000000)
+ x <<= 2, shift += 2;
+ if (x < 0x40000000)
+ x <<= 1, shift += 1;
+
+ coef = (x < m_SQRTHALF) ? poly43lo : poly43hi;
+
+ /* polynomial */
+ y = coef[0];
+ y = MULSHIFT32(y, x) + coef[1];
+ y = MULSHIFT32(y, x) + coef[2];
+ y = MULSHIFT32(y, x) + coef[3];
+ y = MULSHIFT32(y, x) + coef[4];
+ y = MULSHIFT32(y, pow2frac[shift]) << 3;
+
+ /* fractional scale */
+ y = MULSHIFT32(y, scalef);
+ shift = scalei - pow2exp[shift];
+ }
+
+ /* integer scale */
+ if (shift < 0) {
+ shift = -shift;
+ if (y > (0x7fffffff >> shift))
+ y = 0x7fffffff; /* clip */
+ else
+ y <<= shift;
+ } else {
+ y >>= shift;
+ }
+ }
+
+ /* sign and store */
+ mask |= y;
+ *outbuf++ = (sx < 0) ? -y : y;
+
+ } while (--num);
+
+ return mask;
+}
+
+/***********************************************************************************************************************
+ * Function: DequantChannel
+ *
+ * Description: dequantize one granule, one channel worth of decoded Huffman codewords
+ *
+ * Inputs: sample buffer (decoded Huffman codewords), length = m_MAX_NSAMP samples
+ * work buffer for reordering short-block, length = m_MAX_REORDER_SAMPS
+ * samples (3 * width of largest short-block critical band)
+ * non-zero bound for this channel/granule
+ * valid FrameHeader, SideInfoSub, ScaleFactorInfoSub, and CriticalBandInfo
+ * structures for this channel/granule
+ *
+ * Outputs: MAX_NSAMP dequantized samples in sampleBuf
+ * updated non-zero bound (indicating which samples are != 0 after DQ)
+ * filled-in cbi structure indicating start and end critical bands
+ *
+ * Return: minimum number of guard bits in dequantized sampleBuf
+ *
+ * Notes: dequantized samples in Q(DQ_FRACBITS_OUT) format
+ **********************************************************************************************************************/
+int32_t DequantChannel(int32_t *sampleBuf, int32_t *workBuf, int32_t *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis,
+ CriticalBandInfo_t *cbi)
+{
+ int32_t i, j, w, cb;
+ int32_t /* cbStartL, */ cbEndL, cbStartS, cbEndS;
+ int32_t nSamps, nonZero, sfactMultiplier, gbMask;
+ int32_t globalGain, gainI;
+ int32_t cbMax[3];
+ typedef int32_t ARRAY3[3]; /* for short-block reordering */
+ ARRAY3 *buf; /* short block reorder */
+
+ /* set default start/end points for short/long blocks - will update with non-zero cb info */
+ if (sis->blockType == 2) {
+ // cbStartL = 0;
+ if (sis->mixedBlock) {
+ cbEndL = (m_MPEGVersion == MPEG1 ? 8 : 6);
+ cbStartS = 3;
+ } else {
+ cbEndL = 0;
+ cbStartS = 0;
+ }
+ cbEndS = 13;
+ } else {
+ /* long block */
+ //cbStartL = 0;
+ cbEndL = 22;
+ cbStartS = 13;
+ cbEndS = 13;
+ }
+ cbMax[2] = cbMax[1] = cbMax[0] = 0;
+ gbMask = 0;
+ i = 0;
+
+ /* sfactScale = 0 --> quantizer step size = 2
+ * sfactScale = 1 --> quantizer step size = sqrt(2)
+ * so sfactMultiplier = 2 or 4 (jump through globalGain by powers of 2 or sqrt(2))
+ */
+ sfactMultiplier = 2 * (sis->sfactScale + 1);
+
+ /* offset globalGain by -2 if midSide enabled, for 1/sqrt(2) used in MidSideProc()
+ * (DequantBlock() does 0.25 * gainI so knocking it down by two is the same as
+ * dividing every sample by sqrt(2) = multiplying by 2^-.5)
+ */
+ globalGain = sis->globalGain;
+ if (m_FrameHeader->modeExt >> 1)
+ globalGain -= 2;
+ globalGain += m_IMDCT_SCALE; /* scale everything by sqrt(2), for fast IMDCT36 */
+
+ /* long blocks */
+ for (cb = 0; cb < cbEndL; cb++) {
+
+ nonZero = 0;
+ nSamps = m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb];
+ gainI = 210 - globalGain + sfactMultiplier * (sfis->l[cb] + (sis->preFlag ? (int32_t)preTab[cb] : 0));
+
+ nonZero |= DequantBlock(sampleBuf + i, sampleBuf + i, nSamps, gainI);
+ i += nSamps;
+
+ /* update highest non-zero critical band */
+ if (nonZero)
+ cbMax[0] = cb;
+ gbMask |= nonZero;
+
+ if (i >= *nonZeroBound)
+ break;
+ }
+
+ /* set cbi (Type, EndS[], EndSMax will be overwritten if we proceed to do short blocks) */
+ cbi->cbType = 0; /* long only */
+ cbi->cbEndL = cbMax[0];
+ cbi->cbEndS[0] = cbi->cbEndS[1] = cbi->cbEndS[2] = 0;
+ cbi->cbEndSMax = 0;
+
+ /* early exit if no short blocks */
+ if (cbStartS >= 12)
+ return CLZ(gbMask) - 1;
+
+ /* short blocks */
+ cbMax[2] = cbMax[1] = cbMax[0] = cbStartS;
+ for (cb = cbStartS; cb < cbEndS; cb++) {
+
+ nSamps = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb];
+ for (w = 0; w < 3; w++) {
+ nonZero = 0;
+ gainI = 210 - globalGain + 8*sis->subBlockGain[w] + sfactMultiplier*(sfis->s[cb][w]);
+
+ nonZero |= DequantBlock(sampleBuf + i + nSamps*w, workBuf + nSamps*w, nSamps, gainI);
+
+ /* update highest non-zero critical band */
+ if (nonZero)
+ cbMax[w] = cb;
+ gbMask |= nonZero;
+ }
+
+ /* reorder blocks */
+ buf = (ARRAY3 *)(sampleBuf + i);
+ i += 3*nSamps;
+ for (j = 0; j < nSamps; j++) {
+ buf[j][0] = workBuf[0*nSamps + j];
+ buf[j][1] = workBuf[1*nSamps + j];
+ buf[j][2] = workBuf[2*nSamps + j];
+ }
+
+ assert(3*nSamps <= m_MAX_REORDER_SAMPS);
+
+ if (i >= *nonZeroBound)
+ break;
+ }
+
+ /* i = last non-zero INPUT sample processed, which corresponds to highest possible non-zero
+ * OUTPUT sample (after reorder)
+ * however, the original nzb is no longer necessarily true
+ * for each cb, buf[][] is updated with 3*nSamps samples (i increases 3*nSamps each time)
+ * (buf[j + 1][0] = 3 (input) samples ahead of buf[j][0])
+ * so update nonZeroBound to i
+ */
+ *nonZeroBound = i;
+
+ assert(*nonZeroBound <= m_MAX_NSAMP);
+
+ cbi->cbType = (sis->mixedBlock ? 2 : 1); /* 2 = mixed short/long, 1 = short only */
+
+ cbi->cbEndS[0] = cbMax[0];
+ cbi->cbEndS[1] = cbMax[1];
+ cbi->cbEndS[2] = cbMax[2];
+
+ cbi->cbEndSMax = cbMax[0];
+ cbi->cbEndSMax = (cbi->cbEndSMax > cbMax[1] ? cbi->cbEndSMax : cbMax[1]);
+ cbi->cbEndSMax = (cbi->cbEndSMax > cbMax[2] ? cbi->cbEndSMax : cbMax[2]);
+
+ return CLZ(gbMask) - 1;
+}
+
+/***********************************************************************************************************************
+ * S T P R O C
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: MidSideProc
+ *
+ * Description: sum-difference stereo reconstruction
+ *
+ * Inputs: vector x with dequantized samples from left and right channels
+ * number of non-zero samples (MAX of left and right)
+ * assume 1 guard bit in input
+ * guard bit mask (left and right channels)
+ *
+ * Outputs: updated sample vector x
+ * updated guard bit mask
+ *
+ * Return: none
+ *
+ * Notes: assume at least 1 GB in input
+ **********************************************************************************************************************/
+void MidSideProc(int32_t x[m_MAX_NCHAN][m_MAX_NSAMP], int32_t nSamps, int32_t mOut[2]){
+ int32_t i, xr, xl, mOutL, mOutR;
+
+ /* L = (M+S)/sqrt(2), R = (M-S)/sqrt(2)
+ * NOTE: 1/sqrt(2) done in DequantChannel() - see comments there
+ */
+ mOutL = mOutR = 0;
+ for (i = 0; i < nSamps; i++) {
+ xl = x[0][i];
+ xr = x[1][i];
+ x[0][i] = xl + xr;
+ x[1][i] = xl - xr;
+ mOutL |= FASTABS(x[0][i]);
+ mOutR |= FASTABS(x[1][i]);
+ }
+ mOut[0] |= mOutL;
+ mOut[1] |= mOutR;
+}
+
+/***********************************************************************************************************************
+ * Function: IntensityProcMPEG1
+ *
+ * Description: intensity stereo processing for MPEG1
+ *
+ * Inputs: vector x with dequantized samples from left and right channels
+ * number of non-zero samples in left channel
+ * valid FrameHeader struct
+ * two each of ScaleFactorInfoSub, CriticalBandInfo structs (both channels)
+ * flags indicating midSide on/off, mixedBlock on/off
+ * guard bit mask (left and right channels)
+ *
+ * Outputs: updated sample vector x
+ * updated guard bit mask
+ *
+ * Return: none
+ *
+ * Notes: assume at least 1 GB in input
+ *
+ **********************************************************************************************************************/
+void IntensityProcMPEG1(int32_t x[m_MAX_NCHAN][m_MAX_NSAMP], int32_t nSamps, ScaleFactorInfoSub_t *sfis,
+ CriticalBandInfo_t *cbi, int32_t midSideFlag, int32_t mixFlag, int32_t mOut[2])
+{
+ int32_t i = 0, j = 0, n = 0, cb = 0, w = 0;
+ int32_t sampsLeft, isf, mOutL, mOutR, xl, xr;
+ int32_t fl, fr, fls[3], frs[3];
+ int32_t cbStartL = 0, cbStartS = 0, cbEndL = 0, cbEndS = 0;
+ int32_t *isfTab;
+ (void) mixFlag;
+
+ /* NOTE - this works fine for mixed blocks, as long as the switch point starts in the
+ * short block section (i.e. on or after sample 36 = sfBand->l[8] = 3*sfBand->s[3]
+ * is this a safe assumption?
+ */
+ if (cbi[1].cbType == 0) {
+ /* long block */
+ cbStartL = cbi[1].cbEndL + 1;
+ cbEndL = cbi[0].cbEndL + 1;
+ cbStartS = cbEndS = 0;
+ i = m_SFBandTable.l[cbStartL];
+ } else if (cbi[1].cbType == 1 || cbi[1].cbType == 2) {
+ /* short or mixed block */
+ cbStartS = cbi[1].cbEndSMax + 1;
+ cbEndS = cbi[0].cbEndSMax + 1;
+ cbStartL = cbEndL = 0;
+ i = 3 * m_SFBandTable.s[cbStartS];
+ }
+ sampsLeft = nSamps - i; /* process to length of left */
+ isfTab = (int32_t *) ISFMpeg1[midSideFlag];
+ mOutL = mOutR = 0;
+
+ /* long blocks */
+ for (cb = cbStartL; cb < cbEndL && sampsLeft > 0; cb++) {
+ isf = sfis->l[cb];
+ if (isf == 7) {
+ fl = ISFIIP[midSideFlag][0];
+ fr = ISFIIP[midSideFlag][1];
+ } else {
+ fl = isfTab[isf];
+ fr = isfTab[6] - isfTab[isf];
+ }
+
+ n = m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb];
+ for (j = 0; j < n && sampsLeft > 0; j++, i++) {
+ xr = MULSHIFT32(fr, x[0][i]) << 2;
+ x[1][i] = xr;
+ mOutR |= FASTABS(xr);
+ xl = MULSHIFT32(fl, x[0][i]) << 2;
+ x[0][i] = xl;
+ mOutL |= FASTABS(xl);
+ sampsLeft--;
+ }
+ }
+ /* short blocks */
+ for (cb = cbStartS; cb < cbEndS && sampsLeft >= 3; cb++) {
+ for (w = 0; w < 3; w++) {
+ isf = sfis->s[cb][w];
+ if (isf == 7) {
+ fls[w] = ISFIIP[midSideFlag][0];
+ frs[w] = ISFIIP[midSideFlag][1];
+ } else {
+ fls[w] = isfTab[isf];
+ frs[w] = isfTab[6] - isfTab[isf];
+ }
+ }
+ n = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb];
+ for (j = 0; j < n && sampsLeft >= 3; j++, i += 3) {
+ xr = MULSHIFT32(frs[0], x[0][i + 0]) << 2;
+ x[1][i + 0] = xr;
+ mOutR |= FASTABS(xr);
+ xl = MULSHIFT32(fls[0], x[0][i + 0]) << 2;
+ x[0][i + 0] = xl;
+ mOutL |= FASTABS(xl);
+ xr = MULSHIFT32(frs[1], x[0][i + 1]) << 2;
+ x[1][i + 1] = xr;
+ mOutR |= FASTABS(xr);
+ xl = MULSHIFT32(fls[1], x[0][i + 1]) << 2;
+ x[0][i + 1] = xl;
+ mOutL |= FASTABS(xl);
+ xr = MULSHIFT32(frs[2], x[0][i + 2]) << 2;
+ x[1][i + 2] = xr;
+ mOutR |= FASTABS(xr);
+ xl = MULSHIFT32(fls[2], x[0][i + 2]) << 2;
+ x[0][i + 2] = xl;
+ mOutL |= FASTABS(xl);
+ sampsLeft -= 3;
+ }
+ }
+ mOut[0] = mOutL;
+ mOut[1] = mOutR;
+ return;
+}
+
+/***********************************************************************************************************************
+ * Function: IntensityProcMPEG2
+ *
+ * Description: intensity stereo processing for MPEG2
+ *
+ * Inputs: vector x with dequantized samples from left and right channels
+ * number of non-zero samples in left channel
+ * valid FrameHeader struct
+ * two each of ScaleFactorInfoSub, CriticalBandInfo structs (both channels)
+ * ScaleFactorJS struct with joint stereo info from UnpackSFMPEG2()
+ * flags indicating midSide on/off, mixedBlock on/off
+ * guard bit mask (left and right channels)
+ *
+ * Outputs: updated sample vector x
+ * updated guard bit mask
+ *
+ * Return: none
+ *
+ * Notes: assume at least 1 GB in input
+ *
+ **********************************************************************************************************************/
+void IntensityProcMPEG2(int32_t x[m_MAX_NCHAN][m_MAX_NSAMP], int32_t nSamps,
+ ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi,
+ ScaleFactorJS_t *sfjs, int32_t midSideFlag, int32_t mixFlag, int32_t mOut[2]) {
+ int32_t i, j, k, n, r, cb, w;
+ int32_t fl, fr, mOutL, mOutR, xl, xr;
+ int32_t sampsLeft;
+ int32_t isf, sfIdx, tmp, il[23];
+ int32_t *isfTab;
+ int32_t cbStartL, cbStartS, cbEndL, cbEndS;
+
+ (void) mixFlag;
+
+ isfTab = (int32_t *) ISFMpeg2[sfjs->intensityScale][midSideFlag];
+ mOutL = mOutR = 0;
+
+ /* fill buffer with illegal intensity positions (depending on slen) */
+ for (k = r = 0; r < 4; r++) {
+ tmp = (1 << sfjs->slen[r]) - 1;
+ for (j = 0; j < sfjs->nr[r]; j++, k++)
+ il[k] = tmp;
+ }
+
+ if (cbi[1].cbType == 0) {
+ /* long blocks */
+ il[21] = il[22] = 1;
+ cbStartL = cbi[1].cbEndL + 1; /* start at end of right */
+ cbEndL = cbi[0].cbEndL + 1; /* process to end of left */
+ i = m_SFBandTable.l[cbStartL];
+ sampsLeft = nSamps - i;
+
+ for (cb = cbStartL; cb < cbEndL; cb++) {
+ sfIdx = sfis->l[cb];
+ if (sfIdx == il[cb]) {
+ fl = ISFIIP[midSideFlag][0];
+ fr = ISFIIP[midSideFlag][1];
+ } else {
+ isf = (sfis->l[cb] + 1) >> 1;
+ fl = isfTab[(sfIdx & 0x01 ? isf : 0)];
+ fr = isfTab[(sfIdx & 0x01 ? 0 : isf)];
+ }
+ int32_t r=m_SFBandTable.l[cb + 1] - m_SFBandTable.l[cb];
+ n=(r < sampsLeft ? r : sampsLeft);
+ //n = MIN(fh->sfBand->l[cb + 1] - fh->sfBand->l[cb], sampsLeft);
+ for (j = 0; j < n; j++, i++) {
+ xr = MULSHIFT32(fr, x[0][i]) << 2;
+ x[1][i] = xr;
+ mOutR |= FASTABS(xr);
+ xl = MULSHIFT32(fl, x[0][i]) << 2;
+ x[0][i] = xl;
+ mOutL |= FASTABS(xl);
+ }
+ /* early exit once we've used all the non-zero samples */
+ sampsLeft -= n;
+ if (sampsLeft == 0)
+ break;
+ }
+ } else {
+ /* short or mixed blocks */
+ il[12] = 1;
+
+ for (w = 0; w < 3; w++) {
+ cbStartS = cbi[1].cbEndS[w] + 1; /* start at end of right */
+ cbEndS = cbi[0].cbEndS[w] + 1; /* process to end of left */
+ i = 3 * m_SFBandTable.s[cbStartS] + w;
+
+ /* skip through sample array by 3, so early-exit logic would be more tricky */
+ for (cb = cbStartS; cb < cbEndS; cb++) {
+ sfIdx = sfis->s[cb][w];
+ if (sfIdx == il[cb]) {
+ fl = ISFIIP[midSideFlag][0];
+ fr = ISFIIP[midSideFlag][1];
+ } else {
+ isf = (sfis->s[cb][w] + 1) >> 1;
+ fl = isfTab[(sfIdx & 0x01 ? isf : 0)];
+ fr = isfTab[(sfIdx & 0x01 ? 0 : isf)];
+ }
+ n = m_SFBandTable.s[cb + 1] - m_SFBandTable.s[cb];
+
+ for (j = 0; j < n; j++, i += 3) {
+ xr = MULSHIFT32(fr, x[0][i]) << 2;
+ x[1][i] = xr;
+ mOutR |= FASTABS(xr);
+ xl = MULSHIFT32(fl, x[0][i]) << 2;
+ x[0][i] = xl;
+ mOutL |= FASTABS(xl);
+ }
+ }
+ }
+ }
+ mOut[0] = mOutL;
+ mOut[1] = mOutR;
+ return;
+}
+
+/***********************************************************************************************************************
+ * I M D C T
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: AntiAlias
+ *
+ * Description: smooth transition across DCT block boundaries (every 18 coefficients)
+ *
+ * Inputs: vector of dequantized coefficients, length = (nBfly+1) * 18
+ * number of "butterflies" to perform (one butterfly means one
+ * inter-block smoothing operation)
+ *
+ * Outputs: updated coefficient vector x
+ *
+ * Return: none
+ *
+ * Notes: weighted average of opposite bands (pairwise) from the 8 samples
+ * before and after each block boundary
+ * nBlocks = (nonZeroBound + 7) / 18, since nZB is the first ZERO sample
+ * above which all other samples are also zero
+ * max gain per sample = 1.372
+ * MAX(i) (abs(csa[i][0]) + abs(csa[i][1]))
+ * bits gained = 0
+ * assume at least 1 guard bit in x[] to avoid overflow
+ * (should be guaranteed from dequant, and max gain from stproc * max
+ * gain from AntiAlias < 2.0)
+ **********************************************************************************************************************/
+// a little bit faster in RAM (< 1 ms per block)
+/* __attribute__ ((section (".data"))) */
+void AntiAlias(int32_t *x, int32_t nBfly){
+ int32_t k, a0, b0, c0, c1;
+ const uint32_t *c;
+
+ /* csa = Q31 */
+ for (k = nBfly; k > 0; k--) {
+ c = csa[0];
+ x += 18;
+ a0 = x[-1];
+ c0 = *c;
+ c++;
+ b0 = x[0];
+ c1 = *c;
+ c++;
+ x[-1] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[0] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-2];
+ c0 = *c;
+ c++;
+ b0 = x[1];
+ c1 = *c;
+ c++;
+ x[-2] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[1] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-3];
+ c0 = *c;
+ c++;
+ b0 = x[2];
+ c1 = *c;
+ c++;
+ x[-3] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[2] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-4];
+ c0 = *c;
+ c++;
+ b0 = x[3];
+ c1 = *c;
+ c++;
+ x[-4] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[3] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-5];
+ c0 = *c;
+ c++;
+ b0 = x[4];
+ c1 = *c;
+ c++;
+ x[-5] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[4] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-6];
+ c0 = *c;
+ c++;
+ b0 = x[5];
+ c1 = *c;
+ c++;
+ x[-6] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[5] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-7];
+ c0 = *c;
+ c++;
+ b0 = x[6];
+ c1 = *c;
+ c++;
+ x[-7] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[6] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+
+ a0 = x[-8];
+ c0 = *c;
+ c++;
+ b0 = x[7];
+ c1 = *c;
+ c++;
+ x[-8] = (MULSHIFT32(c0, a0) - MULSHIFT32(c1, b0)) << 1;
+ x[7] = (MULSHIFT32(c0, b0) + MULSHIFT32(c1, a0)) << 1;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: WinPrevious
+ *
+ * Description: apply specified window to second half of previous IMDCT (overlap part)
+ *
+ * Inputs: vector of 9 coefficients (xPrev)
+ *
+ * Outputs: 18 windowed output coefficients (gain 1 integer bit)
+ * window type (0, 1, 2, 3)
+ *
+ * Return: none
+ *
+ * Notes: produces 9 output samples from 18 input samples via symmetry
+ * all blocks gain at least 1 guard bit via window (long blocks get extra
+ * sign bit, short blocks can have one addition but max gain < 1.0)
+ **********************************************************************************************************************/
+
+void WinPrevious(int32_t *xPrev, int32_t *xPrevWin, int32_t btPrev){
+ int32_t i, x, *xp, *xpwLo, *xpwHi, wLo, wHi;
+ const uint32_t *wpLo, *wpHi;
+
+ xp = xPrev;
+ /* mapping (see IMDCT12x3): xPrev[0-2] = sum[6-8], xPrev[3-8] = sum[12-17] */
+ if (btPrev == 2) {
+ /* this could be reordered for minimum loads/stores */
+ wpLo = imdctWin[btPrev];
+ xPrevWin[0] = MULSHIFT32(wpLo[6], xPrev[2])
+ + MULSHIFT32(wpLo[0], xPrev[6]);
+ xPrevWin[1] = MULSHIFT32(wpLo[7], xPrev[1])
+ + MULSHIFT32(wpLo[1], xPrev[7]);
+ xPrevWin[2] = MULSHIFT32(wpLo[8], xPrev[0])
+ + MULSHIFT32(wpLo[2], xPrev[8]);
+ xPrevWin[3] = MULSHIFT32(wpLo[9], xPrev[0])
+ + MULSHIFT32(wpLo[3], xPrev[8]);
+ xPrevWin[4] = MULSHIFT32(wpLo[10], xPrev[1])
+ + MULSHIFT32(wpLo[4], xPrev[7]);
+ xPrevWin[5] = MULSHIFT32(wpLo[11], xPrev[2])
+ + MULSHIFT32(wpLo[5], xPrev[6]);
+ xPrevWin[6] = MULSHIFT32(wpLo[6], xPrev[5]);
+ xPrevWin[7] = MULSHIFT32(wpLo[7], xPrev[4]);
+ xPrevWin[8] = MULSHIFT32(wpLo[8], xPrev[3]);
+ xPrevWin[9] = MULSHIFT32(wpLo[9], xPrev[3]);
+ xPrevWin[10] = MULSHIFT32(wpLo[10], xPrev[4]);
+ xPrevWin[11] = MULSHIFT32(wpLo[11], xPrev[5]);
+ xPrevWin[12] = xPrevWin[13] = xPrevWin[14] = xPrevWin[15] =
+ xPrevWin[16] = xPrevWin[17] = 0;
+ } else {
+ /* use ARM-style pointers (*ptr++) so that ADS compiles well */
+ wpLo = imdctWin[btPrev] + 18;
+ wpHi = wpLo + 17;
+ xpwLo = xPrevWin;
+ xpwHi = xPrevWin + 17;
+ for (i = 9; i > 0; i--) {
+ x = *xp++;
+ wLo = *wpLo++;
+ wHi = *wpHi--;
+ *xpwLo++ = MULSHIFT32(wLo, x);
+ *xpwHi-- = MULSHIFT32(wHi, x);
+ }
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: FreqInvertRescale
+ *
+ * Description: do frequency inversion (odd samples of odd blocks) and rescale
+ * if necessary (extra guard bits added before IMDCT)
+ *
+ * Inputs: output vector y (18 new samples, spaced NBANDS apart)
+ * previous sample vector xPrev (9 samples)
+ * index of current block
+ * number of extra shifts added before IMDCT (usually 0)
+ *
+ * Outputs: inverted and rescaled (as necessary) outputs
+ * rescaled (as necessary) previous samples
+ *
+ * Return: updated mOut (from new outputs y)
+ **********************************************************************************************************************/
+
+int32_t FreqInvertRescale(int32_t *y, int32_t *xPrev, int32_t blockIdx, int32_t es) {
+
+ if (es == 0) {
+ /* fast case - frequency invert only (no rescaling) */
+ if (blockIdx & 0x01) {
+ y += m_NBANDS;
+ for (int32_t i = 0; i < 9; i++) {
+ *y = - *y; y += 2 * m_NBANDS;
+ }
+ }
+ return 0;
+ }
+
+ int32_t d, mOut;
+ /* undo pre-IMDCT scaling, clipping if necessary */
+ mOut = 0;
+ if (blockIdx & 0x01) {
+ /* frequency invert */
+ for (int32_t i = 0; i < 9; i++) {
+ d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS;
+ d = -*y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS;
+ d = *xPrev; CLIP_2N(d, (31 - es)); *xPrev++ = d << es;
+ }
+ } else {
+ for (int32_t i = 0; i < 9; i++) {
+ d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS;
+ d = *y; CLIP_2N(d, (31 - es)); *y = d << es; mOut |= FASTABS(*y); y += m_NBANDS;
+ d = *xPrev; CLIP_2N(d, (31 - es)); *xPrev++ = d << es;
+ }
+ }
+ return mOut;
+
+}
+
+
+/* require at least 3 guard bits in x[] to ensure no overflow */
+void idct9(int32_t *x) {
+ int32_t a1, a2, a3, a4, a5, a6, a7, a8, a9;
+ int32_t a10, a11, a12, a13, a14, a15, a16, a17, a18;
+ int32_t a19, a20, a21, a22, a23, a24, a25, a26, a27;
+ int32_t m1, m3, m5, m6, m7, m8, m9, m10, m11, m12;
+ int32_t x0, x1, x2, x3, x4, x5, x6, x7, x8;
+
+ x0 = x[0];
+ x1 = x[1];
+ x2 = x[2];
+ x3 = x[3];
+ x4 = x[4];
+ x5 = x[5];
+ x6 = x[6];
+ x7 = x[7];
+ x8 = x[8];
+
+ a1 = x0 - x6;
+ a2 = x1 - x5;
+ a3 = x1 + x5;
+ a4 = x2 - x4;
+ a5 = x2 + x4;
+ a6 = x2 + x8;
+ a7 = x1 + x7;
+
+ a8 = a6 - a5; /* ie x[8] - x[4] */
+ a9 = a3 - a7; /* ie x[5] - x[7] */
+ a10 = a2 - x7; /* ie x[1] - x[5] - x[7] */
+ a11 = a4 - x8; /* ie x[2] - x[4] - x[8] */
+
+ /* do the << 1 as constant shifts where mX is actually used (free, no stall or extra inst.) */
+ m1 = MULSHIFT32(c9_0, x3);
+ m3 = MULSHIFT32(c9_0, a10);
+ m5 = MULSHIFT32(c9_1, a5);
+ m6 = MULSHIFT32(c9_2, a6);
+ m7 = MULSHIFT32(c9_1, a8);
+ m8 = MULSHIFT32(c9_2, a5);
+ m9 = MULSHIFT32(c9_3, a9);
+ m10 = MULSHIFT32(c9_4, a7);
+ m11 = MULSHIFT32(c9_3, a3);
+ m12 = MULSHIFT32(c9_4, a9);
+
+ a12 = x[0] + (x[6] >> 1);
+ a13 = a12 + (m1 << 1);
+ a14 = a12 - (m1 << 1);
+ a15 = a1 + (a11 >> 1);
+ a16 = (m5 << 1) + (m6 << 1);
+ a17 = (m7 << 1) - (m8 << 1);
+ a18 = a16 + a17;
+ a19 = (m9 << 1) + (m10 << 1);
+ a20 = (m11 << 1) - (m12 << 1);
+
+ a21 = a20 - a19;
+ a22 = a13 + a16;
+ a23 = a14 + a16;
+ a24 = a14 + a17;
+ a25 = a13 + a17;
+ a26 = a14 - a18;
+ a27 = a13 - a18;
+
+ x0 = a22 + a19;
+ x[0] = x0;
+ x1 = a15 + (m3 << 1);
+ x[1] = x1;
+ x2 = a24 + a20;
+ x[2] = x2;
+ x3 = a26 - a21;
+ x[3] = x3;
+ x4 = a1 - a11;
+ x[4] = x4;
+ x5 = a27 + a21;
+ x[5] = x5;
+ x6 = a25 - a20;
+ x[6] = x6;
+ x7 = a15 - (m3 << 1);
+ x[7] = x7;
+ x8 = a23 - a19;
+ x[8] = x8;
+}
+
+
+/***********************************************************************************************************************
+ * Function: IMDCT36
+ *
+ * Description: 36-point modified DCT, with windowing and overlap-add (50% overlap)
+ *
+ * Inputs: vector of 18 coefficients (N/2 inputs produces N outputs, by symmetry)
+ * overlap part of last IMDCT (9 samples - see output comments)
+ * window type (0,1,2,3) of current and previous block
+ * current block index (for deciding whether to do frequency inversion)
+ * number of guard bits in input vector
+ *
+ * Outputs: 18 output samples, after windowing and overlap-add with last frame
+ * second half of (unwindowed) 36-point IMDCT - save for next time
+ * only save 9 xPrev samples, using symmetry (see WinPrevious())
+ *
+ * Notes: this is Ken's hyper-fast algorithm, including symmetric sin window
+ * optimization, if applicable
+ * total number of multiplies, general case:
+ * 2*10 (idct9) + 9 (last stage imdct) + 36 (for windowing) = 65
+ * total number of multiplies, btCurr == 0 && btPrev == 0:
+ * 2*10 (idct9) + 9 (last stage imdct) + 18 (for windowing) = 47
+ *
+ * blockType == 0 is by far the most common case, so it should be
+ * possible to use the fast path most of the time
+ * this is the fastest known algorithm for performing
+ * long IMDCT + windowing + overlap-add in MP3
+ *
+ * Return: mOut (OR of abs(y) for all y calculated here)
+ **********************************************************************************************************************/
+// barely faster in RAM
+
+int32_t IMDCT36(int32_t *xCurr, int32_t *xPrev, int32_t *y, int32_t btCurr, int32_t btPrev, int32_t blockIdx, int32_t gb){
+ int32_t i, es, xBuf[18], xPrevWin[18];
+ int32_t acc1, acc2, s, d, t, mOut;
+ int32_t xo, xe, c, *xp, yLo, yHi;
+ const uint32_t *cp, *wp;
+ acc1 = acc2 = 0;
+ xCurr += 17;
+ /* 7 gb is always adequate for antialias + accumulator loop + idct9 */
+ if (gb < 7) {
+ /* rarely triggered - 5% to 10% of the time on normal clips (with Q25 input) */
+ es = 7 - gb;
+ for (i = 8; i >= 0; i--) {
+ acc1 = ((*xCurr--) >> es) - acc1;
+ acc2 = acc1 - acc2;
+ acc1 = ((*xCurr--) >> es) - acc1;
+ xBuf[i + 9] = acc2; /* odd */
+ xBuf[i + 0] = acc1; /* even */
+ xPrev[i] >>= es;
+ }
+ } else {
+ es = 0;
+ /* max gain = 18, assume adequate guard bits */
+ for (i = 8; i >= 0; i--) {
+ acc1 = (*xCurr--) - acc1;
+ acc2 = acc1 - acc2;
+ acc1 = (*xCurr--) - acc1;
+ xBuf[i + 9] = acc2; /* odd */
+ xBuf[i + 0] = acc1; /* even */
+ }
+ }
+ /* xEven[0] and xOdd[0] scaled by 0.5 */
+ xBuf[9] >>= 1;
+ xBuf[0] >>= 1;
+
+ /* do 9-point IDCT on even and odd */
+ idct9(xBuf + 0); /* even */
+ idct9(xBuf + 9); /* odd */
+
+ xp = xBuf + 8;
+ cp = c18 + 8;
+ mOut = 0;
+ if (btPrev == 0 && btCurr == 0) {
+ /* fast path - use symmetry of sin window to reduce windowing multiplies to 18 (N/2) */
+ wp = fastWin36;
+ for (i = 0; i < 9; i++) {
+ /* do ARM-style pointer arithmetic (i still needed for y[] indexing - compiler spills if 2 y pointers) */
+ c = *cp--;
+ xo = *(xp + 9);
+ xe = *xp--;
+ /* gain 2int32_t bits here */
+ xo = MULSHIFT32(c, xo); /* 2*c18*xOdd (mul by 2 implicit in scaling) */
+ xe >>= 2;
+
+ s = -(*xPrev); /* sum from last block (always at least 2 guard bits) */
+ d = -(xe - xo); /* gain 2int32_t bits, don't shift xo (effective << 1 to eat sign bit, << 1 for mul by 2) */
+ (*xPrev++) = xe + xo; /* symmetry - xPrev[i] = xPrev[17-i] for long blocks */
+ t = s - d;
+
+ yLo = (d + (MULSHIFT32(t, *wp++) << 2));
+ yHi = (s + (MULSHIFT32(t, *wp++) << 2));
+ y[(i) * m_NBANDS] = yLo;
+ y[(17 - i) * m_NBANDS] = yHi;
+ mOut |= FASTABS(yLo);
+ mOut |= FASTABS(yHi);
+ }
+ } else {
+ /* slower method - either prev or curr is using window type != 0 so do full 36-point window
+ * output xPrevWin has at least 3 guard bits (xPrev has 2, gain 1 in WinPrevious)
+ */
+ WinPrevious(xPrev, xPrevWin, btPrev);
+
+ wp = imdctWin[btCurr];
+ for (i = 0; i < 9; i++) {
+ c = *cp--;
+ xo = *(xp + 9);
+ xe = *xp--;
+ /* gain 2int32_t bits here */
+ xo = MULSHIFT32(c, xo); /* 2*c18*xOdd (mul by 2 implicit in scaling) */
+ xe >>= 2;
+
+ d = xe - xo;
+ (*xPrev++) = xe + xo; /* symmetry - xPrev[i] = xPrev[17-i] for long blocks */
+
+ yLo = (xPrevWin[i] + MULSHIFT32(d, wp[i])) << 2;
+ yHi = (xPrevWin[17 - i] + MULSHIFT32(d, wp[17 - i])) << 2;
+ y[(i) * m_NBANDS] = yLo;
+ y[(17 - i) * m_NBANDS] = yHi;
+ mOut |= FASTABS(yLo);
+ mOut |= FASTABS(yHi);
+ }
+ }
+
+ xPrev -= 9;
+ mOut |= FreqInvertRescale(y, xPrev, blockIdx, es);
+
+ return mOut;
+}
+
+
+
+/* 12-point inverse DCT, used in IMDCT12x3()
+ * 4 input guard bits will ensure no overflow
+ */
+void imdct12(int32_t *x, int32_t *out) {
+ int32_t a0, a1, a2;
+ int32_t x0, x1, x2, x3, x4, x5;
+
+ x0 = *x;
+ x += 3;
+ x1 = *x;
+ x += 3;
+ x2 = *x;
+ x += 3;
+ x3 = *x;
+ x += 3;
+ x4 = *x;
+ x += 3;
+ x5 = *x;
+ x += 3;
+
+ x4 -= x5;
+ x3 -= x4;
+ x2 -= x3;
+ x3 -= x5;
+ x1 -= x2;
+ x0 -= x1;
+ x1 -= x3;
+
+ x0 >>= 1;
+ x1 >>= 1;
+
+ a0 = MULSHIFT32(c3_0, x2) << 1;
+ a1 = x0 + (x4 >> 1);
+ a2 = x0 - x4;
+ x0 = a1 + a0;
+ x2 = a2;
+ x4 = a1 - a0;
+
+ a0 = MULSHIFT32(c3_0, x3) << 1;
+ a1 = x1 + (x5 >> 1);
+ a2 = x1 - x5;
+
+ /* cos window odd samples, mul by 2, eat sign bit */
+ x1 = MULSHIFT32(c6[0], a1 + a0) << 2;
+ x3 = MULSHIFT32(c6[1], a2) << 2;
+ x5 = MULSHIFT32(c6[2], a1 - a0) << 2;
+
+ *out = x0 + x1;
+ out++;
+ *out = x2 + x3;
+ out++;
+ *out = x4 + x5;
+ out++;
+ *out = x4 - x5;
+ out++;
+ *out = x2 - x3;
+ out++;
+ *out = x0 - x1;
+}
+
+/***********************************************************************************************************************
+ * Function: IMDCT12x3
+ *
+ * Description: three 12-point modified DCT's for short blocks, with windowing,
+ * short block concatenation, and overlap-add
+ *
+ * Inputs: 3 interleaved vectors of 6 samples each
+ * (block0[0], block1[0], block2[0], block0[1], block1[1]....)
+ * overlap part of last IMDCT (9 samples - see output comments)
+ * window type (0,1,2,3) of previous block
+ * current block index (for deciding whether to do frequency inversion)
+ * number of guard bits in input vector
+ *
+ * Outputs: updated sample vector x, net gain of 1 integer bit
+ * second half of (unwindowed) IMDCT's - save for next time
+ * only save 9 xPrev samples, using symmetry (see WinPrevious())
+ *
+ * Return: mOut (OR of abs(y) for all y calculated here)
+ **********************************************************************************************************************/
+// barely faster in RAM
+int32_t IMDCT12x3(int32_t *xCurr, int32_t *xPrev, int32_t *y, int32_t btPrev, int32_t blockIdx, int32_t gb){
+ int32_t i, es, mOut, yLo, xBuf[18], xPrevWin[18]; /* need temp buffer for reordering short blocks */
+ const uint32_t *wp;
+ es = 0;
+ /* 7 gb is always adequate for accumulator loop + idct12 + window + overlap */
+ if (gb < 7) {
+ es = 7 - gb;
+ for (i = 0; i < 18; i += 2) {
+ xCurr[i + 0] >>= es;
+ xCurr[i + 1] >>= es;
+ *xPrev++ >>= es;
+ }
+ xPrev -= 9;
+ }
+
+ /* requires 4 input guard bits for each imdct12 */
+ imdct12(xCurr + 0, xBuf + 0);
+ imdct12(xCurr + 1, xBuf + 6);
+ imdct12(xCurr + 2, xBuf + 12);
+
+ /* window previous from last time */
+ WinPrevious(xPrev, xPrevWin, btPrev);
+
+ /* could unroll this for speed, minimum loads (short blocks usually rare, so doesn't make much overall difference)
+ * xPrevWin[i] << 2 still has 1 gb always, max gain of windowed xBuf stuff also < 1.0 and gain the sign bit
+ * so y calculations won't overflow
+ */
+ wp = imdctWin[2];
+ mOut = 0;
+ for (i = 0; i < 3; i++) {
+ yLo = (xPrevWin[0 + i] << 2);
+ mOut |= FASTABS(yLo);
+ y[(0 + i) * m_NBANDS] = yLo;
+ yLo = (xPrevWin[3 + i] << 2);
+ mOut |= FASTABS(yLo);
+ y[(3 + i) * m_NBANDS] = yLo;
+ yLo = (xPrevWin[6 + i] << 2) + (MULSHIFT32(wp[0 + i], xBuf[3 + i]));
+ mOut |= FASTABS(yLo);
+ y[(6 + i) * m_NBANDS] = yLo;
+ yLo = (xPrevWin[9 + i] << 2) + (MULSHIFT32(wp[3 + i], xBuf[5 - i]));
+ mOut |= FASTABS(yLo);
+ y[(9 + i) * m_NBANDS] = yLo;
+ yLo = (xPrevWin[12 + i] << 2)
+ + (MULSHIFT32(wp[6 + i], xBuf[2 - i])
+ + MULSHIFT32(wp[0 + i], xBuf[(6 + 3) + i]));
+ mOut |= FASTABS(yLo);
+ y[(12 + i) * m_NBANDS] = yLo;
+ yLo = (xPrevWin[15 + i] << 2)
+ + (MULSHIFT32(wp[9 + i], xBuf[0 + i])
+ + MULSHIFT32(wp[3 + i], xBuf[(6 + 5) - i]));
+ mOut |= FASTABS(yLo);
+ y[(15 + i) * m_NBANDS] = yLo;
+ }
+
+ /* save previous (unwindowed) for overlap - only need samples 6-8, 12-17 */
+ for (i = 6; i < 9; i++)
+ *xPrev++ = xBuf[i] >> 2;
+ for (i = 12; i < 18; i++)
+ *xPrev++ = xBuf[i] >> 2;
+
+ xPrev -= 9;
+ mOut |= FreqInvertRescale(y, xPrev, blockIdx, es);
+
+ return mOut;
+}
+
+/***********************************************************************************************************************
+ * Function: HybridTransform
+ *
+ * Description: IMDCT's, windowing, and overlap-add on long/short/mixed blocks
+ *
+ * Inputs: vector of input coefficients, length = nBlocksTotal * 18)
+ * vector of overlap samples from last time, length = nBlocksPrev * 9)
+ * buffer for output samples, length = MAXNSAMP
+ * SideInfoSub struct for this granule/channel
+ * BlockCount struct with necessary info
+ * number of non-zero input and overlap blocks
+ * number of long blocks in input vector (rest assumed to be short blocks)
+ * number of blocks which use long window (type) 0 in case of mixed block
+ * (bc->currWinSwitch, 0 for non-mixed blocks)
+ *
+ * Outputs: transformed, windowed, and overlapped sample buffer
+ * does frequency inversion on odd blocks
+ * updated buffer of samples for overlap
+ *
+ * Return: number of non-zero IMDCT blocks calculated in this call
+ * (including overlap-add)
+ **********************************************************************************************************************/
+int32_t HybridTransform(int32_t *xCurr, int32_t *xPrev, int32_t y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc){
+ int32_t xPrevWin[18], currWinIdx, prevWinIdx;
+ int32_t i, j, nBlocksOut, nonZero, mOut;
+ int32_t fiBit, xp;
+
+ assert(bc->nBlocksLong <= m_NBANDS);
+ assert(bc->nBlocksTotal <= m_NBANDS);
+ assert(bc->nBlocksPrev <= m_NBANDS);
+
+ mOut = 0;
+
+ /* do long blocks, if any */
+ for (i = 0; i < bc->nBlocksLong; i++) {
+ /* currWinIdx picks the right window for long blocks (if mixed, long blocks use window type 0) */
+ currWinIdx = sis->blockType;
+ if (sis->mixedBlock && i < bc->currWinSwitch)
+ currWinIdx = 0;
+
+ prevWinIdx = bc->prevType;
+ if (i < bc->prevWinSwitch)
+ prevWinIdx = 0;
+
+ /* do 36-point IMDCT, including windowing and overlap-add */
+ mOut |= IMDCT36(xCurr, xPrev, &(y[0][i]), currWinIdx, prevWinIdx, i,
+ bc->gbIn);
+ xCurr += 18;
+ xPrev += 9;
+ }
+
+ /* do short blocks (if any) */
+ for (; i < bc->nBlocksTotal; i++) {
+ assert(sis->blockType == 2);
+
+ prevWinIdx = bc->prevType;
+ if (i < bc->prevWinSwitch)
+ prevWinIdx = 0;
+
+ mOut |= IMDCT12x3(xCurr, xPrev, &(y[0][i]), prevWinIdx, i, bc->gbIn);
+ xCurr += 18;
+ xPrev += 9;
+ }
+ nBlocksOut = i;
+
+ /* window and overlap prev if prev longer that current */
+ for (; i < bc->nBlocksPrev; i++) {
+ prevWinIdx = bc->prevType;
+ if (i < bc->prevWinSwitch)
+ prevWinIdx = 0;
+ WinPrevious(xPrev, xPrevWin, prevWinIdx);
+
+ nonZero = 0;
+ fiBit = i << 31;
+ for (j = 0; j < 9; j++) {
+ xp = xPrevWin[2 * j + 0] << 2; /* << 2 temp for scaling */
+ nonZero |= xp;
+ y[2 * j + 0][i] = xp;
+ mOut |= FASTABS(xp);
+
+ /* frequency inversion on odd blocks/odd samples (flip sign if i odd, j odd) */
+ xp = xPrevWin[2 * j + 1] << 2;
+ xp = (xp ^ (fiBit >> 31)) + (i & 0x01);
+ nonZero |= xp;
+ y[2 * j + 1][i] = xp;
+ mOut |= FASTABS(xp);
+
+ xPrev[j] = 0;
+ }
+ xPrev += 9;
+ if (nonZero)
+ nBlocksOut = i;
+ }
+
+ /* clear rest of blocks */
+ for (; i < 32; i++) {
+ for (j = 0; j < 18; j++)
+ y[j][i] = 0;
+ }
+
+ bc->gbOut = CLZ(mOut) - 1;
+
+ return nBlocksOut;
+}
+
+/***********************************************************************************************************************
+ * Function: IMDCT
+ *
+ * Description: do alias reduction, inverse MDCT, overlap-add, and frequency inversion
+ *
+ * Inputs: MP3DecInfo structure filled by UnpackFrameHeader(), UnpackSideInfo(),
+ * UnpackScaleFactors(), and DecodeHuffman() (for this granule, channel)
+ * includes PCM samples in overBuf (from last call to IMDCT) for OLA
+ * index of current granule and channel
+ *
+ * Outputs: PCM samples in outBuf, for input to subband transform
+ * PCM samples in overBuf, for OLA next time
+ * updated hi->nonZeroBound index for this channel
+ *
+ * Return: 0 on success, -1 if null input pointers
+ **********************************************************************************************************************/
+// a bit faster in RAM
+/*__attribute__ ((section (".data")))*/
+int32_t IMDCT(int32_t gr, int32_t ch) {
+ int32_t nBfly, blockCutoff;
+ BlockCount_t bc;
+
+ /* m_SideInfo is an array of up to 4 structs, stored as gr0ch0, gr0ch1, gr1ch0, gr1ch1 */
+ /* anti-aliasing done on whole long blocks only
+ * for mixed blocks, nBfly always 1, except 3 for 8 kHz MPEG 2.5 (see sfBandTab)
+ * nLongBlocks = number of blocks with (possibly) non-zero power
+ * nBfly = number of butterflies to do (nLongBlocks - 1, unless no long blocks)
+ */
+ blockCutoff = m_SFBandTable.l[(m_MPEGVersion == MPEG1 ? 8 : 6)] / 18; /* same as 3* num short sfb's in spec */
+ if (m_SideInfoSub[gr][ch].blockType != 2) {
+ /* all long transforms */
+ int32_t x=(m_HuffmanInfo->nonZeroBound[ch] + 7) / 18 + 1;
+ bc.nBlocksLong=(x<32 ? x : 32);
+ //bc.nBlocksLong = min((hi->nonZeroBound[ch] + 7) / 18 + 1, 32);
+ nBfly = bc.nBlocksLong - 1;
+ } else if (m_SideInfoSub[gr][ch].blockType == 2 && m_SideInfoSub[gr][ch].mixedBlock) {
+ /* mixed block - long transforms until cutoff, then short transforms */
+ bc.nBlocksLong = blockCutoff;
+ nBfly = bc.nBlocksLong - 1;
+ } else {
+ /* all short transforms */
+ bc.nBlocksLong = 0;
+ nBfly = 0;
+ }
+
+ AntiAlias(m_HuffmanInfo->huffDecBuf[ch], nBfly);
+ int32_t x=m_HuffmanInfo->nonZeroBound[ch];
+ int32_t y=nBfly * 18 + 8;
+ m_HuffmanInfo->nonZeroBound[ch]=(x>y ? x: y);
+
+ assert(m_HuffmanInfo->nonZeroBound[ch] <= m_MAX_NSAMP);
+
+ /* for readability, use a struct instead of passing a million parameters to HybridTransform() */
+ bc.nBlocksTotal = (m_HuffmanInfo->nonZeroBound[ch] + 17) / 18;
+ bc.nBlocksPrev = m_IMDCTInfo->numPrevIMDCT[ch];
+ bc.prevType = m_IMDCTInfo->prevType[ch];
+ bc.prevWinSwitch = m_IMDCTInfo->prevWinSwitch[ch];
+ /* where WINDOW switches (not nec. transform) */
+ bc.currWinSwitch = (m_SideInfoSub[gr][ch].mixedBlock ? blockCutoff : 0);
+ bc.gbIn = m_HuffmanInfo->gb[ch];
+
+ m_IMDCTInfo->numPrevIMDCT[ch] = HybridTransform(m_HuffmanInfo->huffDecBuf[ch], m_IMDCTInfo->overBuf[ch],
+ m_IMDCTInfo->outBuf[ch], &m_SideInfoSub[gr][ch], &bc);
+ m_IMDCTInfo->prevType[ch] = m_SideInfoSub[gr][ch].blockType;
+ m_IMDCTInfo->prevWinSwitch[ch] = bc.currWinSwitch; /* 0 means not a mixed block (either all short or all long) */
+ m_IMDCTInfo->gb[ch] = bc.gbOut;
+
+ assert(m_IMDCTInfo->numPrevIMDCT[ch] <= m_NBANDS);
+
+ /* output has gained 2int32_t bits */
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * S U B B A N D
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: Subband
+ *
+ * Description: do subband transform on all the blocks in one granule, all channels
+ *
+ * Inputs: filled MP3DecInfo structure, after calling IMDCT for all channels
+ * vbuf[ch] and vindex[ch] must be preserved between calls
+ *
+ * Outputs: decoded PCM data, interleaved LRLRLR... if stereo
+ *
+ * Return: 0 on success, -1 if null input pointers
+ **********************************************************************************************************************/
+int32_t Subband(int16_t *pcmBuf) {
+ int32_t b;
+ if (m_MP3DecInfo->nChans == 2) {
+ /* stereo */
+ for (b = 0; b < m_BLOCK_SIZE; b++) {
+ FDCT32(m_IMDCTInfo->outBuf[0][b], m_SubbandInfo->vbuf + 0 * 32, m_SubbandInfo->vindex,
+ (b & 0x01), m_IMDCTInfo->gb[0]);
+ FDCT32(m_IMDCTInfo->outBuf[1][b], m_SubbandInfo->vbuf + 1 * 32, m_SubbandInfo->vindex,
+ (b & 0x01), m_IMDCTInfo->gb[1]);
+ PolyphaseStereo(pcmBuf,
+ m_SubbandInfo->vbuf + m_SubbandInfo->vindex + m_VBUF_LENGTH * (b & 0x01),
+ polyCoef);
+ m_SubbandInfo->vindex = (m_SubbandInfo->vindex - (b & 0x01)) & 7;
+ pcmBuf += (2 * m_NBANDS);
+ }
+ } else {
+ /* mono */
+ for (b = 0; b < m_BLOCK_SIZE; b++) {
+ FDCT32(m_IMDCTInfo->outBuf[0][b], m_SubbandInfo->vbuf + 0 * 32, m_SubbandInfo->vindex,
+ (b & 0x01), m_IMDCTInfo->gb[0]);
+ PolyphaseMono(pcmBuf, m_SubbandInfo->vbuf + m_SubbandInfo->vindex + m_VBUF_LENGTH * (b & 0x01), polyCoef);
+ m_SubbandInfo->vindex = (m_SubbandInfo->vindex - (b & 0x01)) & 7;
+ pcmBuf += m_NBANDS;
+ }
+ }
+
+ return 0;
+}
+
+/***********************************************************************************************************************
+ * D C T 3 2
+ **********************************************************************************************************************/
+
+/***********************************************************************************************************************
+ * Function: FDCT32
+ *
+ * Description: Ken's highly-optimized 32-point DCT (radix-4 + radix-8)
+ *
+ * Inputs: input buffer, length = 32 samples
+ * require at least 6 guard bits in input vector x to avoid possibility
+ * of overflow in internal calculations (see bbtest_imdct test app)
+ * buffer offset and oddblock flag for polyphase filter input buffer
+ * number of guard bits in input
+ *
+ * Outputs: output buffer, data copied and interleaved for polyphase filter
+ * no guarantees about number of guard bits in output
+ *
+ * Return: none
+ *
+ * Notes: number of muls = 4*8 + 12*4 = 80
+ * final stage of DCT is hardcoded to shuffle data into the proper order
+ * for the polyphase filterbank
+ * fully unrolled stage 1, for max precision (scale the 1/cos() factors
+ * differently, depending on magnitude)
+ * guard bit analysis verified by exhaustive testing of all 2^32
+ * combinations of max pos/max neg values in x[]
+ **********************************************************************************************************************/
+#define D32FP(i, s1, s2) { \
+ a0 = buf[i]; a3 = buf[31-i]; \
+ a1 = buf[15-i]; a2 = buf[16+i]; \
+ b0 = a0 + a3; b3 = MULSHIFT32(*cptr++, a0 - a3) << 1; \
+ b1 = a1 + a2; b2 = MULSHIFT32(*cptr++, a1 - a2) << (s1); \
+ buf[i] = b0 + b1; buf[15-i] = MULSHIFT32(*cptr, b0 - b1) << (s2); \
+ buf[16+i] = b2 + b3; buf[31-i] = MULSHIFT32(*cptr++, b3 - b2) << (s2); \
+}
+
+static const uint8_t FDCT32s1s2[16] = {5,3,3,2,2,1,1,1, 1,1,1,1,1,2,2,4};
+
+void FDCT32(int32_t *buf, int32_t *dest, int32_t offset, int32_t oddBlock, int32_t gb) {
+ int32_t i, s, tmp, es;
+ const int32_t *cptr = (const int32_t*)m_dcttab;
+ int32_t a0, a1, a2, a3, a4, a5, a6, a7;
+ int32_t b0, b1, b2, b3, b4, b5, b6, b7;
+ int32_t *d;
+
+ /* scaling - ensure at least 6 guard bits for DCT
+ * (in practice this is already true 99% of time, so this code is
+ * almost never triggered)
+ */
+ es = 0;
+ if (gb < 6) {
+ es = 6 - gb;
+ for (i = 0; i < 32; i++)
+ buf[i] >>= es;
+ }
+
+ /* first pass */
+ for (unsigned i=0; i < 8; i++) {
+ D32FP(i, FDCT32s1s2[0 + i], FDCT32s1s2[8 + i]);
+ }
+
+ /* second pass */
+ for (i = 4; i > 0; i--) {
+ a0 = buf[0]; a7 = buf[7]; a3 = buf[3]; a4 = buf[4];
+ b0 = a0 + a7; b7 = MULSHIFT32(*cptr++, a0 - a7) << 1;
+ b3 = a3 + a4; b4 = MULSHIFT32(*cptr++, a3 - a4) << 3;
+ a0 = b0 + b3; a3 = MULSHIFT32(*cptr, b0 - b3) << 1;
+ a4 = b4 + b7; a7 = MULSHIFT32(*cptr++, b7 - b4) << 1;
+
+ a1 = buf[1]; a6 = buf[6]; a2 = buf[2]; a5 = buf[5];
+ b1 = a1 + a6; b6 = MULSHIFT32(*cptr++, a1 - a6) << 1;
+ b2 = a2 + a5; b5 = MULSHIFT32(*cptr++, a2 - a5) << 1;
+ a1 = b1 + b2; a2 = MULSHIFT32(*cptr, b1 - b2) << 2;
+ a5 = b5 + b6; a6 = MULSHIFT32(*cptr++, b6 - b5) << 2;
+
+ b0 = a0 + a1; b1 = MULSHIFT32(m_COS4_0, a0 - a1) << 1;
+ b2 = a2 + a3; b3 = MULSHIFT32(m_COS4_0, a3 - a2) << 1;
+ buf[0] = b0; buf[1] = b1;
+ buf[2] = b2 + b3; buf[3] = b3;
+
+ b4 = a4 + a5; b5 = MULSHIFT32(m_COS4_0, a4 - a5) << 1;
+ b6 = a6 + a7; b7 = MULSHIFT32(m_COS4_0, a7 - a6) << 1;
+ b6 += b7;
+ buf[4] = b4 + b6; buf[5] = b5 + b7;
+ buf[6] = b5 + b6; buf[7] = b7;
+
+ buf += 8;
+ }
+ buf -= 32; /* reset */
+
+ /* sample 0 - always delayed one block */
+ d = dest + 64*16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH);
+ s = buf[ 0]; d[0] = d[8] = s;
+
+ /* samples 16 to 31 */
+ d = dest + offset + (oddBlock ? m_VBUF_LENGTH : 0);
+
+ s = buf[ 1]; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[25] + buf[29];
+ s = buf[17] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[ 9] + buf[13]; d[0] = d[8] = s; d += 64;
+ s = buf[21] + tmp; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[29] + buf[27];
+ s = buf[ 5]; d[0] = d[8] = s; d += 64;
+ s = buf[21] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[13] + buf[11]; d[0] = d[8] = s; d += 64;
+ s = buf[19] + tmp; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[27] + buf[31];
+ s = buf[ 3]; d[0] = d[8] = s; d += 64;
+ s = buf[19] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[11] + buf[15]; d[0] = d[8] = s; d += 64;
+ s = buf[23] + tmp; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[31];
+ s = buf[ 7]; d[0] = d[8] = s; d += 64;
+ s = buf[23] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[15]; d[0] = d[8] = s; d += 64;
+ s = tmp; d[0] = d[8] = s;
+
+ /* samples 16 to 1 (sample 16 used again) */
+ d = dest + 16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH);
+
+ s = buf[ 1]; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[30] + buf[25];
+ s = buf[17] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[14] + buf[ 9]; d[0] = d[8] = s; d += 64;
+ s = buf[22] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[ 6]; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[26] + buf[30];
+ s = buf[22] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[10] + buf[14]; d[0] = d[8] = s; d += 64;
+ s = buf[18] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[ 2]; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[28] + buf[26];
+ s = buf[18] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[12] + buf[10]; d[0] = d[8] = s; d += 64;
+ s = buf[20] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[ 4]; d[0] = d[8] = s; d += 64;
+
+ tmp = buf[24] + buf[28];
+ s = buf[20] + tmp; d[0] = d[8] = s; d += 64;
+ s = buf[ 8] + buf[12]; d[0] = d[8] = s; d += 64;
+ s = buf[16] + tmp; d[0] = d[8] = s;
+
+ /* this is so rarely invoked that it's not worth making two versions of the output
+ * shuffle code (one for no shift, one for clip + variable shift) like in IMDCT
+ * here we just load, clip, shift, and store on the rare instances that es != 0
+ */
+ if (es) {
+ d = dest + 64*16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH);
+ s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es);
+
+ d = dest + offset + (oddBlock ? m_VBUF_LENGTH : 0);
+ for (i = 16; i <= 31; i++) {
+ s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); d += 64;
+ }
+
+ d = dest + 16 + ((offset - oddBlock) & 7) + (oddBlock ? 0 : m_VBUF_LENGTH);
+ for (i = 15; i >= 0; i--) {
+ s = d[0]; CLIP_2N(s, (31 - es)); d[0] = d[8] = (s << es); d += 64;
+ }
+ }
+}
+
+/***********************************************************************************************************************
+ * P O L Y P H A S E
+ **********************************************************************************************************************/
+inline
+short ClipToShort(int32_t x, int32_t fracBits){
+
+ /* assumes you've already rounded (x += (1 << (fracBits-1))) */
+ x >>= fracBits;
+
+#ifndef __XTENSA__
+ /* Ken's trick: clips to [-32768, 32767] */
+ //ok vor generic case (fb)
+ int32_t sign = x >> 31;
+ if (sign != (x >> 15))
+ x = sign ^ ((1 << 15) - 1);
+
+ return (short)x;
+#else
+ //this is better on xtensa (fb)
+ asm ("clamps %0, %1, 15" : "=a" (x) : "a" (x) : );
+ return x;
+#endif
+}
+/***********************************************************************************************************************
+ * Function: PolyphaseMono
+ *
+ * Description: filter one subband and produce 32 output PCM samples for one channel
+ *
+ * Inputs: pointer to PCM output buffer
+ * number of "extra shifts" (vbuf format = Q(DQ_FRACBITS_OUT-2))
+ * pointer to start of vbuf (preserved from last call)
+ * start of filter coefficient table (in proper, shuffled order)
+ * no minimum number of guard bits is required for input vbuf
+ * (see additional scaling comments below)
+ *
+ * Outputs: 32 samples of one channel of decoded PCM data, (i.e. Q16.0)
+ *
+ * Return: none
+ **********************************************************************************************************************/
+void PolyphaseMono(int16_t *pcm, int32_t *vbuf, const uint32_t *coefBase){
+ int32_t i;
+ const uint32_t *coef;
+ int32_t *vb1;
+ int32_t vLo, vHi, c1, c2;
+ uint64_t sum1L, sum2L, rndVal;
+
+ rndVal = (uint64_t)( 1ULL << ((m_DQ_FRACBITS_OUT - 2 - 2 - 15) - 1 + (32 - m_CSHIFT)) );
+
+ /* special case, output sample 0 */
+ coef = coefBase;
+ vb1 = vbuf;
+ sum1L = rndVal;
+ for(int32_t j=0; j<8; j++){
+ c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi=*(vb1+(23-(j))); // 0...7
+ sum1L=MADD64(sum1L, vLo, c1); sum1L=MADD64(sum1L, vHi, -c2);
+ }
+ *(pcm + 0) = ClipToShort((int32_t)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+
+ /* special case, output sample 16 */
+ coef = coefBase + 256;
+ vb1 = vbuf + 64*16;
+ sum1L = rndVal;
+ for(int32_t j=0; j<8; j++){
+ c1=*coef; coef++; vLo=*(vb1+(j)); sum1L = MADD64(sum1L, vLo, c1); // 0...7
+ }
+ *(pcm + 16) = ClipToShort((int32_t)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+
+ /* main convolution loop: sum1L = samples 1, 2, 3, ... 15 sum2L = samples 31, 30, ... 17 */
+ coef = coefBase + 16;
+ vb1 = vbuf + 64;
+ pcm++;
+
+ /* right now, the compiler creates bad asm from this... */
+ for (i = 15; i > 0; i--) {
+ sum1L = sum2L = rndVal;
+ for(int32_t j=0; j<8; j++){
+ c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j)));
+ sum1L=MADD64(sum1L, vLo, c1); sum2L = MADD64(sum2L, vLo, c2);
+ sum1L=MADD64(sum1L, vHi, -c2); sum2L = MADD64(sum2L, vHi, c1);
+ }
+ vb1 += 64;
+ *(pcm) = ClipToShort((int32_t)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ *(pcm + 2*i) = ClipToShort((int32_t)SAR64(sum2L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ pcm++;
+ }
+}
+/***********************************************************************************************************************
+ * Function: PolyphaseStereo
+ *
+ * Description: filter one subband and produce 32 output PCM samples for each channel
+ *
+ * Inputs: pointer to PCM output buffer
+ * number of "extra shifts" (vbuf format = Q(DQ_FRACBITS_OUT-2))
+ * pointer to start of vbuf (preserved from last call)
+ * start of filter coefficient table (in proper, shuffled order)
+ * no minimum number of guard bits is required for input vbuf
+ * (see additional scaling comments below)
+ *
+ * Outputs: 32 samples of two channels of decoded PCM data, (i.e. Q16.0)
+ *
+ * Return: none
+ *
+ * Notes: interleaves PCM samples LRLRLR...
+ **********************************************************************************************************************/
+void PolyphaseStereo(int16_t *pcm, int32_t *vbuf, const uint32_t *coefBase){
+ int32_t i;
+ const uint32_t *coef;
+ int32_t *vb1;
+ int32_t vLo, vHi, c1, c2;
+ uint64_t sum1L, sum2L, sum1R, sum2R, rndVal;
+
+ rndVal = (uint64_t)( 1 << ((m_DQ_FRACBITS_OUT - 2 - 2 - 15) - 1 + (32 - m_CSHIFT)) );
+
+ /* special case, output sample 0 */
+ coef = coefBase;
+ vb1 = vbuf;
+ sum1L = sum1R = rndVal;
+
+ for(int32_t j=0; j<8; j++){
+ c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j)));
+ sum1L=MADD64(sum1L, vLo, c1); sum1L=MADD64(sum1L, vHi, -c2);
+ vLo=*(vb1+32+(j)); vHi=*(vb1+32+(23-(j)));
+ sum1R=MADD64(sum1R, vLo, c1); sum1R=MADD64(sum1R, vHi, -c2); \
+ }
+ *(pcm + 0) = ClipToShort((int32_t)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ *(pcm + 1) = ClipToShort((int32_t)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+
+ /* special case, output sample 16 */
+ coef = coefBase + 256;
+ vb1 = vbuf + 64*16;
+ sum1L = sum1R = rndVal;
+
+ for(int32_t j=0; j<8; j++){
+ c1=*coef; coef++; vLo = *(vb1+(j)); sum1L = MADD64(sum1L, vLo, c1);
+ vLo = *(vb1+32+(j)); sum1R = MADD64(sum1R, vLo, c1);
+ }
+ *(pcm + 2*16 + 0) = ClipToShort((int32_t)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ *(pcm + 2*16 + 1) = ClipToShort((int32_t)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+
+ /* main convolution loop: sum1L = samples 1, 2, 3, ... 15 sum2L = samples 31, 30, ... 17 */
+ coef = coefBase + 16;
+ vb1 = vbuf + 64;
+ pcm += 2;
+
+ /* right now, the compiler creates bad asm from this... */
+ for (i = 15; i > 0; i--) {
+ sum1L = sum2L = rndVal;
+ sum1R = sum2R = rndVal;
+
+ for(int32_t j=0; j<8; j++){
+ c1=*coef; coef++; c2=*coef; coef++; vLo=*(vb1+(j)); vHi = *(vb1+(23-(j)));
+ sum1L=MADD64(sum1L, vLo, c1); sum2L=MADD64(sum2L, vLo, c2);
+ sum1L=MADD64(sum1L, vHi, -c2); sum2L=MADD64(sum2L, vHi, c1);
+ vLo=*(vb1+32+(j)); vHi=*(vb1+32+(23-(j)));
+ sum1R=MADD64(sum1R, vLo, c1); sum2R=MADD64(sum2R, vLo, c2);
+ sum1R=MADD64(sum1R, vHi, -c2); sum2R=MADD64(sum2R, vHi, c1);
+ }
+ vb1 += 64;
+ *(pcm + 0) = ClipToShort((int32_t)SAR64(sum1L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ *(pcm + 1) = ClipToShort((int32_t)SAR64(sum1R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ *(pcm + 2*2*i + 0) = ClipToShort((int32_t)SAR64(sum2L, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ *(pcm + 2*2*i + 1) = ClipToShort((int32_t)SAR64(sum2R, (32-m_CSHIFT)), m_DQ_FRACBITS_OUT - 2 - 2 - 15);
+ pcm += 2;
+ }
+}
+
+/***********************************************************************************************************************
+ * Function: AnalyzeFrame
+ *
+ * Description: filter one subband and produce 32 output PCM samples for each channel
+ *
+ * Inputs: pointer to inpit buffer and length
+ *
+ * Outputs: MPEG_VERSION
+ * LAYER
+ * CHANNEL_MODE
+ *
+ * Return: main_data_begin
+ *
+ **********************************************************************************************************************/
+int MP3_AnalyzeFrame(const uint8_t *frame_data, size_t frame_len) {
+ if (frame_len < 4) {
+ log_e("Error: Frame data too short for header (need 4 bytes, got %zu).\n", frame_len);
+ return -3; // Frame too short for header
+ }
+
+ // Define constants for better readability
+ const uint8_t MPEG_VERSION_2_5 = 0; // 00 - unofficial, but often so coded
+ const uint8_t MPEG_VERSION_RESERVED = 1; // 01
+ const uint8_t MPEG_VERSION_2 = 2; // 10
+ const uint8_t MPEG_VERSION_1 = 3; // 11
+
+ const uint8_t LAYER_RESERVED = 0; // 00
+ const uint8_t LAYER_III = 1; // 01
+ const uint8_t LAYER_II = 2; // 10
+ const uint8_t LAYER_I = 3; // 11
+
+ const uint8_t CHANNEL_MODE_STEREO = 0; // 00
+ const uint8_t CHANNEL_MODE_JOINT_STEREO = 1; // 01
+ const uint8_t CHANNEL_MODE_DUAL_CHANNEL = 2; // 10
+ const uint8_t CHANNEL_MODE_MONO = 3; // 11
+
+ (void)MPEG_VERSION_RESERVED; (void)LAYER_III; (void)LAYER_II; (void)LAYER_I; (void)CHANNEL_MODE_STEREO;
+ (void)CHANNEL_MODE_JOINT_STEREO; (void)CHANNEL_MODE_DUAL_CHANNEL; (void)LAYER_RESERVED;
+
+ // ---- 1. Analyze frame header (first 4 bytes) ---
+ // combine the first 4 bytes into a 32-bit integer (Big Endian)
+ uint32_t header = ((uint32_t)frame_data[0] << 24) |
+ ((uint32_t)frame_data[1] << 16) |
+ ((uint32_t)frame_data[2] << 8) |
+ ((uint32_t)frame_data[3]);
+
+ // check sync word (first 11 bits must be 1)
+ // MPEG 2.5 Layer III often uses 12 bits (0xfff), other 11 bits (0xffe)
+ // simple check: data [0] == 0xff and (data [1] & 0xe0) == 0xe0
+ if (! (frame_data[0] == 0xFF && (frame_data[1] & 0xE0) == 0xE0) ) {
+ log_e("Error: Invalid MP3 sync word.\n");
+ return -4;
+ }
+
+ // MPEG version ID (Bits 11-12 of the header, or Bits 19-20 from right in the Uint32_t)
+ // Header: SSSS SSSS SSSV Vllp PBBB BFFM MCCE (S = Sync, V = version, L = layer, p = Protection ...)
+ // In our `Header` Uint32_t:
+ // Bit 31..21: Sync word (11 bits)
+ // Bit 20..19: MPEG Audio version ID
+ // Bit 18..17: Layer description
+ // Bit 16: Protection bit
+ // Bit 15..12: Bitrate index
+ // Bit 11..10: Sampling rate frequency index
+ // Bit 9: Padding bit
+ // Bit 8: Private bit
+ // Bit 7..6: Channel mode
+ // Bit 5..4: Mode extension (for Joint Stereo)
+ // Bit 3: Copyright
+ // Bit 2: Original
+ // Bit 1..0: Emphasis
+
+ uint8_t mpeg_version_id = (header >> 19) & 0x03;
+ uint8_t layer_description = (header >> 17) & 0x03;
+ uint8_t protection_bit = (header >> 16) & 0x01;
+ uint8_t channel_mode = (header >> 6) & 0x03;
+
+ // Debug output(optional)
+ // log_w("MPEG Version ID raw: %u\n", mpeg_version_id);
+ // log_w("Layer Description raw: %u\n", layer_description);
+ // log_w("Protection Bit: %u\n", protection_bit);
+ // log_w("Channel Mode raw: %u\n", channel_mode);
+
+
+ // --- 2. Check whether it is Layer III ---
+ if (layer_description != LAYER_III) {
+ //fprintf(stderr, "Info: Not an MPEG Layer III frame (Layer: %u).\n", layer_description);
+ return -1; // no Layer III
+ }
+
+ // --- 3. Side Information, Determine offset and size ---
+ int side_info_offset = 4; // after the 4-Byte Header
+ if (protection_bit == 0) { // 0 means CRC is available
+ side_info_offset += 2; // Skip 16-bit CRC
+ }
+
+ int side_info_size; (void)side_info_size;
+ // Derive MPEG versions from the ID (according to ISO/IEC 13818-3 Table B.1)
+ // ID '00' -> MPEG 2.5
+ // ID '01' -> reserved
+ // ID '10' -> MPEG 2
+ // ID '11' -> MPEG 1
+ if (mpeg_version_id == MPEG_VERSION_1) { // MPEG-1
+ if (channel_mode == CHANNEL_MODE_MONO) {
+ side_info_size = 17; // Mono
+ } else {
+ side_info_size = 32; // Stereo, Joint Stereo, Dual Channel
+ }
+ } else if (mpeg_version_id == MPEG_VERSION_2 || mpeg_version_id == MPEG_VERSION_2_5) { // MPEG-2 oder MPEG-2.5
+ if (channel_mode == CHANNEL_MODE_MONO) {
+ side_info_size = 9; // Mono
+ } else {
+ side_info_size = 17; // Stereo, Joint Stereo, Dual Channel
+ }
+ } else {
+ fprintf(stderr, "Error: Reserved or unknown MPEG version ID: %u.\n", mpeg_version_id);
+ return -2; //Unknown/reserved MPEG version
+ }
+
+ // ensure that the frame is long enough for the side information
+ // We need at least 2 bytes of the side info for Main_data_begin
+ if (frame_len < (size_t)(side_info_offset + 2)) {
+ fprintf(stderr, "Error: Frame data too short for side information (need %d bytes, got %zu).\n", side_info_offset + 2, frame_len);
+ return -3;
+ }
+ // (optional) Check whether the entire Side Information is available
+ /*
+ if (frame_len < (size_t)(side_info_offset + side_info_size)) {
+ fprintf(stderr, "Warning: Frame data might be too short for full side information (expected %d, got %zu available after header/CRC).\n", side_info_size, frame_len - side_info_offset);
+ // Fortfahren, da main_data_begin am Anfang ist, aber es ist ein Hinweis
+ }
+ */
+
+ // --- 4. Main_data_begin extract from the Side Information ---
+ // Main_data_begin are the first 9 bits of the Side Information
+ // Side information begins with frame_data [side_info_offset]
+ const uint8_t *side_info_ptr = frame_data + side_info_offset;
+
+ // The 9 bits consist of:
+ // - the complete 8 bits of the first bytes of the Side Information
+ // - the MSB (highest quality bit) of the second bytes of the Side Information
+ uint16_t main_data_begin_val = ((uint16_t)side_info_ptr[0] << 1) | (side_info_ptr[1] >> 7);
+
+ return main_data_begin_val;
+}
+
diff --git a/libraries/ESP32-audioI2S/additional_info/old/Helix MP3_decoder/mp3_decoder.h_ b/libraries/ESP32-audioI2S/additional_info/old/Helix MP3_decoder/mp3_decoder.h_
new file mode 100644
index 0000000..ebbe4a1
--- /dev/null
+++ b/libraries/ESP32-audioI2S/additional_info/old/Helix MP3_decoder/mp3_decoder.h_
@@ -0,0 +1,606 @@
+// based om helix mp3 decoder
+#pragma once
+
+#include "Arduino.h"
+#include "../psram_unique_ptr.hpp"
+#include "assert.h"
+
+extern __attribute__((weak)) void audio_info(const char*);
+
+static const uint8_t m_HUFF_PAIRTABS =32;
+static const uint8_t m_BLOCK_SIZE =18;
+static const uint8_t m_NBANDS =32;
+static const uint8_t m_MAX_REORDER_SAMPS =(192-126)*3; // largest critical band for short blocks (see sfBandTable)
+static const uint16_t m_VBUF_LENGTH =17*2* m_NBANDS; // for double-sized vbuf FIFO
+static const uint8_t m_MAX_SCFBD =4; // max scalefactor bands per channel
+static const uint16_t m_MAINBUF_SIZE =1940;
+static const uint8_t m_MAX_NGRAN =2; // max granules
+static const uint8_t m_MAX_NCHAN =2; // max channels
+static const uint16_t m_MAX_NSAMP =576; // max samples per channel, per granule
+
+enum {
+ MP3_NONE = 0,
+ MP3_ERR = -1,
+ MP3_STOP = -100,
+};
+
+typedef struct MP3FrameInfo {
+ int32_t bitrate;
+ int32_t nChans;
+ int32_t samprate;
+ int32_t bitsPerSample;
+ int32_t outputSamps;
+ int32_t layer;
+ int32_t version;
+} MP3FrameInfo_t;
+
+typedef struct SFBandTable {
+ int32_t l[23];
+ int32_t s[14];
+} SFBandTable_t;
+
+typedef struct BitStreamInfo {
+ uint8_t *bytePtr;
+ uint32_t iCache;
+ int32_t cachedBits;
+ int32_t nBytes;
+} BitStreamInfo_t;
+
+typedef enum { /* map these to the corresponding 2-bit values in the frame header */
+ Stereo = 0x00, /* two independent channels, but L and R frames might have different # of bits */
+ Joint = 0x01, /* coupled channels - layer III: mix of M-S and intensity, Layers I/II: intensity and direct coding only */
+ Dual = 0x02, /* two independent channels, L and R always have exactly 1/2 the total bitrate */
+ Mono = 0x03 /* one channel */
+} StereoMode_t;
+
+typedef enum { /* map to 0,1,2 to make table indexing easier */
+ MPEG1 = 0,
+ MPEG2 = 1,
+ MPEG25 = 2
+} MPEGVersion_t;
+
+typedef struct FrameHeader {
+ int32_t layer; /* layer index (1, 2, or 3) */
+ int32_t crc; /* CRC flag: 0 = disabled, 1 = enabled */
+ int32_t brIdx; /* bitrate index (0 - 15) */
+ int32_t srIdx; /* sample rate index (0 - 2) */
+ int32_t paddingBit; /* padding flag: 0 = no padding, 1 = single pad byte */
+ int32_t privateBit; /* unused */
+ int32_t modeExt; /* used to decipher joint stereo mode */
+ int32_t copyFlag; /* copyright flag: 0 = no, 1 = yes */
+ int32_t origFlag; /* original flag: 0 = copy, 1 = original */
+ int32_t emphasis; /* deemphasis mode */
+ int32_t CRCWord; /* CRC word (16 bits, 0 if crc not enabled) */
+} FrameHeader_t;
+
+typedef struct SideInfoSub {
+ int32_t part23Length; /* number of bits in main data */
+ int32_t nBigvals; /* 2x this = first set of Huffman cw's (maximum amplitude can be > 1) */
+ int32_t globalGain; /* overall gain for dequantizer */
+ int32_t sfCompress; /* unpacked to figure out number of bits in scale factors */
+ int32_t winSwitchFlag; /* window switching flag */
+ int32_t blockType; /* block type */
+ int32_t mixedBlock; /* 0 = regular block (all short or long), 1 = mixed block */
+ int32_t tableSelect[3]; /* index of Huffman tables for the big values regions */
+ int32_t subBlockGain[3]; /* subblock gain offset, relative to global gain */
+ int32_t region0Count; /* 1+region0Count = num scale factor bands in first region of bigvals */
+ int32_t region1Count; /* 1+region1Count = num scale factor bands in second region of bigvals */
+ int32_t preFlag; /* for optional high frequency boost */
+ int32_t sfactScale; /* scaling of the scalefactors */
+ int32_t count1TableSelect; /* index of Huffman table for quad codewords */
+} SideInfoSub_t;
+
+typedef struct SideInfo {
+ int32_t mainDataBegin;
+ int32_t privateBits;
+ int32_t scfsi[m_MAX_NCHAN][m_MAX_SCFBD]; /* 4 scalefactor bands per channel */
+} SideInfo_t;
+
+typedef struct {
+ int32_t cbType; /* pure long = 0, pure short = 1, mixed = 2 */
+ int32_t cbEndS[3]; /* number nonzero short cb's, per subbblock */
+ int32_t cbEndSMax; /* max of cbEndS[] */
+ int32_t cbEndL; /* number nonzero long cb's */
+} CriticalBandInfo_t;
+
+typedef struct DequantInfo {
+ int32_t workBuf[m_MAX_REORDER_SAMPS]; /* workbuf for reordering short blocks */
+} DequantInfo_t;
+
+typedef struct HuffmanInfo {
+ int32_t huffDecBuf[m_MAX_NCHAN][m_MAX_NSAMP]; /* used both for decoded Huffman values and dequantized coefficients */
+ int32_t nonZeroBound[m_MAX_NCHAN]; /* number of coeffs in huffDecBuf[ch] which can be > 0 */
+ int32_t gb[m_MAX_NCHAN]; /* minimum number of guard bits in huffDecBuf[ch] */
+} HuffmanInfo_t;
+
+typedef enum HuffTabType {
+ noBits,
+ oneShot,
+ loopNoLinbits,
+ loopLinbits,
+ quadA,
+ quadB,
+ invalidTab
+} HuffTabType_t;
+
+typedef struct HuffTabLookup {
+ int32_t linBits;
+ int32_t tabType; /*HuffTabType*/
+} HuffTabLookup_t;
+
+typedef struct IMDCTInfo {
+ int32_t outBuf[m_MAX_NCHAN][m_BLOCK_SIZE][m_NBANDS]; /* output of IMDCT */
+ int32_t overBuf[m_MAX_NCHAN][m_MAX_NSAMP / 2]; /* overlap-add buffer (by symmetry, only need 1/2 size) */
+ int32_t numPrevIMDCT[m_MAX_NCHAN]; /* how many IMDCT's calculated in this channel on prev. granule */
+ int32_t prevType[m_MAX_NCHAN];
+ int32_t prevWinSwitch[m_MAX_NCHAN];
+ int32_t gb[m_MAX_NCHAN];
+} IMDCTInfo_t;
+
+typedef struct BlockCount {
+ int32_t nBlocksLong;
+ int32_t nBlocksTotal;
+ int32_t nBlocksPrev;
+ int32_t prevType;
+ int32_t prevWinSwitch;
+ int32_t currWinSwitch;
+ int32_t gbIn;
+ int32_t gbOut;
+} BlockCount_t;
+
+typedef struct ScaleFactorInfoSub { /* max bits in scalefactors = 5, so use char's to save space */
+ char l[23]; /* [band] */
+ char s[13][3]; /* [band][window] */
+} ScaleFactorInfoSub_t;
+
+typedef struct ScaleFactorJS { /* used in MPEG 2, 2.5 intensity (joint) stereo only */
+ int32_t intensityScale;
+ int32_t slen[4];
+ int32_t nr[4];
+} ScaleFactorJS_t;
+
+/* NOTE - could get by with smaller vbuf if memory is more important than speed
+ * (in Subband, instead of replicating each block in FDCT32 you would do a memmove on the
+ * last 15 blocks to shift them down one, a hardware style FIFO)
+ */
+typedef struct SubbandInfo {
+ int32_t vbuf[m_MAX_NCHAN * m_VBUF_LENGTH]; /* vbuf for fast DCT-based synthesis PQMF - double size for speed (no modulo indexing) */
+ int32_t vindex; /* internal index for tracking position in vbuf */
+} SubbandInfo_t;
+
+typedef struct MP3DecInfo {
+ /* buffer which must be large enough to hold largest possible main_data section */
+ uint8_t mainBuf[m_MAINBUF_SIZE];
+ /* special info for "free" bitrate files */
+ int32_t freeBitrateFlag;
+ int32_t freeBitrateSlots;
+ /* user-accessible info */
+ int32_t bitrate;
+ int32_t nChans;
+ int32_t samprate;
+ int32_t nGrans; /* granules per frame */
+ int32_t nGranSamps; /* samples per granule */
+ int32_t nSlots;
+ int32_t layer;
+
+ int32_t mainDataBegin;
+ int32_t mainDataBytes;
+ int32_t part23Length[m_MAX_NGRAN][m_MAX_NCHAN];
+} MP3DecInfo_t;
+
+const uint16_t mpeg1_layer3_bitrates[16] = { // Bitraten-Lookup tables (example for MPEG1 Layer III)
+ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 // Attention: These tables must be complete and correct!
+}; // Index 0 is invalid, index 15 is invalid.
+
+// SamplingFrequenz-Lookup tables(Beispiel für MPEG1, MPEG2, MPEG2.5)
+const uint16_t sampling_rates[3][4] = {
+ {44100, 48000, 32000, 0}, // MPEG1
+ {22050, 24000, 16000, 0}, // MPEG2
+ {11025, 12000, 8000, 0} // MPEG2.5
+};
+
+typedef struct {
+ uint8_t mpeg_version; // 0=MPEG2.5, 1=reserved, 2=MPEG2, 3=MPEG1
+ uint8_t layer; // 0=reserved, 1=Layer III, 2=Layer II, 3=Layer I
+ bool crc_protected;
+ uint8_t bitrate_idx;
+ uint8_t sample_rate_idx;
+ bool padding;
+ uint8_t channel_mode;
+ uint32_t frame_length; // In Bytes
+} Mp3FrameHeader;
+
+
+/* format = Q31
+ * #define M_PI 3.14159265358979323846
+ * double u = 2.0 * M_PI / 9.0;
+ * float c0 = sqrt(3.0) / 2.0;
+ * float c1 = cos(u);
+ * float c2 = cos(2*u);
+ * float c3 = sin(u);
+ * float c4 = sin(2*u);
+ */
+
+const int32_t c9_0 = 0x6ed9eba1;
+const int32_t c9_1 = 0x620dbe8b;
+const int32_t c9_2 = 0x163a1a7e;
+const int32_t c9_3 = 0x5246dd49;
+const int32_t c9_4 = 0x7e0e2e32;
+
+
+
+const int32_t c3_0 = 0x6ed9eba1; /* format = Q31, cos(pi/6) */
+const int32_t c6[3] = { 0x7ba3751d, 0x5a82799a, 0x2120fb83 }; /* format = Q31, cos(((0:2) + 0.5) * (pi/6)) */
+
+/* format = Q31
+ * cos(((0:8) + 0.5) * (pi/18))
+ */
+const uint32_t c18[9] = { 0x7f834ed0, 0x7ba3751d, 0x7401e4c1, 0x68d9f964, 0x5a82799a, 0x496af3e2, 0x36185aee, 0x2120fb83, 0x0b27eb5c};
+
+/* scale factor lengths (num bits) */
+const char m_SFLenTab[16][2] = { {0, 0}, {0, 1}, {0, 2}, {0, 3}, {3, 0}, {1, 1}, {1, 2}, {1, 3},
+ {2, 1}, {2, 2}, {2, 3}, {3, 1}, {3, 2}, {3, 3}, {4, 2}, {4, 3}};
+
+/* NRTab[size + 3*is_right][block type][partition]
+ * block type index: 0 = (bt0,bt1,bt3), 1 = bt2 non-mixed, 2 = bt2 mixed
+ * partition: scale factor groups (sfb1 through sfb4)
+ * for block type = 2 (mixed or non-mixed) / by 3 is rolled into this table
+ * (for 3 short blocks per long block)
+ * see 2.4.3.2 in MPEG 2 (low sample rate) spec
+ * stuff rolled into this table:
+ * NRTab[x][1][y] --> (NRTab[x][1][y]) / 3
+ * NRTab[x][2][>=1] --> (NRTab[x][2][>=1]) / 3 (first partition is long block)
+ */
+const char NRTab[6][3][4] = {
+ {{ 6, 5, 5, 5}, {3, 3, 3, 3}, {6, 3, 3, 3}},
+ {{ 6, 5, 7, 3}, {3, 3, 4, 2}, {6, 3, 4, 2}},
+ {{11, 10, 0, 0}, {6, 6, 0, 0}, {6, 3, 6, 0}},
+ {{ 7, 7, 7, 0}, {4, 4, 4, 0}, {6, 5, 4, 0}},
+ {{ 6, 6, 6, 3}, {4, 3, 3, 2}, {6, 4, 3, 2}},
+ {{ 8, 8, 5, 0}, {5, 4, 3, 0}, {6, 6, 3, 0}}
+};
+
+
+
+/* optional pre-emphasis for high-frequency scale factor bands */
+const char preTab[22] = { 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,2,2,3,3,3,2,0 };
+
+/* pow(2,-i/4) for i=0..3, Q31 format */
+const int32_t pow14[4] PROGMEM = {
+ 0x7fffffff, 0x6ba27e65, 0x5a82799a, 0x4c1bf829
+};
+
+
+/*
+ * Minimax polynomial approximation to pow(x, 4/3), over the range
+ * poly43lo: x = [0.5, 0.7071]
+ * poly43hi: x = [0.7071, 1.0]
+ *
+ * Relative error < 1E-7
+ * Coefs are scaled by 4, 2, 1, 0.5, 0.25
+ */
+const uint32_t poly43lo[5] PROGMEM = { 0x29a0bda9, 0xb02e4828, 0x5957aa1b, 0x236c498d, 0xff581859 };
+const uint32_t poly43hi[5] PROGMEM = { 0x10852163, 0xd333f6a4, 0x46e9408b, 0x27c2cef0, 0xfef577b4 };
+
+/* pow(2, i*4/3) as exp and frac */
+const int32_t pow2exp[8] PROGMEM = { 14, 13, 11, 10, 9, 7, 6, 5 };
+
+const int32_t pow2frac[8] PROGMEM = {
+ 0x6597fa94, 0x50a28be6, 0x7fffffff, 0x6597fa94,
+ 0x50a28be6, 0x7fffffff, 0x6597fa94, 0x50a28be6
+};
+
+const uint16_t m_HUFF_OFFSET_01= 0;
+const uint16_t m_HUFF_OFFSET_02= 9 + m_HUFF_OFFSET_01;
+const uint16_t m_HUFF_OFFSET_03= 65 + m_HUFF_OFFSET_02;
+const uint16_t m_HUFF_OFFSET_05= 65 + m_HUFF_OFFSET_03;
+const uint16_t m_HUFF_OFFSET_06=257 + m_HUFF_OFFSET_05;
+const uint16_t m_HUFF_OFFSET_07=129 + m_HUFF_OFFSET_06;
+const uint16_t m_HUFF_OFFSET_08=110 + m_HUFF_OFFSET_07;
+const uint16_t m_HUFF_OFFSET_09=280 + m_HUFF_OFFSET_08;
+const uint16_t m_HUFF_OFFSET_10= 93 + m_HUFF_OFFSET_09;
+const uint16_t m_HUFF_OFFSET_11=320 + m_HUFF_OFFSET_10;
+const uint16_t m_HUFF_OFFSET_12=296 + m_HUFF_OFFSET_11;
+const uint16_t m_HUFF_OFFSET_13=185 + m_HUFF_OFFSET_12;
+const uint16_t m_HUFF_OFFSET_15=497 + m_HUFF_OFFSET_13;
+const uint16_t m_HUFF_OFFSET_16=580 + m_HUFF_OFFSET_15;
+const uint16_t m_HUFF_OFFSET_24=651 + m_HUFF_OFFSET_16;
+
+const int32_t huffTabOffset[m_HUFF_PAIRTABS] PROGMEM = {
+ 0, m_HUFF_OFFSET_01, m_HUFF_OFFSET_02, m_HUFF_OFFSET_03,
+ 0, m_HUFF_OFFSET_05, m_HUFF_OFFSET_06, m_HUFF_OFFSET_07,
+ m_HUFF_OFFSET_08, m_HUFF_OFFSET_09, m_HUFF_OFFSET_10, m_HUFF_OFFSET_11,
+ m_HUFF_OFFSET_12, m_HUFF_OFFSET_13, 0, m_HUFF_OFFSET_15,
+ m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16,
+ m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16, m_HUFF_OFFSET_16,
+ m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24,
+ m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24, m_HUFF_OFFSET_24,};
+
+const HuffTabLookup_t huffTabLookup[m_HUFF_PAIRTABS] PROGMEM = {
+ { 0, noBits },
+ { 0, oneShot },
+ { 0, oneShot },
+ { 0, oneShot },
+ { 0, invalidTab },
+ { 0, oneShot },
+ { 0, oneShot },
+ { 0, loopNoLinbits },
+ { 0, loopNoLinbits },
+ { 0, loopNoLinbits },
+ { 0, loopNoLinbits },
+ { 0, loopNoLinbits },
+ { 0, loopNoLinbits },
+ { 0, loopNoLinbits },
+ { 0, invalidTab },
+ { 0, loopNoLinbits },
+ { 1, loopLinbits },
+ { 2, loopLinbits },
+ { 3, loopLinbits },
+ { 4, loopLinbits },
+ { 6, loopLinbits },
+ { 8, loopLinbits },
+ { 10, loopLinbits },
+ { 13, loopLinbits },
+ { 4, loopLinbits },
+ { 5, loopLinbits },
+ { 6, loopLinbits },
+ { 7, loopLinbits },
+ { 8, loopLinbits },
+ { 9, loopLinbits },
+ { 11, loopLinbits },
+ { 13, loopLinbits },
+};
+
+
+const int32_t quadTabOffset[2] PROGMEM = {0, 64};
+const int32_t quadTabMaxBits[2] PROGMEM = {6, 4};
+
+/* indexing = [version][samplerate index]
+ * sample rate of frame (Hz)
+ */
+const int32_t samplerateTab[3][3] PROGMEM = {
+ { 44100, 48000, 32000 }, /* MPEG-1 */
+ { 22050, 24000, 16000 }, /* MPEG-2 */
+ { 11025, 12000, 8000 }, /* MPEG-2.5 */
+};
+
+
+
+/* indexing = [version][layer]
+ * number of samples in one frame (per channel)
+ */
+const uint16_t samplesPerFrameTab[3][3] PROGMEM = { { 384, 1152, 1152 }, /* MPEG1 */
+{ 384, 1152, 576 }, /* MPEG2 */
+{ 384, 1152, 576 }, /* MPEG2.5 */
+};
+
+/* layers 1, 2, 3 */
+const uint8_t bitsPerSlotTab[3] = { 32, 8, 8 };
+
+/* indexing = [version][mono/stereo]
+ * number of bytes in side info section of bitstream
+ */
+const uint8_t sideBytesTab[3][2] PROGMEM = { { 17, 32 }, /* MPEG-1: mono, stereo */
+{ 9, 17 }, /* MPEG-2: mono, stereo */
+{ 9, 17 }, /* MPEG-2.5: mono, stereo */
+};
+
+/* indexing = [version][sampleRate][long (.l) or short (.s) block]
+ * sfBandTable[v][s].l[cb] = index of first bin in critical band cb (long blocks)
+ * sfBandTable[v][s].s[cb] = index of first bin in critical band cb (short blocks)
+ */
+const SFBandTable_t sfBandTable[3][3] PROGMEM = {
+ { /* MPEG-1 (44, 48, 32 kHz) */
+ { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 },
+ {0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192} },
+ { {0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 },
+ {0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192} },
+ { {0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576 },
+ {0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192} } },
+ { /* MPEG-2 (22, 24, 16 kHz) */
+ { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 },
+ {0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192} },
+ { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 332, 394, 464, 540, 576 },
+ {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192} },
+ { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 },
+ {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192} }, },
+ { /* MPEG-2.5 (11, 12, 8 kHz) */
+ { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 },
+ {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } },
+ { {0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 },
+ {0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 } },
+ { {0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576 },
+ {0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192 } }, },
+};
+
+
+/* indexing = [intensity scale on/off][left/right]
+ * format = Q30, range = [0.0, 1.414]
+ *
+ * illegal intensity position scalefactors (see comments on ISFMpeg1)
+ */
+const int32_t ISFIIP[2][2] PROGMEM = {
+ {0x40000000, 0x00000000}, /* mid-side off */
+ {0x40000000, 0x40000000}, /* mid-side on */
+};
+
+const uint8_t uniqueIDTab[8] = {0x5f, 0x4b, 0x43, 0x5f, 0x5f, 0x4a, 0x52, 0x5f};
+
+/* anti-alias coefficients - see spec Annex B, table 3-B.9
+ * csa[0][i] = CSi, csa[1][i] = CAi
+ * format = Q31
+ */
+const uint32_t csa[8][2] PROGMEM = {
+ {0x6dc253f0, 0xbe2500aa},
+ {0x70dcebe4, 0xc39e4949},
+ {0x798d6e73, 0xd7e33f4a},
+ {0x7ddd40a7, 0xe8b71176},
+ {0x7f6d20b7, 0xf3e4fe2f},
+ {0x7fe47e40, 0xfac1a3c7},
+ {0x7ffcb263, 0xfe2ebdc6},
+ {0x7fffc694, 0xff86c25d},
+};
+
+/* format = Q30, right shifted by 12 (sign bits only in top 12 - undo this when rounding to short)
+ * this is to enable early-terminating multiplies on ARM
+ * range = [-1.144287109, 1.144989014]
+ * max gain of filter (per output sample) ~= 2.731
+ *
+ * new (properly sign-flipped) values
+ * - these actually are correct to 32 bits, (floating-pt coefficients in spec
+ * chosen such that only ~20 bits are required)
+ *
+ * Reordering - see table 3-B.3 in spec (appendix B)
+ *
+ * polyCoef[i] =
+ * D[ 0, 32, 64, ... 480], i = [ 0, 15]
+ * D[ 1, 33, 65, ... 481], i = [ 16, 31]
+ * D[ 2, 34, 66, ... 482], i = [ 32, 47]
+ * ...
+ * D[15, 47, 79, ... 495], i = [240,255]
+ *
+ * also exploits symmetry: D[i] = -D[512 - i], for i = [1, 255]
+ *
+ * polyCoef[256, 257, ... 263] are for special case of sample 16 (out of 0)
+ * see PolyphaseStereo() and PolyphaseMono()
+ */
+
+static const char* mpeg_version_table[] = {
+ "MPEG-2.5", // 0
+ "reserved", // 1
+ "MPEG-2", // 2
+ "MPEG-1" // 3
+};
+
+static const char* layer_table[] = {
+ "Unknown", // 0
+ "Layer I", // 1
+ "Layer II", // 2
+ "Layer III" // 3
+};
+
+// prototypes
+bool MP3Decoder_AllocateBuffers(void);
+bool MP3Decoder_IsInit();
+void MP3Decoder_FreeBuffers();
+int32_t MP3Decode( uint8_t *inbuf, int32_t *bytesLeft, int16_t *outbuf);
+void MP3GetLastFrameInfo();
+int32_t MP3GetNextFrameInfo(uint8_t *buf);
+int32_t MP3FindSyncWord(uint8_t *buf, int32_t nBytes);
+int32_t MP3GetSampRate();
+int32_t MP3GetChannels();
+int32_t MP3GetBitsPerSample();
+int32_t MP3GetBitrate();
+int32_t MP3GetOutputSamps();
+const char* MP3GetLayer();
+const char* MP3GetMPEGVersion();
+
+//internally used
+int MP3_AnalyzeFrame(const uint8_t *frame_data, size_t frame_len);
+void MP3Decoder_ClearBuffer(void);
+void PolyphaseMono(int16_t *pcm, int32_t *vbuf, const uint32_t* coefBase);
+void PolyphaseStereo(int16_t *pcm, int32_t *vbuf, const uint32_t* coefBase);
+void SetBitstreamPointer(BitStreamInfo_t *bsi, int32_t nBytes, uint8_t *buf);
+uint32_t GetBits(BitStreamInfo_t *bsi, int32_t nBits);
+int32_t CalcBitsUsed(BitStreamInfo_t *bsi, uint8_t *startBuf, int32_t startOffset);
+int32_t DequantChannel(int32_t *sampleBuf, int32_t *workBuf, int32_t *nonZeroBound, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi);
+void MidSideProc(int32_t x[m_MAX_NCHAN][m_MAX_NSAMP], int32_t nSamps, int32_t mOut[2]);
+void IntensityProcMPEG1(int32_t x[m_MAX_NCHAN][m_MAX_NSAMP], int32_t nSamps, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, int32_t midSideFlag, int32_t mixFlag, int32_t mOut[2]);
+void IntensityProcMPEG2(int32_t x[m_MAX_NCHAN][m_MAX_NSAMP], int32_t nSamps, ScaleFactorInfoSub_t *sfis, CriticalBandInfo_t *cbi, ScaleFactorJS_t *sfjs, int32_t midSideFlag, int32_t mixFlag, int32_t mOut[2]);
+void FDCT32(int32_t *x, int32_t *d, int32_t offset, int32_t oddBlock, int32_t gb);// __attribute__ ((section (".data")));
+int32_t CheckPadBit();
+int32_t UnpackFrameHeader(uint8_t *buf);
+int32_t UnpackSideInfo(uint8_t *buf);
+int32_t DecodeHuffman( uint8_t *buf, int32_t *bitOffset, int32_t huffBlockBits, int32_t gr, int32_t ch);
+int32_t MP3Dequantize( int32_t gr);
+int32_t IMDCT( int32_t gr, int32_t ch);
+int32_t UnpackScaleFactors( uint8_t *buf, int32_t *bitOffset, int32_t bitsAvail, int32_t gr, int32_t ch);
+int32_t Subband(int16_t *pcmBuf);
+int16_t ClipToShort(int32_t x, int32_t fracBits);
+void RefillBitstreamCache(BitStreamInfo_t *bsi);
+void UnpackSFMPEG1(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int32_t *scfsi, int32_t gr, ScaleFactorInfoSub_t *sfisGr0);
+void UnpackSFMPEG2(BitStreamInfo_t *bsi, SideInfoSub_t *sis, ScaleFactorInfoSub_t *sfis, int32_t gr, int32_t ch, int32_t modeExt, ScaleFactorJS_t *sfjs);
+int32_t MP3FindFreeSync(uint8_t *buf, uint8_t firstFH[4], int32_t nBytes);
+void MP3ClearBadFrame( int16_t *outbuf);
+int32_t DecodeHuffmanPairs(int32_t *xy, int32_t nVals, int32_t tabIdx, int32_t bitsLeft, uint8_t *buf, int32_t bitOffset);
+int32_t DecodeHuffmanQuads(int32_t *vwxy, int32_t nVals, int32_t tabIdx, int32_t bitsLeft, uint8_t *buf, int32_t bitOffset);
+int32_t DequantBlock(int32_t *inbuf, int32_t *outbuf, int32_t num, int32_t scale);
+void AntiAlias(int32_t *x, int32_t nBfly);
+void WinPrevious(int32_t *xPrev, int32_t *xPrevWin, int32_t btPrev);
+int32_t FreqInvertRescale(int32_t *y, int32_t *xPrev, int32_t blockIdx, int32_t es);
+void idct9(int32_t *x);
+int32_t IMDCT36(int32_t *xCurr, int32_t *xPrev, int32_t *y, int32_t btCurr, int32_t btPrev, int32_t blockIdx, int32_t gb);
+void imdct12(int32_t *x, int32_t *out);
+int32_t IMDCT12x3(int32_t *xCurr, int32_t *xPrev, int32_t *y, int32_t btPrev, int32_t blockIdx, int32_t gb);
+int32_t HybridTransform(int32_t *xCurr, int32_t *xPrev, int32_t y[m_BLOCK_SIZE][m_NBANDS], SideInfoSub_t *sis, BlockCount_t *bc);
+inline uint64_t SAR64(uint64_t x, int32_t n) {return x >> n;}
+inline int32_t MULSHIFT32(int32_t x, int32_t y) { int32_t z; z = (uint64_t) x * (uint64_t) y >> 32; return z;}
+inline uint64_t MADD64(uint64_t sum64, int32_t x, int32_t y) {sum64 += (uint64_t) x * (uint64_t) y; return sum64;}/* returns 64-bit value in [edx:eax] */
+inline uint64_t xSAR64(uint64_t x, int32_t n){return x >> n;}
+inline int32_t FASTABS(int32_t x){ return __builtin_abs(x);} //xtensa has a fast abs instruction //fb
+#define CLZ(x) __builtin_clz(x) //fb
+
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+ // 📌📌📌 L O G G I N G 📌📌📌
+
+template
+void MP3_ERROR_IMPL(uint8_t level, const char* path, int line, const char* fmt, Args&&... args) {
+ extern __attribute__((weak)) void audio_info(const char*);
+ #define ANSI_ESC_RESET "\033[0m"
+ #define ANSI_ESC_BLACK "\033[30m"
+ #define ANSI_ESC_RED "\033[31m"
+ #define ANSI_ESC_GREEN "\033[32m"
+ #define ANSI_ESC_YELLOW "\033[33m"
+ #define ANSI_ESC_BLUE "\033[34m"
+ #define ANSI_ESC_MAGENTA "\033[35m"
+ #define ANSI_ESC_CYAN "\033[36m"
+ #define ANSI_ESC_WHITE "\033[37m"
+
+ ps_ptr result;
+ ps_ptr file;
+
+ file.copy_from(path);
+ while(file.contains("/")){
+ file.remove_before('/', false);
+ }
+
+ // First run: determine size
+ int len = std::snprintf(nullptr, 0, fmt, std::forward(args)...);
+ if (len <= 0) return;
+
+ result.alloc(len + 1, "result");
+ char* dst = result.get();
+ if (!dst) return;
+ std::snprintf(dst, len + 1, fmt, std::forward(args)...);
+
+ // build a final string with file/line prefix
+ ps_ptr final;
+ int total_len = std::snprintf(nullptr, 0, "%s:%d:" ANSI_ESC_RED " %s" ANSI_ESC_RESET, file.c_get(), line, dst);
+ if (total_len <= 0) return;
+ final.alloc(total_len + 1, "final");
+ char* dest = final.get();
+ if (!dest) return; // Or error treatment
+ if(audio_info){
+ if (level == 1) snprintf(dest, total_len + 1, "%s:%d:" ANSI_ESC_RED " %s" ANSI_ESC_RESET, file.c_get(), line, dst);
+ else if(level == 2) snprintf(dest, total_len + 1, "%s:%d:" ANSI_ESC_YELLOW " %s" ANSI_ESC_RESET, file.c_get(), line, dst);
+ else if(level == 3) snprintf(dest, total_len + 1, "%s:%d:" ANSI_ESC_GREEN " %s" ANSI_ESC_RESET, file.c_get(), line, dst);
+ else snprintf(dest, total_len + 1, "%s:%d:" ANSI_ESC_BLUE " %s" ANSI_ESC_RESET, file.c_get(), line, dst);
+ audio_info(final.get());
+ }
+ else{
+ std::snprintf(dest, total_len + 1, "%s:%d: %s", file.c_get(), line, dst);
+ if (level == 1) log_e("%s", final.c_get());
+ else if(level == 2) log_w("%s", final.c_get());
+ else if(level == 3) log_i("%s", final.c_get());
+ else log_d("%s", final.c_get());
+ }
+ final.reset();
+ result.reset();
+}
+
+// Macro for comfortable calls
+#define MP3_ERROR(fmt, ...) MP3_ERROR_IMPL(1, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
+#define MP3_WARN(fmt, ...) MP3_ERROR_IMPL(2, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
+#define MP3_INFO(fmt, ...) MP3_ERROR_IMPL(3, __FILE__, __LINE__, fmt, ##__VA_ARGS__)
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+
diff --git a/libraries/ESP32-audioI2S/examples/AC101/AC101.cpp b/libraries/ESP32-audioI2S/examples/AC101/AC101.cpp
new file mode 100644
index 0000000..cf80aff
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/AC101/AC101.cpp
@@ -0,0 +1,352 @@
+/*
+ AC101 - An AC101 Codec driver library for Arduino
+ Copyright (C) 2019, Ivo Pullens, Emmission
+
+ Inspired by:
+ https://github.com/donny681/esp-adf/tree/master/components/audio_hal/driver/AC101
+
+ 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 .
+
+ Febr 2021 modified by schreibfaul1 - set correct pll values
+ March 2021 modified by schreibfaul1 - can handle two i2c instances
+ May 2021 modified by schreibfaul1 - constructor changed
+ Oct 2021 modified by schreibfaul1 - I2C wrong ACK in ReadReg
+ Jan 2022 modified by schreibfaul1 - left right channel swapped
+ Jan 2022 modified by schreibfaul1 - suppress compiler warning: left shift of negative value
+
+ examples:
+
+ //one I2C bus: (default behaviour)
+ AC101 ac;
+ ac.begin(sda, scl);
+
+ //two I2C busses:
+ TwoWire i2cBusOne = TwoWire(0);
+ TwoWire i2cBusTwo = TwoWire(1);
+ AC101 ac(&i2cBusOne);
+
+ i2cBusOne.begin(sda, scl, 400000);
+*/
+
+#include "AC101.h"
+
+#define BCLK // clock over BCLK comment out: clock over MCLK
+
+#define AC101_ADDR 0x1A // Device address
+
+#define CHIP_AUDIO_RS 0x00
+#define PLL_CTRL1 0x01
+#define PLL_CTRL2 0x02
+#define SYSCLK_CTRL 0x03
+#define MOD_CLK_ENA 0x04
+#define MOD_RST_CTRL 0x05
+#define I2S_SR_CTRL 0x06
+#define I2S1LCK_CTRL 0x10
+#define I2S1_SDOUT_CTRL 0x11
+#define I2S1_SDIN_CTRL 0x12
+#define I2S1_MXR_SRC 0x13
+#define I2S1_VOL_CTRL1 0x14
+#define I2S1_VOL_CTRL2 0x15
+#define I2S1_VOL_CTRL3 0x16
+#define I2S1_VOL_CTRL4 0x17
+#define I2S1_MXR_GAIN 0x18
+#define ADC_DIG_CTRL 0x40
+#define ADC_VOL_CTRL 0x41
+#define HMIC_CTRL1 0x44
+#define HMIC_CTRL2 0x45
+#define HMIC_STATUS 0x46
+#define DAC_DIG_CTRL 0x48
+#define DAC_VOL_CTRL 0x49
+#define DAC_MXR_SRC 0x4C
+#define DAC_MXR_GAIN 0x4D
+#define ADC_APC_CTRL 0x50
+#define ADC_SRC 0x51
+#define ADC_SRCBST_CTRL 0x52
+#define OMIXER_DACA_CTRL 0x53
+#define OMIXER_SR 0x54
+#define OMIXER_BST1_CTRL 0x55
+#define HPOUT_CTRL 0x56
+#define SPKOUT_CTRL 0x58
+#define AC_DAC_DAPCTRL 0xA0
+#define AC_DAC_DAPHHPFC 0xA1
+#define AC_DAC_DAPLHPFC 0xA2
+#define AC_DAC_DAPLHAVC 0xA3
+#define AC_DAC_DAPLLAVC 0xA4
+#define AC_DAC_DAPRHAVC 0xA5
+#define AC_DAC_DAPRLAVC 0xA6
+#define AC_DAC_DAPHGDEC 0xA7
+#define AC_DAC_DAPLGDEC 0xA8
+#define AC_DAC_DAPHGATC 0xA9
+#define AC_DAC_DAPLGATC 0xAA
+#define AC_DAC_DAPHETHD 0xAB
+#define AC_DAC_DAPLETHD 0xAC
+#define AC_DAC_DAPHGKPA 0xAD
+#define AC_DAC_DAPLGKPA 0xAE
+#define AC_DAC_DAPHGOPA 0xAF
+#define AC_DAC_DAPLGOPA 0xB0
+#define AC_DAC_DAPOPT 0xB1
+#define DAC_DAP_ENA 0xB5
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+const uint8_t regs[] = {
+ CHIP_AUDIO_RS ,
+ PLL_CTRL1 ,
+ PLL_CTRL2 ,
+ SYSCLK_CTRL ,
+ MOD_CLK_ENA ,
+ MOD_RST_CTRL ,
+ I2S_SR_CTRL ,
+ I2S1LCK_CTRL ,
+ I2S1_SDOUT_CTRL ,
+ I2S1_SDIN_CTRL ,
+ I2S1_MXR_SRC ,
+ I2S1_VOL_CTRL1 ,
+ I2S1_VOL_CTRL2 ,
+ I2S1_VOL_CTRL3 ,
+ I2S1_VOL_CTRL4 ,
+ I2S1_MXR_GAIN ,
+ ADC_DIG_CTRL ,
+ ADC_VOL_CTRL ,
+ HMIC_CTRL1 ,
+ HMIC_CTRL2 ,
+ HMIC_STATUS ,
+ DAC_DIG_CTRL ,
+ DAC_VOL_CTRL ,
+ DAC_MXR_SRC ,
+ DAC_MXR_GAIN ,
+ ADC_APC_CTRL ,
+ ADC_SRC ,
+ ADC_SRCBST_CTRL ,
+ OMIXER_DACA_CTRL ,
+ OMIXER_SR ,
+ OMIXER_BST1_CTRL ,
+ HPOUT_CTRL ,
+ SPKOUT_CTRL ,
+ AC_DAC_DAPCTRL ,
+ AC_DAC_DAPHHPFC ,
+ AC_DAC_DAPLHPFC ,
+ AC_DAC_DAPLHAVC ,
+ AC_DAC_DAPLLAVC ,
+ AC_DAC_DAPRHAVC ,
+ AC_DAC_DAPRLAVC ,
+ AC_DAC_DAPHGDEC ,
+ AC_DAC_DAPLGDEC ,
+ AC_DAC_DAPHGATC ,
+ AC_DAC_DAPLGATC ,
+ AC_DAC_DAPHETHD ,
+ AC_DAC_DAPLETHD ,
+ AC_DAC_DAPHGKPA ,
+ AC_DAC_DAPLGKPA ,
+ AC_DAC_DAPHGOPA ,
+ AC_DAC_DAPLGOPA ,
+ AC_DAC_DAPOPT ,
+ DAC_DAP_ENA
+};
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::WriteReg(uint8_t reg, uint16_t val)
+{
+ _TwoWireInstance->beginTransmission(AC101_ADDR);
+ _TwoWireInstance->write(reg);
+ _TwoWireInstance->write(uint8_t((val >> 8) & 0xff));
+ _TwoWireInstance->write(uint8_t(val & 0xff));
+ return 0 == _TwoWireInstance->endTransmission(true);
+}
+
+uint16_t AC101::ReadReg(uint8_t reg)
+{
+ _TwoWireInstance->beginTransmission(AC101_ADDR);
+ _TwoWireInstance->write(reg);
+ _TwoWireInstance->endTransmission(false);
+
+ uint16_t val = 0u;
+ if (2 == _TwoWireInstance->requestFrom(uint16_t(AC101_ADDR), uint8_t(2)))
+ {
+ val = uint16_t(_TwoWireInstance->read() << 8) + uint16_t(_TwoWireInstance->read());
+ }
+ _TwoWireInstance->endTransmission(true);
+ return val;
+}
+//----------------------------------------------------------------------------------------------------------------------
+AC101::AC101( TwoWire *TwoWireInstance ){
+ _TwoWireInstance = TwoWireInstance;
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::begin(int32_t sda, int32_t scl, uint32_t frequency) {
+ bool ok;
+ if((sda >= 0) && (scl >= 0)){
+ ok = _TwoWireInstance->begin(sda, scl, frequency);
+ }
+ else {
+ ok = true;
+ }
+
+ // Reset all registers, readback default as sanity check
+ ok &= WriteReg(CHIP_AUDIO_RS, 0x123);
+ delay(100);
+ ok &= 0x0101 == ReadReg(CHIP_AUDIO_RS);
+ ok &= WriteReg(SPKOUT_CTRL, 0xe880);
+ // Enable the PLL from 256*44.1KHz MCLK source
+ ok &= WriteReg(PLL_CTRL1, 0x0141);
+ uint16_t N = 48 << 4; /* 512 / (M * (2*K+1)) / (CHANNELS * WORD_SIZE) -> 512 / 3 * (2 * 16) */
+ uint16_t PLL_EN = 1 << 15;
+ uint16_t N_f = 0<<0; /* 0.2 N */
+ ok &= WriteReg(PLL_CTRL2, N | PLL_EN | N_f);
+
+ // Clocking system
+ uint16_t PLLCLK_ENA = 1<<15; /* 0: Disable, 1: Enable */
+#ifdef BCLK
+ uint16_t PLL_CLK = 0x2 << 12; /* bclk1 */
+ uint16_t I2S1CLK_SRC = 0x3<<8; /* PLL */
+#else
+ uint16_t PLL_CLK = 0x0 << 12; /* MCLK1 */
+ uint16_t I2S1CLK_SRC = 0x0<<8; /* MLCK1 */
+#endif
+ uint16_t I2S1CLK_ENA = 1<<11; /* 0: Disable, 1: Enable */
+
+ uint16_t SYSCLK_ENA = 1<<3;
+ ok &= WriteReg(SYSCLK_CTRL, PLLCLK_ENA|PLL_CLK| I2S1CLK_ENA|I2S1CLK_SRC|SYSCLK_ENA/*0x8b08*/);
+
+ ok &= WriteReg(MOD_CLK_ENA, 0x800c);
+ ok &= WriteReg(MOD_RST_CTRL, 0x800c);
+
+ // Set default at I2S, 44.1KHz, 16bit
+ ok &= SetI2sSampleRate(SAMPLE_RATE_44100);
+ ok &= SetI2sClock(BCLK_DIV_8, false, LRCK_DIV_32, false);
+ ok &= SetI2sMode(MODE_SLAVE);
+ ok &= SetI2sWordSize(WORD_SIZE_16_BITS);
+ ok &= SetI2sFormat(DATA_FORMAT_I2S);
+
+ // AIF config
+ ok &= WriteReg(I2S1_SDOUT_CTRL, 0xc000);
+ ok &= WriteReg(I2S1_SDIN_CTRL, 0xc000);
+ ok &= WriteReg(I2S1_MXR_SRC, 0x2200);
+
+ ok &= WriteReg(ADC_SRCBST_CTRL, 0xccc4);
+ ok &= WriteReg(ADC_SRC, 0x1040);
+ ok &= WriteReg(ADC_DIG_CTRL, 0x8000);
+ ok &= WriteReg(ADC_APC_CTRL, 0xbbc3);
+
+ // Path Configuration
+ ok &= WriteReg(DAC_MXR_SRC, 0xcc00);
+ ok &= WriteReg(DAC_DIG_CTRL, 0x8000);
+ ok &= WriteReg(OMIXER_SR, 0x0081);
+ ok &= WriteReg(OMIXER_DACA_CTRL, 0xf080);
+
+ ok &= SetMode( MODE_DAC );
+
+ return ok;
+}
+//----------------------------------------------------------------------------------------------------------------------
+void AC101::DumpRegisters() {
+ for (size_t i = 0; i < ARRAY_SIZE(regs); ++i){
+ Serial.print(regs[i], HEX);
+ Serial.print(" = ");
+ Serial.println(ReadReg(regs[i]), HEX);
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------
+uint8_t AC101::GetVolumeSpeaker() {
+ // Times 2, to scale to same range as headphone volume
+ return (ReadReg(SPKOUT_CTRL) & 31) * 2;
+}
+
+bool AC101::SetVolumeSpeaker(uint8_t volume) {
+ // Divide by 2, as it is scaled to same range as headphone volume
+ volume /= 2;
+ if(volume > 31) volume = 31;
+
+ uint16_t val = ReadReg(SPKOUT_CTRL);
+ val &= ~31;
+ val |= volume;
+ return WriteReg(SPKOUT_CTRL, val);
+}
+//----------------------------------------------------------------------------------------------------------------------
+uint8_t AC101::GetVolumeHeadphone() {
+ return (ReadReg(HPOUT_CTRL) >> 4) & 63;
+}
+
+bool AC101::SetVolumeHeadphone(uint8_t volume) {
+ if(volume > 63) volume = 63;
+
+ uint16_t val = ReadReg(HPOUT_CTRL);
+ val &= ~63U << 4;
+ val |= volume << 4;
+ return WriteReg(HPOUT_CTRL, val);
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::SetI2sSampleRate(I2sSampleRate_t rate) {
+ return WriteReg(I2S_SR_CTRL, rate);
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::SetI2sMode(I2sMode_t mode) {
+ uint16_t val = ReadReg(I2S1LCK_CTRL);
+ val &= ~0x8000;
+ val |= uint16_t(mode) << 15;
+ return WriteReg(I2S1LCK_CTRL, val);
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::SetI2sWordSize(I2sWordSize_t size) {
+ uint16_t val = ReadReg(I2S1LCK_CTRL);
+ val &= ~0x0030;
+ val |= uint16_t(size) << 4;
+ return WriteReg(I2S1LCK_CTRL, val);
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::SetI2sFormat(I2sFormat_t format) {
+ uint16_t val = ReadReg(I2S1LCK_CTRL);
+ val &= ~0x000C;
+ val |= uint16_t(format) << 2;
+ return WriteReg(I2S1LCK_CTRL, val);
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::SetI2sClock(I2sBitClockDiv_t bitClockDiv, bool bitClockInv, I2sLrClockDiv_t lrClockDiv, bool lrClockInv) {
+ uint16_t val = ReadReg(I2S1LCK_CTRL);
+ val &= ~0x7FC0;
+ val |= uint16_t(bitClockInv ? 1 : 0) << 14;
+ val |= uint16_t(bitClockDiv) << 9;
+ val |= uint16_t(lrClockInv ? 1 : 0) << 13;
+ val |= uint16_t(lrClockDiv) << 6;
+ return WriteReg(I2S1LCK_CTRL, val);
+}
+//----------------------------------------------------------------------------------------------------------------------
+bool AC101::SetMode(Mode_t mode) {
+ bool ok = true;
+ if(MODE_LINE == mode) {
+ ok &= WriteReg(ADC_SRC, 0x0408);
+ ok &= WriteReg(ADC_DIG_CTRL, 0x8000);
+ ok &= WriteReg(ADC_APC_CTRL, 0x3bc0);
+ }
+
+ if((MODE_ADC == mode) or (MODE_ADC_DAC == mode) or (MODE_LINE == mode)) {
+ ok &= WriteReg(MOD_CLK_ENA, 0x800c);
+ ok &= WriteReg(MOD_RST_CTRL, 0x800c);
+ }
+
+ if((MODE_DAC == mode) or (MODE_ADC_DAC == mode) or (MODE_LINE == mode)) {
+ // Enable Headphone output
+ ok &= WriteReg(OMIXER_DACA_CTRL, 0xff80);
+ ok &= WriteReg(HPOUT_CTRL, 0xc3c1);
+ ok &= WriteReg(HPOUT_CTRL, 0xcb00);
+ delay(100);
+ ok &= WriteReg(HPOUT_CTRL, 0xfbc0);
+ ok &= SetVolumeHeadphone(30);
+
+ // Enable Speaker output
+ ok &= WriteReg(SPKOUT_CTRL, 0xeabd);
+ delay(10);
+ ok &= SetVolumeSpeaker(30);
+ }
+ return ok;
+}
diff --git a/libraries/ESP32-audioI2S/examples/AC101/AC101.h b/libraries/ESP32-audioI2S/examples/AC101/AC101.h
new file mode 100644
index 0000000..f34127b
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/AC101/AC101.h
@@ -0,0 +1,166 @@
+/*
+ AC101 - An AC101 Codec driver library for Arduino
+ Copyright (C) 2019, Ivo Pullens, Emmission
+
+ Inspired by:
+ https://github.com/donny681/esp-adf/tree/master/components/audio_hal/driver/AC101
+
+ 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 AC101_H
+#define AC101_H
+
+#include
+#include
+#include
+
+class AC101
+{
+public:
+ typedef enum {
+ SAMPLE_RATE_8000 = 0x0000,
+ SAMPLE_RATE_11052 = 0x1000,
+ SAMPLE_RATE_12000 = 0x2000,
+ SAMPLE_RATE_16000 = 0x3000,
+ SAMPLE_RATE_22050 = 0x4000,
+ SAMPLE_RATE_24000 = 0x5000,
+ SAMPLE_RATE_32000 = 0x6000,
+ SAMPLE_RATE_44100 = 0x7000,
+ SAMPLE_RATE_48000 = 0x8000,
+ SAMPLE_RATE_96000 = 0x9000,
+ SAMPLE_RATE_192000 = 0xa000,
+ } I2sSampleRate_t;
+
+ typedef enum {
+ MODE_MASTER = 0x00,
+ MODE_SLAVE = 0x01,
+ } I2sMode_t;
+
+ typedef enum {
+ WORD_SIZE_8_BITS = 0x00,
+ WORD_SIZE_16_BITS = 0x01,
+ WORD_SIZE_20_BITS = 0x02,
+ WORD_SIZE_24_BITS = 0x03,
+ } I2sWordSize_t;
+
+ typedef enum {
+ DATA_FORMAT_I2S = 0x00,
+ DATA_FORMAT_LEFT = 0x01,
+ DATA_FORMAT_RIGHT = 0x02,
+ DATA_FORMAT_DSP = 0x03,
+ } I2sFormat_t;
+
+ typedef enum {
+ BCLK_DIV_1 = 0x0,
+ BCLK_DIV_2 = 0x1,
+ BCLK_DIV_4 = 0x2,
+ BCLK_DIV_6 = 0x3,
+ BCLK_DIV_8 = 0x4,
+ BCLK_DIV_12 = 0x5,
+ BCLK_DIV_16 = 0x6,
+ BCLK_DIV_24 = 0x7,
+ BCLK_DIV_32 = 0x8,
+ BCLK_DIV_48 = 0x9,
+ BCLK_DIV_64 = 0xa,
+ BCLK_DIV_96 = 0xb,
+ BCLK_DIV_128 = 0xc,
+ BCLK_DIV_192 = 0xd,
+ } I2sBitClockDiv_t;
+
+ typedef enum {
+ LRCK_DIV_16 = 0x0,
+ LRCK_DIV_32 = 0x1,
+ LRCK_DIV_64 = 0x2,
+ LRCK_DIV_128 = 0x3,
+ LRCK_DIV_256 = 0x4,
+ } I2sLrClockDiv_t;
+
+
+ typedef enum {
+ MODE_ADC,
+ MODE_DAC,
+ MODE_ADC_DAC,
+ MODE_LINE
+ } Mode_t;
+
+ // Constructor.
+ AC101(TwoWire *TwoWireInstance = &Wire);
+
+ // Initialize codec, using provided I2C pins and bus frequency.
+ // @return True on success, false on failure.
+ bool begin(int32_t sda = -1, int32_t scl = -1, uint32_t frequency = 400000);
+
+ // Get speaker volume.
+ // @return Speaker volume, [63..0] for [0..-43.5] [dB], in increments of 2.
+ uint8_t GetVolumeSpeaker();
+
+ // Set speaker volume.
+ // @param volume Target volume, [63..0] for [0..-43.5] [dB], in increments of 2.
+ // @return True on success, false on failure.
+ bool SetVolumeSpeaker(uint8_t volume);
+
+ // Get headphone volume.
+ // @return Headphone volume, [63..0] for [0..-62] [dB]
+ uint8_t GetVolumeHeadphone();
+
+ // Set headphone volume
+ // @param volume Target volume, [63..0] for [0..-62] [dB]
+ // @return True on success, false on failure.
+ bool SetVolumeHeadphone(uint8_t volume);
+
+ // Configure I2S samplerate.
+ // @param rate Samplerate.
+ // @return True on success, false on failure.
+ bool SetI2sSampleRate(I2sSampleRate_t rate);
+
+ // Configure I2S mode (master/slave).
+ // @param mode Mode.
+ // @return True on success, false on failure.
+ bool SetI2sMode(I2sMode_t mode);
+
+ // Configure I2S word size (8/16/20/24 bits).
+ // @param size Word size.
+ // @return True on success, false on failure.
+ bool SetI2sWordSize(I2sWordSize_t size);
+
+ // Configure I2S format (I2S/Left/Right/Dsp).
+ // @param format I2S format.
+ // @return True on success, false on failure.
+ bool SetI2sFormat(I2sFormat_t format);
+
+ // Configure I2S clock.
+ // @param bitClockDiv I2S1CLK/BCLK1 ratio.
+ // @param bitClockInv I2S1 BCLK Polarity.
+ // @param lrClockDiv BCLK1/LRCK ratio.
+ // @param lrClockInv I2S1 LRCK Polarity.
+ // @return True on success, false on failure.
+ bool SetI2sClock(I2sBitClockDiv_t bitClockDiv, bool bitClockInv, I2sLrClockDiv_t lrClockDiv, bool lrClockInv);
+
+ // Configure the mode (Adc/Dac/Adc+Dac/Line)
+ // @param mode Operating mode.
+ // @return True on success, false on failure.
+ bool SetMode(Mode_t mode);
+
+ // Dumpt the current register configuration to serial.
+ void DumpRegisters();
+
+protected:
+ bool WriteReg(uint8_t reg, uint16_t val);
+ uint16_t ReadReg(uint8_t reg);
+private:
+ TwoWire *_TwoWireInstance = NULL; // TwoWire Instance
+};
+
+#endif
diff --git a/libraries/ESP32-audioI2S/examples/AC101/main.cpp b/libraries/ESP32-audioI2S/examples/AC101/main.cpp
new file mode 100644
index 0000000..28f2a59
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/AC101/main.cpp
@@ -0,0 +1,85 @@
+#include "Arduino.h"
+#include "WiFi.h"
+#include "SPI.h"
+#include "SD.h"
+#include "FS.h"
+#include "Wire.h"
+#include "AC101.h"
+#include "Audio.h"
+
+// I2S GPIOs, the names refer on AC101, AS1 Audio Kit V2.2 2379
+#define I2S_DSIN 35 // pin not used
+#define I2S_BCLK 27
+#define I2S_LRC 26
+#define I2S_MCLK 0
+#define I2S_DOUT 25
+
+// I2C GPIOs
+#define IIC_CLK 32
+#define IIC_DATA 33
+
+// amplifier enable
+#define GPIO_PA_EN 21
+
+//Switch S1: 1-OFF, 2-ON, 3-ON, 4-OFF, 5-OFF
+
+String ssid = "*****";
+String password = "*****";
+
+static AC101 dac; // AC101
+int volume = 40; // 0...100
+
+Audio audio;
+
+//#####################################################################
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid.c_str(), password.c_str());
+
+ while (WiFi.status() != WL_CONNECTED){
+ Serial.print(".");
+ delay(100);
+ }
+
+ Serial.printf_P(PSTR("Connected\r\nRSSI: "));
+ Serial.print(WiFi.RSSI());
+ Serial.print(" IP: ");
+ Serial.println(WiFi.localIP());
+
+ Serial.printf("Connect to DAC codec... ");
+ while (not dac.begin(IIC_DATA, IIC_CLK)){
+ Serial.printf("Failed!\n");
+ delay(1000);
+ }
+ Serial.printf("OK\n");
+
+ dac.SetVolumeSpeaker(volume);
+ dac.SetVolumeHeadphone(volume);
+// ac.DumpRegisters();
+
+ // Enable amplifier
+ pinMode(GPIO_PA_EN, OUTPUT);
+ digitalWrite(GPIO_PA_EN, HIGH);
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT, I2S_MCLK);
+ audio.setVolume(10); // 0...21
+
+ audio.connecttohost("http://mp3channels.webradio.antenne.de:80/oldies-but-goldies");
+// audio.connecttohost("http://dg-rbb-http-dus-dtag-cdn.cast.addradio.de/rbb/antennebrandenburg/live/mp3/128/stream.mp3");
+// audio.connecttospeech("Wenn die Hunde schlafen, kann der Wolf gut Schafe stehlen.", "de");
+
+}
+
+//-----------------------------------------------------------------------
+
+void loop(){
+ vTaskDelay(1);
+ audio.loop();
+}
diff --git a/libraries/ESP32-audioI2S/examples/Audio Recorder/audio_recorder.cpp b/libraries/ESP32-audioI2S/examples/Audio Recorder/audio_recorder.cpp
new file mode 100644
index 0000000..9d87749
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/Audio Recorder/audio_recorder.cpp
@@ -0,0 +1,292 @@
+
+// example AUDIO RECORDER
+// starts an audio stream and records 10 seconds of it on the SD card.
+
+
+#include "Arduino.h"
+#include "Audio.h"
+#include "WiFiMulti.h"
+#include
+#include
+
+Audio audio;
+WiFiMulti wifiMulti;
+
+//______________________________________________________________________________________________________________________________________________________________________________________________________
+// A U D I O R E C O R D E R
+//______________________________________________________________________________________________________________________________________________________________________________________________________
+
+struct WAVHeader {
+ char riff[4] = {'R', 'I', 'F', 'F'};
+ uint32_t size;
+ char wave[4] = {'W', 'A', 'V', 'E'};
+ char fmt[4] = {'f', 'm', 't', ' '};
+ uint32_t fmtSize = 16;
+ uint16_t format = 1;
+ uint16_t channels = 2;
+ uint32_t sampleRate;
+ uint32_t byteRate;
+ uint16_t blockAlign;
+ uint16_t bits;
+ char data[4] = {'d', 'a', 't', 'a'};
+ uint32_t dataSize;
+};
+
+constexpr size_t REC_BUFFER_SIZE = 512 * 1024; // 512KB für 2-3 Sekunden Puffer
+constexpr size_t WRITE_CHUNK_SIZE = 10242; // not too big!
+constexpr size_t SD_FLUSH_INTERVAL = 65536; // Alle 64KB flush
+ps_ptr rec_buffer;
+ps_ptr writeBuffer;
+
+class AudioRecorder {
+ public:
+ std::atomic writePos{0};
+ std::atomic readPos{0};
+ uint32_t totalBytes = 0;
+ uint16_t sampleRate = 44100;
+ uint32_t overflowCount = 0;
+
+ volatile bool startRequested = false;
+ volatile bool stopRequested = false;
+ volatile bool running = false;
+
+ bool push16(const int32_t* data, size_t frames) {
+ // frames = Stereo-Frames
+ size_t bytes16 = frames * 2 * sizeof(int16_t);
+
+ size_t currentWrite = writePos.load(std::memory_order_relaxed);
+ size_t currentRead = readPos.load(std::memory_order_acquire);
+
+ size_t free = (currentRead + REC_BUFFER_SIZE - currentWrite - 1) % REC_BUFFER_SIZE;
+
+ if (bytes16 > free) {
+ overflowCount++;
+ return false;
+ }
+
+ for (size_t i = 0; i < frames * 2; i++) {
+ // 32 → 16 Bit (High word)
+ int32_t v = data[i] >> 16;
+
+ // Optional Clipping (save)
+ if (v > 32767) v = 32767;
+ if (v < -32768) v = -32768;
+
+ int16_t s = (int16_t)v;
+
+ // Write byte by byte (LE)
+ rec_buffer[currentWrite] = s & 0xFF;
+ currentWrite = (currentWrite + 1) % REC_BUFFER_SIZE;
+ rec_buffer[currentWrite] = (s >> 8) & 0xFF;
+ currentWrite = (currentWrite + 1) % REC_BUFFER_SIZE;
+ }
+ writePos.store(currentWrite, std::memory_order_release);
+ return true;
+ }
+
+ // Copies data to dest, returns bytes actually read
+ size_t pop(uint8_t* dest, size_t maxLen) {
+ size_t currentRead = readPos.load(std::memory_order_relaxed);
+ size_t currentWrite = writePos.load(std::memory_order_acquire);
+
+ if (currentRead == currentWrite) return 0;
+
+ size_t avail = (currentWrite > currentRead) ? (currentWrite - currentRead) : (REC_BUFFER_SIZE - currentRead);
+
+ size_t toRead = std::min(avail, maxLen);
+
+ // Wrap-around handling
+ size_t firstChunk = std::min(toRead, REC_BUFFER_SIZE - currentRead);
+ memcpy(dest, &rec_buffer[currentRead], firstChunk);
+ if (toRead > firstChunk) { memcpy(dest + firstChunk, &rec_buffer[0], toRead - firstChunk); }
+
+ readPos.store((currentRead + toRead) % REC_BUFFER_SIZE, std::memory_order_release);
+ return toRead;
+ }
+
+ // For external access to buffers (e.g. for pop with pointer math, but not recommended)
+ size_t available() {
+ size_t w = writePos.load(std::memory_order_acquire);
+ size_t r = readPos.load(std::memory_order_acquire);
+ return (w >= r) ? (w - r) : (REC_BUFFER_SIZE - r + w);
+ }
+};
+
+AudioRecorder recorder;
+
+void wavWriterTask(void*) {
+ File file;
+ WAVHeader hdr;
+ bool fileOpen = false;
+
+ size_t writeBufferFill = 0;
+ uint32_t bytesSinceFlush = 0;
+
+ while (true) {
+ // --- START REQUEST ---
+ if (recorder.startRequested && !fileOpen) {
+ recorder.startRequested = false;
+
+ // Datei mit Zeitstempel erstellen
+ char filename[64];
+ snprintf(filename, sizeof(filename), "/recording.wav");
+
+ file = SD_MMC.open(filename, FILE_WRITE);
+ if (!file) {
+ Serial.println("Failed to open file! \"/recording.wav\"");
+ continue;
+ }
+ // prepeare header
+ hdr.sampleRate = recorder.sampleRate;
+ hdr.byteRate = recorder.sampleRate * 2 * 2; // Stereo, 16-bit
+ hdr.blockAlign = 2 * 2; // 8 bytes per frame
+ hdr.bits = 16;
+ hdr.dataSize = 0;
+ hdr.size = 36; // 44 - 8 (RIFF header)
+
+ file.write((uint8_t*)&hdr, sizeof(hdr));
+ recorder.totalBytes = 0;
+ writeBufferFill = 0;
+ bytesSinceFlush = 0;
+ fileOpen = true;
+ recorder.running = true;
+
+ Serial.printf("Recording started: %s\n", filename);
+ }
+
+ // --- WRITE DATA ---
+ if (fileOpen) {
+ // fill local buffer
+ while (writeBufferFill < WRITE_CHUNK_SIZE) {
+ size_t spaceInLocalBuffer = WRITE_CHUNK_SIZE - writeBufferFill;
+ size_t bytesRead = recorder.pop(writeBuffer + writeBufferFill, spaceInLocalBuffer);
+
+ if (bytesRead == 0) break; // ringbuffer is empty
+
+ writeBufferFill += bytesRead;
+ }
+
+ // write full block to SD
+ if (writeBufferFill >= WRITE_CHUNK_SIZE) {
+ size_t written = file.write(writeBuffer.get(), WRITE_CHUNK_SIZE);
+ if (written != WRITE_CHUNK_SIZE) {
+ Serial.println("SD write error!");
+ // Optional: error handling, hold buffer?
+ }
+
+ recorder.totalBytes += written;
+ bytesSinceFlush += written;
+ writeBufferFill = 0; // buffer is empty (or move remaining data)
+
+ // Periodic flush for data integrity
+ if (bytesSinceFlush >= SD_FLUSH_INTERVAL) {
+ file.flush();
+ bytesSinceFlush = 0;
+ }
+ }
+ }
+
+ // --- STOP REQUEST ---
+ if (recorder.stopRequested && fileOpen) {
+ recorder.stopRequested = false;
+
+ // Write remaining data to local buffer
+ if (writeBufferFill > 0) {
+ file.write(writeBuffer.get(), writeBufferFill);
+ recorder.totalBytes += writeBufferFill;
+ }
+
+ // Update header
+ hdr.dataSize = recorder.totalBytes;
+ hdr.size = recorder.totalBytes + 36;
+
+ file.seek(0);
+ file.write((uint8_t*)&hdr, sizeof(hdr));
+ file.flush();
+ file.close();
+
+ fileOpen = false;
+ writeBufferFill = 0;
+ recorder.running = false;
+ Serial.printf("Recording stopped. Total bytes: %u, Overflows: %u\n", recorder.totalBytes, recorder.overflowCount);
+ }
+
+ // Small delay to feed watchdog and release CPU
+ // But not too long, so that the ring buffer does not overflow!
+ vTaskDelay(pdMS_TO_TICKS(1)); // 1ms = ~176 Bytes bei 44.1kHz Stereo 32-bit
+ }
+}
+
+//______________________________________________________________________________________________________________________________________________________________________________________________________
+
+#define I2S_DOUT 25
+#define I2S_BCLK 27
+#define I2S_LRC 26
+#define SD_MMC_D0 2
+#define SD_MMC_CLK 14
+#define SD_MMC_CMD 15
+
+String ssid = "*****";
+String password = "*****";
+
+uint32_t t;
+enum {IDLE, RECORDING, FINISH, PLAYBACK};
+uint8_t state = IDLE;
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Serial.begin(115200);
+ Audio::audio_info_callback = my_audio_info;
+ Serial.print("\n\n");
+
+ wifiMulti.addAP(ssid.c_str(), password.c_str());
+ wifiMulti.run(); // if there are multiple access points, use the strongest one
+ while (WiFi.status() != WL_CONNECTED) delay(1500);
+ pinMode(SD_MMC_D0, INPUT_PULLUP);
+ SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
+ SD_MMC.begin("/sdcard", true);
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(20); // default 0...21
+ audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/"); // aac
+
+ rec_buffer.alloc_array(REC_BUFFER_SIZE, "rec_buffer"); // allocate in PSRAM
+ writeBuffer.alloc_array(WRITE_CHUNK_SIZE, "writeBuffer"); // allocate in PSRAM
+ xTaskCreatePinnedToCore(wavWriterTask, "wavWriter", 4096, nullptr, 1, nullptr, 0); // start recorder task
+ Serial.printf("recorder task started, Free heap: %u\n", ESP.getFreeHeap());
+ t = millis();
+}
+
+void loop() {
+ audio.loop();
+ vTaskDelay(1);
+ if (t + 5000 < millis() && state == IDLE && audio.isRunning()) {
+ Serial.println("start recording");
+ state = RECORDING;
+ recorder.sampleRate = audio.getSampleRate();
+ recorder.startRequested = true;
+ }
+ if (t + 15000 < millis() && state == RECORDING) {
+ Serial.println("stop recording");
+ state = FINISH;
+ recorder.stopRequested = true;
+ audio.stopSong();
+ }
+ if (t + 17000 < millis() && state == FINISH) {
+ state = PLAYBACK;
+ audio.connecttoFS(SD_MMC, "/recording.wav");
+ }
+ if (t + 29000 < millis() && state == PLAYBACK && !audio.isRunning()) {
+ state = IDLE;
+ audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/");
+ t = millis();
+ }
+}
+//______________________________________________________________________________________________________________________________________________________________________________________________________
+void audio_process_raw_samples(int32_t* outBuff, int16_t validSamples) { // samples are available, write in wav file
+ if (recorder.running == true) {
+ recorder.push16(outBuff, validSamples);
+ }
+}
diff --git a/libraries/ESP32-audioI2S/examples/ES8311/ES8311_DS.pdf b/libraries/ESP32-audioI2S/examples/ES8311/ES8311_DS.pdf
new file mode 100644
index 0000000..dd5b658
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/ES8311/ES8311_DS.pdf differ
diff --git a/libraries/ESP32-audioI2S/examples/ES8311/es8311.cpp b/libraries/ESP32-audioI2S/examples/ES8311/es8311.cpp
new file mode 100644
index 0000000..1da6c0a
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES8311/es8311.cpp
@@ -0,0 +1,316 @@
+/*
+ ES8311 - An ES8311 Codec driver library for Arduino
+
+ 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 .
+
+ examples:
+
+ //one I2C bus: (default behaviour)
+ ES8311 es;
+ es.begin(sda, scl);
+
+ //two I2C busses:
+ TwoWire i2cBusOne = TwoWire(0);
+ TwoWire i2cBusTwo = TwoWire(1);
+ ES8311 es(&i2cBusOne);
+
+ i2cBusOne.begin(sda, scl, 400000);
+*/
+
+#include "es8311.h"
+
+/* codec hifi mclk clock divider coefficients */
+static const struct _coeff_div coeff_div[] = {
+ /*!end();
+ }
+}
+
+/*
+* look for the coefficient in coeff_div[] table
+*/
+int ES8311::get_coeff(uint32_t mclk, uint32_t rate){
+ for (int i = 0; i < (sizeof(coeff_div) / sizeof(coeff_div[0])); i++) {
+ if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+bool ES8311::begin(int32_t sda, int32_t scl, uint32_t frequency) {
+ bool ok = true;
+ uint8_t reg = 0;
+
+ if((sda >= 0) && (scl >= 0)){
+ ok = _TwoWireInstance->begin(sda, scl, frequency);
+ _TwoWireInstance->beginTransmission(ES8311_ADDR);
+ ok = (Wire.endTransmission() == 0);
+ if(!ok) {
+ _TwoWireInstance->end();
+ log_e("ES8311 not found"); return false;
+ }
+ }
+ else {
+ log_e("Invalid SDA/SCL pins");
+ return false;
+ }
+
+ ok |= WriteReg(0x00, 0x1F); // Reset
+ vTaskDelay(20 / portTICK_PERIOD_MS);
+ ok |= WriteReg(0x00, 0x00); // Release reset
+ ok |= WriteReg(0x00, 0x80); // Power on
+
+ ok |= WriteReg(0x01, 0x3F); // Enable all clocks
+
+ reg = ReadReg(0x06);
+ reg &= ~BIT(5); // SCLK (BCLK) pin not inverted
+ ok |= WriteReg(0x06, reg); //
+
+ ok |= setSampleRate(ES8311_SAMPLE_RATE48); // default
+ ok |= setBitsPerSample(ES8311_BITS_PER_SAMPLE16); // default
+
+ ok |= WriteReg(0x0D, 0x01); // Power up analog circuitry
+ ok |= WriteReg(0x0E, 0x02); // Enable analog PGA, enable ADC modulator
+ ok |= WriteReg(0x12, 0x00); // Power-up DAC
+ ok |= WriteReg(0x13, 0x10); // Enable output to HP drive
+ ok |= WriteReg(0x1C, 0x6A); // ADC Equalizer bypass, cancel DC offset in digital domain
+ ok |= WriteReg(0x37, 0x08); // Bypass DAC equalizer
+
+ return ok;
+}
+
+bool ES8311::setVolume(uint8_t volume){ // 0...100
+ if (volume > 100) {volume = 100;}
+ int reg32;
+ if (volume == 0) {reg32 = 0;}
+ else { reg32 = ((volume) * 256 / 100) - 1;}
+ return WriteReg(0x32, reg32);
+}
+
+uint8_t ES8311::getVolume(){
+ uint8_t reg32 = ReadReg(0x32);
+ uint8_t volume;
+ if (reg32 == 0) {
+ volume = 0;
+ } else {
+ volume = ((reg32 * 100) / 256) + 1;
+ }
+ return volume;
+}
+
+bool ES8311::setSampleRate(uint32_t sample_rate){
+ uint8_t reg = 0;
+ bool ok = true;
+ _mclk_hz = sample_rate * 256; // default MCLK frequency
+ if(sample_rate > 64000) _mclk_hz /= 2;
+ int coeff = get_coeff(_mclk_hz, sample_rate);
+ if (coeff < 0) {log_e("Invalid sample rate %i", sample_rate); return false;}
+ const struct _coeff_div *const selected_coeff = &coeff_div[coeff];
+ reg = ReadReg(0x02);
+ reg |= (selected_coeff->pre_div - 1) << 5;
+ reg |= selected_coeff->pre_multi << 3;
+ ok |= WriteReg(0x02, reg); // Set pre_div and pre_multi
+ const uint8_t reg03 = (selected_coeff->fs_mode << 6) | selected_coeff->adc_osr;
+ ok |= WriteReg(0x03, reg03); // Set fs_mode and adc_osr
+ ok |= WriteReg(0x04, selected_coeff->dac_osr); // Set dac_osr
+ const uint8_t reg05 = ((selected_coeff->adc_div - 1) << 4) | (selected_coeff->dac_div - 1);
+ ok |= WriteReg(0x05, reg05); // Set adc_div and dac_div
+ reg = ReadReg(0x06);
+ reg &= 0xE0;
+ if (selected_coeff->bclk_div < 19) {reg |= (selected_coeff->bclk_div - 1) << 0;}
+ else { reg |= (selected_coeff->bclk_div) << 0;}
+ ok |= WriteReg(0x06, reg); // Set bclk_div
+ reg = ReadReg(0x07);
+ reg &= 0xC0;
+ reg |= selected_coeff->lrck_h << 0;
+ ok |= WriteReg(0x07, reg); // Set lrck_h
+ ok |= WriteReg(0x08, selected_coeff->lrck_l); // Set lrck_l
+ return ok;
+}
+
+bool ES8311::setBitsPerSample(uint8_t bps){
+ uint8_t reg09 = ReadReg(0x09);
+ uint8_t reg0A = ReadReg(0x0A);
+ switch (bps) {
+ case 16: reg09 |= (3 << 2); reg0A |= (3 << 2); break;
+ case 18: reg09 |= (2 << 2); reg0A |= (2 << 2); break;
+ case 20: reg09 |= (1 << 2); reg0A |= (1 << 2); break;
+ case 24: reg09 |= (0 << 2); reg0A |= (0 << 2); break;
+ case 32: reg09 |= (4 << 2); reg0A |= (4 << 2); break;
+ default: return false; // Invalid bits per sample
+ }
+ bool ok = WriteReg(0x09, reg09);
+ ok |= WriteReg(0x0A, reg0A);
+ return ok;
+}
+
+bool ES8311::enableMicrophone(bool enable){
+ uint8_t reg = 0x1A; // enable analog MIC and max PGA gain
+ if (enable) {
+ reg |= BIT(6);
+ }
+ bool ok = WriteReg(0x17, 0xC8); // ADC_VOLUME
+ ok |= WriteReg(0x14, reg); // Enable MIC
+ return ok;
+}
+
+bool ES8311::setMicrophoneGain(uint8_t gain){ // 0...7
+ uint8_t reg = ReadReg(0x16);
+ reg &= 0xF8; // Clear gain bits
+ if (gain > 7) {gain = 7;}
+ reg |= gain; // Set gain bits
+ bool ok = WriteReg(0x16, gain); // ADC_VOLUME
+ return ok;
+}
+
+uint8_t ES8311::getMicrophoneGain(){
+ uint8_t reg = ReadReg(0x16);
+ return (reg & 0x07); // Get gain bits
+}
+
+bool ES8311::WriteReg(uint8_t reg, uint8_t val){
+ _TwoWireInstance->beginTransmission(ES8311_ADDR);
+ _TwoWireInstance->write(reg);
+ _TwoWireInstance->write(val);
+ return _TwoWireInstance->endTransmission() == 0;
+}
+
+uint8_t ES8311::ReadReg(uint8_t reg){
+ _TwoWireInstance->beginTransmission(ES8311_ADDR);
+ _TwoWireInstance->write(reg);
+ _TwoWireInstance->endTransmission(false);
+
+ uint8_t val = 0u;
+ _TwoWireInstance->requestFrom(uint16_t(ES8311_ADDR), (uint8_t)1, true);
+ if(_TwoWireInstance->available() >= 1){
+ val = _TwoWireInstance->read();
+ }
+ _TwoWireInstance->endTransmission();
+ return val;
+}
+
+void ES8311::read_all(){
+ for (uint8_t i = 0; i < 0x4A; i++) {
+ Serial.printf("0x%02X: 0x%02X\n", i, ReadReg(i));
+ }
+}
+
diff --git a/libraries/ESP32-audioI2S/examples/ES8311/es8311.h b/libraries/ESP32-audioI2S/examples/ES8311/es8311.h
new file mode 100644
index 0000000..842fb6c
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES8311/es8311.h
@@ -0,0 +1,48 @@
+#pragma once
+#include
+#include
+
+
+#define ES8311_ADDR 0x18
+#define ES8311_SAMPLE_RATE48 48000
+#define ES8311_BITS_PER_SAMPLE16 16
+
+struct _coeff_div { /* Clock coefficient structure */
+ uint32_t mclk; /* mclk frequency */
+ uint32_t rate; /* sample rate */
+ uint8_t pre_div; /* the pre divider with range from 1 to 8 */
+ uint8_t pre_multi; /* the pre multiplier with 0: 1x, 1: 2x, 2: 4x, 3: 8x selection */
+ uint8_t adc_div; /* adcclk divider */
+ uint8_t dac_div; /* dacclk divider */
+ uint8_t fs_mode; /* double speed or single speed, =0, ss, =1, ds */
+ uint8_t lrck_h; /* adclrck divider and daclrck divider */
+ uint8_t lrck_l;
+ uint8_t bclk_div; /* sclk divider */
+ uint8_t adc_osr; /* adc osr */
+ uint8_t dac_osr; /* dac osr */
+};
+
+class ES8311{
+
+private:
+ TwoWire *_TwoWireInstance = NULL; // TwoWire Instance
+ uint32_t _mclk_hz = 48000 * 256; // default MCLK frequency
+public:
+ // Constructor.
+ ES8311(TwoWire *TwoWireInstance = &Wire);
+ ~ES8311();
+ bool begin(int32_t sda, int32_t scl, uint32_t frequency);
+ bool setVolume(uint8_t volume);
+ uint8_t getVolume();
+ bool setSampleRate(uint32_t sample_rate);
+ bool setBitsPerSample(uint8_t bps);
+ bool enableMicrophone(bool enable);
+ bool setMicrophoneGain(uint8_t gain);
+ uint8_t getMicrophoneGain();
+ void read_all();
+protected:
+ int get_coeff(uint32_t mclk, uint32_t rate);
+ bool WriteReg(uint8_t reg, uint8_t val);
+ uint8_t ReadReg(uint8_t reg);
+};
+
diff --git a/libraries/ESP32-audioI2S/examples/ES8311/main.cpp b/libraries/ESP32-audioI2S/examples/ES8311/main.cpp
new file mode 100644
index 0000000..37e785b
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES8311/main.cpp
@@ -0,0 +1,62 @@
+#include "Arduino.h"
+#include "Audio.h"
+#include "WiFi.h"
+#include "es8311.h"
+#include "Wire.h"
+
+// Pins: Waveshare ESP32-P4-Module-DEV-KIT and ESP32-P4-NANO High-performance Development Board
+#define I2S_DOUT 9 // DSDIN pin(es8311)
+#define I2S_BCLK 12 // SCLK
+#define I2S_MCLK 13
+#define I2S_LRC 10 // LRCK
+#define I2C_SCL 8
+#define I2C_SDA 7
+#define PA_ENABLE 53
+
+Audio audio;
+ES8311 es;
+
+String ssid = "*****";
+String password = "*****";
+
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ Serial.print("\n\n");
+ Serial.println("----------------------------------");
+ Serial.printf("ESP32 Chip: %s\n", ESP.getChipModel());
+ Serial.printf("Arduino Version: %d.%d.%d\n", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH);
+ Serial.printf("ESP-IDF Version: %d.%d.%d\n", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
+ Serial.printf("ARDUINO_LOOP_STACK_SIZE %d words (32 bit)\n", CONFIG_ARDUINO_LOOP_STACK_SIZE);
+ Serial.println("----------------------------------");
+ Serial.print("\n\n");
+
+ WiFi.begin(ssid.c_str(), password.c_str());
+ while (WiFi.status() != WL_CONNECTED) {delay(1500); Serial.print(".");}
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT, I2S_MCLK);
+ audio.setVolume(21); // default 0...21
+
+ pinMode(PA_ENABLE, OUTPUT);
+ digitalWrite(PA_ENABLE, HIGH);
+
+ if(!es.begin(I2C_SDA, I2C_SCL, 400000)) log_e("ES8311 begin failed");
+ es.setVolume(50);
+ es.setBitsPerSample(16);
+
+ // es.setSampleRate(22050);
+ // es.read_all();
+ // audio.connecttohost("http://www.wdr.de/wdrlive/media/einslive.m3u");
+ audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/"); // aac
+
+}
+
+void loop() {
+ audio.loop();
+ vTaskDelay(1);
+}
diff --git a/libraries/ESP32-audioI2S/examples/ES8388/ES8388.cpp b/libraries/ESP32-audioI2S/examples/ES8388/ES8388.cpp
new file mode 100644
index 0000000..bf286b5
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES8388/ES8388.cpp
@@ -0,0 +1,270 @@
+#include
+#include "ES8388.h"
+#include
+
+#define ES8388_ADDR 0x10
+
+/* ES8388 register */
+#define ES8388_CONTROL1 0x00
+#define ES8388_CONTROL2 0x01
+#define ES8388_CHIPPOWER 0x02
+#define ES8388_ADCPOWER 0x03
+#define ES8388_DACPOWER 0x04
+#define ES8388_CHIPLOPOW1 0x05
+#define ES8388_CHIPLOPOW2 0x06
+#define ES8388_ANAVOLMANAG 0x07
+#define ES8388_MASTERMODE 0x08
+
+/* ADC */
+#define ES8388_ADCCONTROL1 0x09
+#define ES8388_ADCCONTROL2 0x0a
+#define ES8388_ADCCONTROL3 0x0b
+#define ES8388_ADCCONTROL4 0x0c
+#define ES8388_ADCCONTROL5 0x0d
+#define ES8388_ADCCONTROL6 0x0e
+#define ES8388_ADCCONTROL7 0x0f
+#define ES8388_ADCCONTROL8 0x10
+#define ES8388_ADCCONTROL9 0x11
+#define ES8388_ADCCONTROL10 0x12
+#define ES8388_ADCCONTROL11 0x13
+#define ES8388_ADCCONTROL12 0x14
+#define ES8388_ADCCONTROL13 0x15
+#define ES8388_ADCCONTROL14 0x16
+
+/* DAC */
+#define ES8388_DACCONTROL1 0x17
+#define ES8388_DACCONTROL2 0x18
+#define ES8388_DACCONTROL3 0x19
+#define ES8388_DACCONTROL4 0x1a
+#define ES8388_DACCONTROL5 0x1b
+#define ES8388_DACCONTROL6 0x1c
+#define ES8388_DACCONTROL7 0x1d
+#define ES8388_DACCONTROL8 0x1e
+#define ES8388_DACCONTROL9 0x1f
+#define ES8388_DACCONTROL10 0x20
+#define ES8388_DACCONTROL11 0x21
+#define ES8388_DACCONTROL12 0x22
+#define ES8388_DACCONTROL13 0x23
+#define ES8388_DACCONTROL14 0x24
+#define ES8388_DACCONTROL15 0x25
+#define ES8388_DACCONTROL16 0x26
+#define ES8388_DACCONTROL17 0x27
+#define ES8388_DACCONTROL18 0x28
+#define ES8388_DACCONTROL19 0x29
+#define ES8388_DACCONTROL20 0x2a
+#define ES8388_DACCONTROL21 0x2b
+#define ES8388_DACCONTROL22 0x2c
+#define ES8388_DACCONTROL23 0x2d
+#define ES8388_DACCONTROL24 0x2e
+#define ES8388_DACCONTROL25 0x2f
+#define ES8388_DACCONTROL26 0x30
+#define ES8388_DACCONTROL27 0x31
+#define ES8388_DACCONTROL28 0x32
+#define ES8388_DACCONTROL29 0x33
+#define ES8388_DACCONTROL30 0x34
+
+bool ES8388::write_reg(uint8_t slave_add, uint8_t reg_add, uint8_t data)
+{
+ Wire.beginTransmission(slave_add);
+ Wire.write(reg_add);
+ Wire.write(data);
+ return Wire.endTransmission() == 0;
+}
+
+bool ES8388::read_reg(uint8_t slave_add, uint8_t reg_add, uint8_t &data)
+{
+ bool retval = false;
+ Wire.beginTransmission(slave_add);
+ Wire.write(reg_add);
+ Wire.endTransmission(false);
+ Wire.requestFrom((uint16_t)slave_add, (uint8_t)1, true);
+ if (Wire.available() >= 1)
+ {
+ data = Wire.read();
+ retval = true;
+ }
+ return retval;
+}
+
+bool ES8388::begin(int32_t sda, int32_t scl, uint32_t frequency)
+{
+ bool res = identify(sda, scl, frequency);
+
+ if (res == true)
+ {
+
+ /* mute DAC during setup, power up all systems, slave mode */
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL3, 0x04);
+ res &= write_reg(ES8388_ADDR, ES8388_CONTROL2, 0x50);
+ res &= write_reg(ES8388_ADDR, ES8388_CHIPPOWER, 0x00);
+ res &= write_reg(ES8388_ADDR, ES8388_MASTERMODE, 0x00);
+
+ /* power up DAC and enable LOUT1+2 / ROUT1+2, ADC sample rate = DAC sample rate */
+ res &= write_reg(ES8388_ADDR, ES8388_DACPOWER, 0x3e);
+ res &= write_reg(ES8388_ADDR, ES8388_CONTROL1, 0x12);
+
+ /* DAC I2S setup: 16 bit word length, I2S format; MCLK / Fs = 256*/
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL1, 0x18);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL2, 0x02);
+
+ /* DAC to output route mixer configuration: ADC MIX TO OUTPUT */
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL16, 0x1B);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL17, 0x90);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL20, 0x90);
+
+ /* DAC and ADC use same LRCK, enable MCLK input; output resistance setup */
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL21, 0x80);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL23, 0x00);
+
+ /* DAC volume control: 0dB (maximum, unattenuated) */
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL5, 0x00);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL4, 0x00);
+
+ /* power down ADC while configuring; volume: +9dB for both channels */
+ res &= write_reg(ES8388_ADDR, ES8388_ADCPOWER, 0xff);
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL1, 0x88); // +24db
+
+ /* select LINPUT2 / RINPUT2 as ADC input; stereo; 16 bit word length, format right-justified, MCLK / Fs = 256 */
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL2, 0xf0); // 50
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL3, 0x80); // 00
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL4, 0x0e);
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL5, 0x02);
+
+ /* set ADC volume */
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL8, 0x20);
+ res &= write_reg(ES8388_ADDR, ES8388_ADCCONTROL9, 0x20);
+
+ /* set LOUT1 / ROUT1 volume: 0dB (unattenuated) */
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL24, 0x1e);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL25, 0x1e);
+
+ /* set LOUT2 / ROUT2 volume: 0dB (unattenuated) */
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL26, 0x1e);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL27, 0x1e);
+
+ /* power up and enable DAC; power up ADC (no MIC bias) */
+ res &= write_reg(ES8388_ADDR, ES8388_DACPOWER, 0x3c);
+ res &= write_reg(ES8388_ADDR, ES8388_DACCONTROL3, 0x00);
+ res &= write_reg(ES8388_ADDR, ES8388_ADCPOWER, 0x00);
+
+ /* set up MCLK) */
+ #ifdef FUNC_GPIO0_CLK_OUT1
+ PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
+ #endif
+ WRITE_PERI_REG(PIN_CTRL, 0xFFF0);
+ }
+ return res;
+}
+
+/**
+ * @brief (un)mute one of the two outputs or main dac output of the ES8388 by switching of the output register bits. Does not really mute the selected output, causes an attenuation.
+ * hence should be used in conjunction with appropriate volume setting. Main dac output mute does mute both outputs
+ *
+ * @param out
+ * @param muted
+ */
+void ES8388::mute(const ES8388_OUT out, const bool muted)
+{
+ uint8_t reg_addr;
+ uint8_t mask_mute;
+ uint8_t mask_val;
+
+ switch (out)
+ {
+ case ES_OUT1:
+ reg_addr = ES8388_DACPOWER;
+ mask_mute = (3 << 4);
+ mask_val = muted ? 0 : mask_mute;
+ break;
+ case ES_OUT2:
+ reg_addr = ES8388_DACPOWER;
+ mask_mute = (3 << 2);
+ mask_val = muted ? 0 : mask_mute;
+ break;
+ case ES_MAIN:
+ default:
+ reg_addr = ES8388_DACCONTROL3;
+ mask_mute = 1 << 2;
+ mask_val = muted ? mask_mute : 0;
+ break;
+ }
+
+ uint8_t reg;
+ if (read_reg(ES8388_ADDR, reg_addr, reg))
+ {
+ reg = (reg & ~mask_mute) | (mask_val & mask_mute);
+ write_reg(ES8388_ADDR, reg_addr, reg);
+ }
+}
+
+/**
+ * @brief Set volume gain for the main dac, or for one of the two output channels. Final gain = main gain + out channel gain
+ *
+ * @param out which gain setting to control
+ * @param vol 0-100 (100 is max)
+ */
+void ES8388::volume(const ES8388_OUT out, const uint8_t vol)
+{
+ const uint32_t max_vol = 100; // max input volume value
+
+ const int32_t max_vol_val = out == ES8388_OUT::ES_MAIN ? 96 : 0x21; // max register value for ES8388 out volume
+
+ uint8_t lreg = 0, rreg = 0;
+
+ switch (out)
+ {
+ case ES_MAIN:
+ lreg = ES8388_DACCONTROL4;
+ rreg = ES8388_DACCONTROL5;
+ break;
+ case ES_OUT1:
+ lreg = ES8388_DACCONTROL24;
+ rreg = ES8388_DACCONTROL25;
+ break;
+ case ES_OUT2:
+ lreg = ES8388_DACCONTROL26;
+ rreg = ES8388_DACCONTROL27;
+ break;
+ }
+
+ uint8_t vol_val = vol > max_vol ? max_vol_val : (max_vol_val * vol) / max_vol;
+
+ // main dac volume control is reverse scale (lowest value is loudest)
+ // hence we reverse the calculated value
+ if (out == ES_MAIN)
+ {
+ vol_val = max_vol_val - vol_val;
+ }
+
+ write_reg(ES8388_ADDR, lreg, vol_val);
+ write_reg(ES8388_ADDR, rreg, vol_val);
+}
+
+void ES8388::SetVolumeSpeaker(uint8_t vol) {
+ vol = vol * 1.6;
+ volume(ES_OUT1, vol);
+ volume(ES_MAIN, 100);
+}
+
+void ES8388::SetVolumeHeadphone(uint8_t vol){
+ vol = vol * 1.6;
+ volume(ES_OUT2, vol);
+ volume(ES_MAIN, 100);
+}
+
+/**
+ * @brief Test if device with I2C address for ES8388 is connected to the I2C bus
+ *
+ * @param sda which pin to use for I2C SDA
+ * @param scl which pin to use for I2C SCL
+ * @param frequency which frequency to use as I2C bus frequency
+ * @return true device was found
+ * @return false device was not found
+ */
+bool ES8388::identify(int32_t sda, int32_t scl, uint32_t frequency)
+{
+ Wire.begin(sda, scl, frequency);
+ Wire.beginTransmission(ES8388_ADDR);
+ return Wire.endTransmission() == 0;
+}
+
diff --git a/libraries/ESP32-audioI2S/examples/ES8388/ES8388.h b/libraries/ESP32-audioI2S/examples/ES8388/ES8388.h
new file mode 100644
index 0000000..991e443
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES8388/ES8388.h
@@ -0,0 +1,27 @@
+#pragma once
+#include
+
+class ES8388
+{
+
+ bool write_reg(uint8_t slave_add, uint8_t reg_add, uint8_t data);
+ bool read_reg(uint8_t slave_add, uint8_t reg_add, uint8_t &data);
+ bool identify(int32_t sda, int32_t scl, uint32_t frequency);
+
+public:
+ bool begin(int32_t sda = -1, int32_t scl = -1, uint32_t frequency = 400000U);
+
+ enum ES8388_OUT
+ {
+ ES_MAIN, // this is the DAC output volume (both outputs)
+ ES_OUT1, // this is the additional gain for OUT1
+ ES_OUT2 // this is the additional gain for OUT2
+ };
+
+ void SetVolumeSpeaker(uint8_t vol);
+ void SetVolumeHeadphone(uint8_t vol);
+
+ void mute(const ES8388_OUT out, const bool muted);
+ void volume(const ES8388_OUT out, const uint8_t vol);
+};
+
diff --git a/libraries/ESP32-audioI2S/examples/ES8388/main.cpp b/libraries/ESP32-audioI2S/examples/ES8388/main.cpp
new file mode 100644
index 0000000..ec8f4f0
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES8388/main.cpp
@@ -0,0 +1,109 @@
+#include "Arduino.h"
+#include "WiFi.h"
+#include "SPI.h"
+#include "SD.h"
+#include "FS.h"
+#include "Wire.h"
+#include "ES8388.h"
+#include "Audio.h"
+
+
+// SPI GPIOs
+#define SD_CS 13
+#define SPI_MOSI 15
+#define SPI_MISO 2
+#define SPI_SCK 14
+
+// I2S GPIOs, the names refer on ES8388, AS1 Audio Kit V2.2 3378
+#define I2S_DSIN 35 // pin not used
+#define I2S_BCLK 27
+#define I2S_LRC 25
+#define I2S_MCLK 0
+#define I2S_DOUT 26
+
+// I2C GPIOs
+#define IIC_CLK 32
+#define IIC_DATA 33
+
+// buttons
+// #define BUTTON_2_PIN 13 // shared mit SPI_CS
+#define BUTTON_3_PIN 19
+#define BUTTON_4_PIN 23
+#define BUTTON_5_PIN 18 // Stop
+#define BUTTON_6_PIN 5 // Play
+
+// amplifier enable
+#define GPIO_PA_EN 21
+
+//Switch S1: 1-OFF, 2-ON, 3-ON, 4-OFF, 5-OFF
+
+String ssid = "*****";
+String password = "*****";
+
+ES8388 dac; // ES8388 (new board)
+int volume = 40; // 0...100
+
+Audio audio;
+
+//#####################################################################
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ Serial.println("\r\nReset");
+ Serial.printf_P(PSTR("Free mem=%l\n"), ESP.getFreeHeap());
+
+ pinMode(SD_CS, OUTPUT);
+ digitalWrite(SD_CS, HIGH);
+ SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+ SPI.setFrequency(1000000);
+
+ SD.begin(SD_CS);
+
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid.c_str(), password.c_str());
+
+ while (WiFi.status() != WL_CONNECTED){
+ Serial.print(".");
+ delay(100);
+ }
+
+ Serial.printf_P(PSTR("Connected\r\nRSSI: "));
+ Serial.print(WiFi.RSSI());
+ Serial.print(" IP: ");
+ Serial.println(WiFi.localIP());
+
+ Serial.printf("Connect to DAC codec... ");
+ while (not dac.begin(IIC_DATA, IIC_CLK)){
+ Serial.printf("Failed!\n");
+ delay(1000);
+ }
+ Serial.printf("OK\n");
+
+ dac.SetVolumeSpeaker(volume);
+ dac.SetVolumeHeadphone(volume);
+// ac.DumpRegisters();
+
+ // Enable amplifier
+ pinMode(GPIO_PA_EN, OUTPUT);
+ digitalWrite(GPIO_PA_EN, HIGH);
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT, I2S_MCLK);
+ audio.setVolume(10); // 0...21
+
+ audio.connecttohost("http://mp3channels.webradio.antenne.de:80/oldies-but-goldies");
+// audio.connecttohost("http://dg-rbb-http-dus-dtag-cdn.cast.addradio.de/rbb/antennebrandenburg/live/mp3/128/stream.mp3");
+// audio.connecttospeech("Wenn die Hunde schlafen, kann der Wolf gut Schafe stehlen.", "de");
+
+}
+
+//-----------------------------------------------------------------------
+
+void loop(){
+ vTaskDelay(1);
+ audio.loop();
+}
diff --git a/libraries/ESP32-audioI2S/examples/ES9038/Readme.md b/libraries/ESP32-audioI2S/examples/ES9038/Readme.md
new file mode 100644
index 0000000..1281c99
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ES9038/Readme.md
@@ -0,0 +1,19 @@
+# 32 bit ES9038
+
+For those who have a board with a 32bit DAC
+[DJ202](https://github.com/dj202) sent us a solution:
+
+
+There are some great sounding and cheap 32 bit decoders available like boards with the ES9038Q2M that don't work because the library outputs 16 bits data.
+You only need to change 3 lines lines of code to change it to 32bit:
+## Audio.cpp
+in Audio::Audio
+````c++
+m_i2s_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO);
+in Audio::resampleTo48kStereo:
+
+m_samplesBuff48K[outputIndex * 2] = clipToInt16(outLeft);
+m_samplesBuff48K[outputIndex * 2 + 1] = 0x00;
+m_samplesBuff48K[outputIndex * 2 + 2] = clipToInt16(outRight);
+m_samplesBuff48K[outputIndex * 2 + 3] = 0x00;
+````
diff --git a/libraries/ESP32-audioI2S/examples/ESP32_A1S/AI-Thinker ESP32-Audio-Kit.jpg b/libraries/ESP32-audioI2S/examples/ESP32_A1S/AI-Thinker ESP32-Audio-Kit.jpg
new file mode 100644
index 0000000..ad858f4
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/ESP32_A1S/AI-Thinker ESP32-Audio-Kit.jpg differ
diff --git a/libraries/ESP32-audioI2S/examples/ESP32_A1S/ESP32-A1S Product Specification.pdf b/libraries/ESP32-audioI2S/examples/ESP32_A1S/ESP32-A1S Product Specification.pdf
new file mode 100644
index 0000000..19421ca
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/ESP32_A1S/ESP32-A1S Product Specification.pdf differ
diff --git a/libraries/ESP32-audioI2S/examples/ESP32_A1S/esp32-a1s_v2.3_specification.pdf b/libraries/ESP32-audioI2S/examples/ESP32_A1S/esp32-a1s_v2.3_specification.pdf
new file mode 100644
index 0000000..762ab33
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/ESP32_A1S/esp32-a1s_v2.3_specification.pdf differ
diff --git a/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/ESP32_TTGO-TAudio.ino b/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/ESP32_TTGO-TAudio.ino
new file mode 100644
index 0000000..0d0a766
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/ESP32_TTGO-TAudio.ino
@@ -0,0 +1,59 @@
+// Copied from https://github.com/LilyGO/TTGO-TAudio/issues/12
+
+
+// Required Libraries (Download zips and add to the Arduino IDE library).
+#include "Arduino.h"
+#include // https://github.com/CelliesProjects/wm8978-esp32
+#include // https://github.com/schreibfaul1/ESP32-audioI2S
+
+// T-Audio 1.6 WM8978 I2C pins.
+#define I2C_SDA 19
+#define I2C_SCL 18
+
+// T-Audio 1.6 WM8978 I2S pins.
+#define I2S_BCK 33
+#define I2S_WS 25
+#define I2S_DOUT 26
+
+// T-Audio 1.6 WM8978 MCLK gpio number
+#define I2S_MCLKPIN 0
+
+Audio audio;
+WM8978 dac;
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+
+ // Setup wm8978 I2C interface.
+ if (!dac.begin(I2C_SDA, I2C_SCL)) {
+ ESP_LOGE(TAG, "Error setting up dac: System halted.");
+ while (1) delay(100);
+ }
+
+ // Select I2S pins
+ audio.setPinout(I2S_BCK, I2S_WS, I2S_DOUT);
+ audio.i2s_mclk_pin_select(I2S_MCLKPIN);
+
+ // WiFi Settings here.
+ WiFi.begin("EnterSSIDHere", "EnterPasswordHere");
+ while (!WiFi.isConnected()) {
+ delay(10);
+ }
+ ESP_LOGI(TAG, "Connected. Starting MP3...");
+ // Enter your Icecast station URL here.
+ audio.setVolume(21);
+ audio.connecttohost("http://hestia2.cdnstream.com/1458_128");
+ // Volume control.
+ dac.setSPKvol(63); // Change volume here for board speaker output (Max 63).
+ dac.setHPvol(63, 63); // Change volume here for headphone jack left, right channel.
+}
+
+void loop() {
+ vTaskDelay(1);
+ audio.loop();
+}
diff --git a/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/T9V1.5.jpg b/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/T9V1.5.jpg
new file mode 100644
index 0000000..5ccb726
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/T9V1.5.jpg differ
diff --git a/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/schematic.pdf b/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/schematic.pdf
new file mode 100644
index 0000000..f5b444d
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/ESP32_TTGO-TAudio/schematic.pdf differ
diff --git a/libraries/ESP32-audioI2S/examples/Ethernet/ETH_IP101.ino b/libraries/ESP32-audioI2S/examples/Ethernet/ETH_IP101.ino
new file mode 100644
index 0000000..5144d52
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/Ethernet/ETH_IP101.ino
@@ -0,0 +1,92 @@
+#include "Arduino.h"
+#include "Audio.h"
+#include "ETH.h"
+
+#ifdef CONFIG_IDF_TARGET_ESP32
+#define I2S_DOUT 25
+#define I2S_BCLK 27
+#define I2S_LRC 26
+#define ETH_PHY_TYPE ETH_PHY_TLK110
+#define ETH_PHY_MDC 23
+#define ETH_PHY_MDIO 18
+#define ETH_PHY_POWER -1
+#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN
+#endif
+
+#ifdef CONFIG_IDF_TARGET_ESP32P4
+#define I2S_DOUT 22
+#define I2S_BCLK 20
+#define I2S_LRC 21
+#define ETH_PHY_TYPE ETH_PHY_TLK110
+#define ETH_PHY_MDC 31
+#define ETH_PHY_MDIO 52
+#define ETH_PHY_POWER 51
+#define ETH_CLK_MODE EMAC_CLK_EXT_IN
+#endif
+
+Audio audio;
+
+static bool eth_connected = false;
+
+void onEvent(arduino_event_id_t event) {
+ switch (event) {
+ case ARDUINO_EVENT_ETH_START:
+ Serial.println("ETH Started");
+ // The hostname must be set after the interface is started, but needs
+ // to be set before DHCP, so set it from the event handler thread.
+ ETH.setHostname("esp32-ethernet");
+ break;
+ case ARDUINO_EVENT_ETH_CONNECTED: Serial.println("ETH Connected"); break;
+ case ARDUINO_EVENT_ETH_GOT_IP:
+ Serial.println("ETH Got IP");
+ Serial.println(ETH);
+ eth_connected = true;
+ break;
+ case ARDUINO_EVENT_ETH_LOST_IP:
+ Serial.println("ETH Lost IP");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_DISCONNECTED:
+ Serial.println("ETH Disconnected");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_STOP:
+ Serial.println("ETH Stopped");
+ eth_connected = false;
+ break;
+ default: break;
+ }
+}
+
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ Serial.print("A\n\n");
+ Serial.println("----------------------------------");
+ Serial.printf("ESP32 Chip: %s\n", ESP.getChipModel());
+ Serial.printf("Arduino Version: %d.%d.%d\n", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH);
+ Serial.printf("ESP-IDF Version: %d.%d.%d\n", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
+ Serial.printf("ARDUINO_LOOP_STACK_SIZE %d words (32 bit)\n", CONFIG_ARDUINO_LOOP_STACK_SIZE);
+ Serial.println("----------------------------------");
+ Serial.print("\n\n");
+ Network.onEvent(onEvent);
+ ETH.begin();
+ while (!eth_connected) delay(100);
+ Serial.println("ETH Connected");
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(21); // default 0...21
+ audio.connecttohost("http://stream.antennethueringen.de/live/aac-64/stream.antennethueringen.de/"); // aac
+ pinMode(53, OUTPUT);
+ digitalWrite(53, HIGH);
+}
+
+void loop() {
+ audio.loop();
+ vTaskDelay(1);
+}
+
diff --git a/libraries/ESP32-audioI2S/examples/Ethernet/ETH_W5500.ino b/libraries/ESP32-audioI2S/examples/Ethernet/ETH_W5500.ino
new file mode 100644
index 0000000..0c77277
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/Ethernet/ETH_W5500.ino
@@ -0,0 +1,74 @@
+#include "Arduino.h" // >= Arduino V3
+#include
+#include
+#include "Audio.h"
+
+Audio audio;
+
+#define USE_TWO_ETH_PORTS 0
+#define ETH_PHY_TYPE ETH_PHY_W5500
+
+// GPIOs
+#define ETH_PHY_ADDR 1
+#define ETH_PHY_CS 3
+#define ETH_PHY_IRQ 8
+#define ETH_PHY_RST 4
+#define ETH_SPI_SCK 7
+#define ETH_SPI_MISO 6
+#define ETH_SPI_MOSI 5
+#define I2S_DOUT 12
+#define I2S_BCLK 13
+#define I2S_LRC 14
+
+static bool eth_connected = false;
+
+void onEvent(arduino_event_id_t event, arduino_event_info_t info) {
+ switch (event) {
+ case ARDUINO_EVENT_ETH_START:
+ Serial.println("ETH Started");
+ ETH.setHostname("esp32-eth0"); //set eth hostname here
+ break;
+ case ARDUINO_EVENT_ETH_CONNECTED: Serial.println("ETH Connected"); break;
+ case ARDUINO_EVENT_ETH_GOT_IP: Serial.printf("ETH Got IP: '%s'\n", esp_netif_get_desc(info.got_ip.esp_netif)); Serial.println(ETH);
+ eth_connected = true;
+ break;
+ case ARDUINO_EVENT_ETH_LOST_IP:
+ Serial.println("ETH Lost IP");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_DISCONNECTED:
+ Serial.println("ETH Disconnected");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_STOP:
+ Serial.println("ETH Stopped");
+ eth_connected = false;
+ break;
+ default: break;
+ }
+}
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ Serial.print("\n\n");
+
+ Network.onEvent(onEvent);
+
+ SPI.begin(ETH_SPI_SCK, ETH_SPI_MISO, ETH_SPI_MOSI);
+ ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI);
+ while (!eth_connected) delay(100);
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(21); // default 0...21
+ audio.connecttohost("https://wdr-wdr2-ruhrgebiet.icecastssl.wdr.de/wdr/wdr2/ruhrgebiet/mp3/128/stream.mp3"); // mp3
+}
+
+void loop(){
+ audio.loop();
+ vTaskDelay(5 /portTICK_PERIOD_MS);
+}
diff --git a/libraries/ESP32-audioI2S/examples/Ethernet/WT32-ETH01.ino b/libraries/ESP32-audioI2S/examples/Ethernet/WT32-ETH01.ino
new file mode 100644
index 0000000..3c7eb84
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/Ethernet/WT32-ETH01.ino
@@ -0,0 +1,88 @@
+
+#include "Arduino.h"
+#include "Audio.h"
+#include "SD.h"
+#include "FS.h"
+
+// Digital I/O used
+#define SD_CS 5
+#define SPI_MOSI 2
+#define SPI_MISO 4
+#define SPI_SCK 17
+#define I2S_DOUT 12
+#define I2S_BCLK 14
+#define I2S_LRC 15
+
+#define ETH_PHY_TYPE ETH_PHY_LAN8720
+#define ETH_PHY_MDC 23
+#define ETH_PHY_MDIO 18
+
+#ifdef CONFIG_IDF_TARGET_ESP32
+#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN
+#endif
+
+#ifdef CONFIG_IDF_TARGET_ESP32P4
+#define ETH_CLK_MODE EMAC_CLK_EXT_IN
+#endif
+
+#include "ETH.h"
+
+Audio audio;
+
+static bool eth_connected = false;
+
+void onEvent(arduino_event_id_t event) {
+ switch (event) {
+ case ARDUINO_EVENT_ETH_START:
+ Serial.println("ETH Started");
+ // The hostname must be set after the interface is started, but needs
+ // to be set before DHCP, so set it from the event handler thread.
+ ETH.setHostname("esp32-ethernet");
+ break;
+ case ARDUINO_EVENT_ETH_CONNECTED: Serial.println("ETH Connected"); break;
+ case ARDUINO_EVENT_ETH_GOT_IP:
+ Serial.println("ETH Got IP");
+ Serial.println(ETH);
+ eth_connected = true;
+ break;
+ case ARDUINO_EVENT_ETH_LOST_IP:
+ Serial.println("ETH Lost IP");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_DISCONNECTED:
+ Serial.println("ETH Disconnected");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_STOP:
+ Serial.println("ETH Stopped");
+ eth_connected = false;
+ break;
+ default: break;
+ }
+}
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH);
+ SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+ Serial.begin(115200);
+ SD.begin(SD_CS);
+
+ Network.onEvent(onEvent);
+ ETH.begin();
+ while (!eth_connected) delay(100);
+ // Eth Connected,
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(21); // default 0...21
+ audio.connecttohost("https://wdr-wdr2-ruhrgebiet.icecastssl.wdr.de/wdr/wdr2/ruhrgebiet/mp3/128/stream.mp3"); // mp3
+}
+
+void loop(){
+ vTaskDelay(1);
+ audio.loop();
+}
diff --git a/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/AudioResampler.hpp b/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/AudioResampler.hpp
new file mode 100644
index 0000000..20dfa75
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/AudioResampler.hpp
@@ -0,0 +1,159 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "esp_log.h"
+
+
+class AudioResampleBuffer {
+public:
+ static constexpr size_t FIFO_SIZE_BYTES = 16384; // Muss Vielfaches von 4 sein (Stereo, 16 Bit)
+ static constexpr size_t I2S_BLOCK_SIZE = 512; // DMA Blockgröße
+
+
+ AudioResampleBuffer()
+ :fifoWrite(0), fifoRead(0), m_resampleCursor(0.0f) {
+ memset(fifo, 0, sizeof(fifo));
+ memset(m_inputHistory, 0, sizeof(m_inputHistory));
+ }
+ void setChannelHandle(i2s_chan_handle_t i2sHandle){
+ m_i2s = i2sHandle;
+ }
+
+ // Set the input samplerates (updated by the LRCK monitoring)
+ void setInputSamplerate(uint32_t samplerate) {
+ if (samplerate == 8000 || samplerate == 22050 || samplerate == 44100 || samplerate == 48000) {
+ m_sampleRate = samplerate;
+ ESP_LOGI("ResampleBuffer", "Input samplerate set to %u Hz", samplerate);
+ } else {
+ ESP_LOGW("ResampleBuffer", "Invalid samplerate %u Hz, defaulting to 44100 Hz", samplerate);
+ m_sampleRate = 44100;
+ }
+ }
+
+ // Muss zyklisch aufgerufen werden (z. B. aus Task)
+ void loopResample() {
+ alignas(4) uint8_t i2sBuf[I2S_BLOCK_SIZE];
+ size_t bytesRead = 0;
+ if (i2s_channel_read(m_i2s, i2sBuf, I2S_BLOCK_SIZE, &bytesRead, 50) != ESP_OK || bytesRead == 0)
+ return;
+
+ size_t inSamples = bytesRead / 4; // Stereo, 16 Bit
+ int16_t* inData = reinterpret_cast(i2sBuf);
+
+ int16_t resampled[1024];
+ size_t outSamples = resampleTo441Stereo(inData, inSamples, resampled);
+ size_t outBytes = outSamples * 4;
+
+ if (fifoFree() >= outBytes) {
+ fifoWriteBytes(reinterpret_cast(resampled), outBytes);
+ } else {
+ vTaskDelay(100);
+ // ESP_LOGW("ResampleBuffer", "FIFO voll, Daten verworfen %i Bytes", outBytes - fifoFree());
+ }
+ }
+
+ // Bluetooth Callback: muss exakt "bytes" liefern
+ int32_t getData(uint8_t* data, int32_t bytes) {
+ while (fifoAvailable() < static_cast(bytes)) {
+ vTaskDelay(1);
+ }
+ fifoReadBytes(data, bytes);
+ return bytes;
+ }
+
+private:
+private:
+ i2s_chan_handle_t m_i2s;
+ uint8_t fifo[FIFO_SIZE_BYTES];
+ size_t fifoWrite;
+ size_t fifoRead;
+ float m_sampleRate = 44100.0f;
+
+ float m_resampleCursor;
+ int16_t m_inputHistory[6]; // 3 Stereo-Samples
+
+ size_t fifoAvailable() const {
+ return (fifoWrite + FIFO_SIZE_BYTES - fifoRead) % FIFO_SIZE_BYTES;
+ }
+
+ size_t fifoFree() const {
+ return FIFO_SIZE_BYTES - fifoAvailable() - 1;
+ }
+
+ void fifoWriteBytes(const uint8_t* data, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ fifo[fifoWrite] = data[i];
+ fifoWrite = (fifoWrite + 1) % FIFO_SIZE_BYTES;
+ }
+ }
+
+ void fifoReadBytes(uint8_t* out, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ out[i] = fifo[fifoRead];
+ fifoRead = (fifoRead + 1) % FIFO_SIZE_BYTES;
+ }
+ }
+
+ // Catmull-Rom Spline Resampling von 48 kHz auf 44,1 kHz
+ size_t resampleTo441Stereo(const int16_t* input, size_t inputSamples, int16_t* output) {
+ float ratio = m_sampleRate / 44100.0f;
+ float cursor = m_resampleCursor;
+
+ size_t extendedSamples = inputSamples + 3;
+ std::vector extendedInput(extendedSamples * 2);
+
+ memcpy(&extendedInput[0], m_inputHistory, 6 * sizeof(int16_t));
+ memcpy(&extendedInput[6], input, inputSamples * 2 * sizeof(int16_t));
+
+ size_t outputIndex = 0;
+
+ auto catmullRom = [](float t, float xm1, float x0, float x1, float x2) {
+ return 0.5f * (
+ (2.0f * x0) +
+ (-xm1 + x1) * t +
+ (2.0f * xm1 - 5.0f * x0 + 4.0f * x1 - x2) * t * t +
+ (-xm1 + 3.0f * x0 - 3.0f * x1 + x2) * t * t * t
+ );
+ };
+
+ auto clip = [](float v) -> int16_t {
+ return v > 32767.0f ? 32767 : (v < -32768.0f ? -32768 : static_cast(v));
+ };
+
+ for (size_t inIdx = 1; inIdx < extendedSamples - 2; ++inIdx) {
+ int16_t xm1_l = extendedInput[(inIdx - 1) * 2];
+ int16_t x0_l = extendedInput[(inIdx + 0) * 2];
+ int16_t x1_l = extendedInput[(inIdx + 1) * 2];
+ int16_t x2_l = extendedInput[(inIdx + 2) * 2];
+
+ int16_t xm1_r = extendedInput[(inIdx - 1) * 2 + 1];
+ int16_t x0_r = extendedInput[(inIdx + 0) * 2 + 1];
+ int16_t x1_r = extendedInput[(inIdx + 1) * 2 + 1];
+ int16_t x2_r = extendedInput[(inIdx + 2) * 2 + 1];
+
+ while (cursor < 1.0f) {
+ float t = cursor;
+ output[outputIndex * 2] = clip(catmullRom(t, xm1_l, x0_l, x1_l, x2_l));
+ output[outputIndex * 2 + 1] = clip(catmullRom(t, xm1_r, x0_r, x1_r, x2_r));
+ ++outputIndex;
+ cursor += ratio;
+ }
+ cursor -= 1.0f;
+ }
+
+ // Historie sichern
+ for (int i = 0; i < 3; ++i) {
+ size_t idx = inputSamples - 3 + i;
+ m_inputHistory[i * 2] = input[idx * 2];
+ m_inputHistory[i * 2 + 1] = input[idx * 2 + 1];
+ }
+
+ m_resampleCursor = cursor;
+ return outputIndex;
+ }
+};
diff --git a/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/README.md b/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/README.md
new file mode 100644
index 0000000..0d461f8
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/README.md
@@ -0,0 +1,26 @@
+
+**I2S Bluetooth Transmitter**
+
+Such boards can be connected directly to the I2S output:
+But to do this, `#define SR_48K` must be activated in Audio.h so that the I2S frequency is always 48KHz.
+
+
+
+If you still have an old ESP32 in your box, you can use it to simulate this board. PSRAM is not required.
+
+The BT transmitter is the slave and is connected in this way, the DAC serves as an analogue output, but is not necessary.
+
+
+
+
+The ESP32-A2DP library is used by P. Schatzmann, https://github.com/pschatzmann/ESP32-A2DP.git
+
+As the I2S output of the audioI2S library not always outputs 44.1KHz, it is scaled internally to 44.1KHz for compatibility. 8000Hz, 22050Hz, 44100Hz and 48000Hz are possible.
+This is necessary because the ESP32 BT library expects this sample rate. This means that old BT devices can also be used.
+It doesn't matter what sample rate the audio source has.
+
+
+
+Test circuit: the audioI2S library is running on the left, the BT transmitter on the right
+
+
diff --git a/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/main.cpp b/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/main.cpp
new file mode 100644
index 0000000..0c0f807
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2S Bluetooth Transmitter/main.cpp
@@ -0,0 +1,168 @@
+#include "Arduino.h"
+/*
+ I2S Bluetooth Transmitter
+ Can be connected to an I2S Master.Sampling rate must be 48KHz
+ Include PSchatzmann /ESP32-A2DP https://github.com/pschatzmann/ESP32-A2DP.git
+*/
+
+#include "Arduino.h"
+#include "esp_bt.h"
+#include "BluetoothA2DPCommon.h"
+#include "BluetoothA2DPSource.h"
+#include
+#include "AudioResampler.hpp"
+
+#define RX_I2S_DIN 25 // connect with I2S Master (signal dout)
+#define RX_I2S_BCLK 27 // connect with I2S Master (bit clock)
+#define RX_I2S_LRC 26 // connect with I2S Master (word select)
+
+#define MEASUREMENT_INTERVAL_MS 100 // Messintervall in ms
+#define TOLERANCE_PERCENT 5 // Toleranz für Schwankungen in %
+static volatile uint32_t lrck_count = 0;
+static uint32_t current_samplerate = 0;
+
+BluetoothA2DPSource a2dp_source;
+AudioResampleBuffer resampler;
+
+char BT_SINK_NAME[] = " Pebble V3\r\n"; // set your sink devicename here
+//char BT_SINK_NAME[] = "Manhattan-165327";
+
+i2s_chan_handle_t i2s_rx_handle = {};
+i2s_chan_config_t i2s_chan_cfg = {}; // stores I2S channel values
+i2s_std_config_t i2s_std_cfg = {}; // stores I2S driver values
+
+const i2s_port_t i2s_num = I2S_NUM_0;
+
+//--------------------------------Recognise Host Samplerate-------------------------------------------------------------------------------------
+// Interrupt-Handler for LRCK-Pulse
+void IRAM_ATTR lrck_interrupt_handler(void *arg) {
+ lrck_count+=1;
+}
+
+// Initialize GPIO interrupt for LRCK
+void init_lrck_monitor() {
+ gpio_config_t io_conf = {
+ .pin_bit_mask = (1ULL << RX_I2S_LRC),
+ .mode = GPIO_MODE_INPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_POSEDGE // Interrupt bei steigender Flanke
+ };
+ gpio_config(&io_conf);
+ gpio_install_isr_service(0);
+ gpio_isr_handler_add((gpio_num_t)RX_I2S_LRC, lrck_interrupt_handler, NULL);
+}
+
+// calculate samplerates based on LRCK counter
+uint32_t measure_samplerate() {
+ lrck_count = 0;
+ vTaskDelay(MEASUREMENT_INTERVAL_MS / portTICK_PERIOD_MS);
+ uint32_t samplerate = (lrck_count * 1000) / MEASUREMENT_INTERVAL_MS; // Hz
+ return samplerate;
+}
+
+// check whether samplerates are one of the expected values
+uint32_t map_to_valid_samplerate(uint32_t measured) {
+ const uint32_t valid_rates[] = {8000, 22050, 44100, 48000};
+ const uint32_t num_rates = sizeof(valid_rates) / sizeof(valid_rates[0]);
+ uint32_t closest_rate = valid_rates[0];
+ int min_diff = abs((int)measured - (int)valid_rates[0]);
+
+ for (int i = 1; i < num_rates; i++) {
+ int diff = abs((int)measured - (int)valid_rates[i]);
+ if (diff < min_diff) {
+ min_diff = diff;
+ closest_rate = valid_rates[i];
+ }
+ }
+
+ // check tolerance
+ if (min_diff <= (closest_rate * TOLERANCE_PERCENT / 100)) {
+ return closest_rate;
+ }
+ return 0; // Invalid samplerates
+}
+
+// task to monitor the samplerates
+void samplerate_monitor_task(void *pvParameters) {
+ init_lrck_monitor();
+
+ while (1) {
+ uint32_t new_samplerate = map_to_valid_samplerate(measure_samplerate());
+
+ if (new_samplerate != 0 && new_samplerate != current_samplerate) {
+ log_w("Samplerate changed to %u Hz", new_samplerate);
+ resampler.setInputSamplerate(new_samplerate);
+ // response to samplerates change
+ if (new_samplerate == 48000) {
+ log_w("Activating resampling to 44100 Hz");
+ // activate resampling logic here
+ } else if (new_samplerate == 44100) {
+ log_w("No resampling needed");
+ // deactivate Resampling, direct transmission
+ } else {
+ log_w("Unsupported samplerate: %u Hz", new_samplerate);
+ // treat 8000 or 22050 Hz, if necessary
+ }
+ current_samplerate = new_samplerate;
+ }
+
+ vTaskDelay(500 / portTICK_PERIOD_MS); // Prüfe alle 500 ms
+ }
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+void i2s_install(){
+
+ i2s_chan_cfg.id = (i2s_port_t)i2s_num; // I2S_NUM_AUTO, I2S_NUM_0, I2S_NUM_1
+ i2s_chan_cfg.role = I2S_ROLE_SLAVE; // I2S controller slave role, bclk and lrc signal will be set to input
+ i2s_chan_cfg.dma_desc_num = 8; // number of DMA buffer
+ i2s_chan_cfg.dma_frame_num = 512; // I2S frame number in one DMA buffer.
+ i2s_chan_cfg.auto_clear = true; // i2s will always send zero automatically if no data to send
+ i2s_new_channel(&i2s_chan_cfg, NULL, &i2s_rx_handle);
+
+ i2s_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); // Set to enable bit shift in Philips mode
+ i2s_std_cfg.gpio_cfg.bclk = (gpio_num_t)RX_I2S_BCLK; // BCLK Assignment
+ i2s_std_cfg.gpio_cfg.din = (gpio_num_t)RX_I2S_DIN; // DIN Assignment
+ i2s_std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; //
+ i2s_std_cfg.gpio_cfg.mclk = I2S_GPIO_UNUSED; //
+ i2s_std_cfg.gpio_cfg.ws = (gpio_num_t)RX_I2S_LRC; // LRC Assignment
+ i2s_std_cfg.gpio_cfg.invert_flags.mclk_inv = false;
+ i2s_std_cfg.gpio_cfg.invert_flags.bclk_inv = false;
+ i2s_std_cfg.gpio_cfg.invert_flags.ws_inv = false;
+ i2s_std_cfg.clk_cfg.sample_rate_hz = 44800;
+ i2s_std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_DEFAULT; // Select PLL_F160M as the default source clock
+ i2s_std_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_128; //
+ i2s_channel_init_std_mode(i2s_rx_handle, &i2s_std_cfg);
+ i2s_channel_enable(i2s_rx_handle);
+}
+
+//---------------------------------------------CallBacks--------------------------------------------------------------------
+
+int32_t get_data(uint8_t *data, int32_t bytes) {
+ return resampler.getData(data, bytes); // Holt exakt die benötigten Daten
+}
+
+// gets called when button on bluetooth speaker is pressed
+void button_handler(uint8_t id, bool isReleased){
+ if (isReleased) {
+ Serial.print("button id ");
+ Serial.print(id);
+ Serial.println(" released");
+ }
+}
+//---------------------------------------------SETUP--------------------------------------------------------------------
+void setup(){
+ Serial.begin(115200);
+ i2s_install();
+ a2dp_source.set_data_callback(get_data);
+ a2dp_source.set_avrc_passthru_command_callback(button_handler);
+ a2dp_source.start(BT_SINK_NAME);
+ resampler.setChannelHandle(i2s_rx_handle);
+ xTaskCreate(samplerate_monitor_task, "samplerate_monitor", 2048, NULL, 5, NULL);
+}
+//----------------------------------------------LOOP--------------------------------------------------------------------
+void loop() {
+ vTaskDelay(1);
+ resampler.loopResample();
+}
\ No newline at end of file
diff --git a/libraries/ESP32-audioI2S/examples/I2Saudio_GoogleTTS/I2Saudio_GoogleTTS.cpp b/libraries/ESP32-audioI2S/examples/I2Saudio_GoogleTTS/I2Saudio_GoogleTTS.cpp
new file mode 100644
index 0000000..1132856
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2Saudio_GoogleTTS/I2Saudio_GoogleTTS.cpp
@@ -0,0 +1,31 @@
+#include "Arduino.h"
+#include "Audio.h"
+#include "WiFi.h"
+
+#define I2S_DOUT 9
+#define I2S_BCLK 3
+#define I2S_LRC 1
+
+Audio audio;
+
+String ssid = "*****";
+String password = "*****";
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ WiFi.begin(ssid.c_str(), password.c_str());
+ while (WiFi.status() != WL_CONNECTED) delay(1500);
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(12); // default 0...21
+ audio.connecttospeech("Wenn die Hunde schlafen, kann der Wolf gut Schafe stehlen.", "de"); // Google TTS
+}
+
+void loop() {
+ audio.loop();
+ vTaskDelay(1);
+}
diff --git a/libraries/ESP32-audioI2S/examples/I2Saudio_OpenAI/I2Saudio_OpenAI.cpp b/libraries/ESP32-audioI2S/examples/I2Saudio_OpenAI/I2Saudio_OpenAI.cpp
new file mode 100644
index 0000000..2f9c7a6
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2Saudio_OpenAI/I2Saudio_OpenAI.cpp
@@ -0,0 +1,30 @@
+#include "Arduino.h"
+#include "Audio.h"
+#include "WiFi.h"
+
+#define I2S_DOUT 9
+#define I2S_BCLK 3
+#define I2S_LRC 1
+
+Audio audio;
+
+String ssid = "*****";
+String password = "*****";
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ WiFi.begin(ssid.c_str(), password.c_str());
+ while (WiFi.status() != WL_CONNECTED) delay(1500);
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(12); // default 0...21
+ audio.openai_speech("openAI-key", "tts-1", "Today is a wonderful day to build something people love!", "", "shimer", "mp3", "1");}
+
+void loop() {
+ audio.loop();
+ vTaskDelay(1);
+}
diff --git a/libraries/ESP32-audioI2S/examples/I2Saudio_SD/I2Saudio_SD.cpp b/libraries/ESP32-audioI2S/examples/I2Saudio_SD/I2Saudio_SD.cpp
new file mode 100644
index 0000000..6215a43
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2Saudio_SD/I2Saudio_SD.cpp
@@ -0,0 +1,53 @@
+//**********************************************************************************************************
+//* audioI2S-- I2S audiodecoder for ESP32, *
+//**********************************************************************************************************
+//
+// first release on 11/2018
+// Version 4 , Aug.22/2025
+//
+//
+// THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT.
+// FOR PERSONAL USE IT IS SUPPLIED 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 AUTHOR
+// OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+//
+
+#include "Arduino.h"
+#include "Audio.h"
+#include "SPI.h"
+#include "SD.h"
+#include "FS.h"
+
+// Digital I/O used
+#define SD_CS 5
+#define SPI_MOSI 23
+#define SPI_MISO 19
+#define SPI_SCK 18
+#define I2S_DOUT 25
+#define I2S_BCLK 27
+#define I2S_LRC 26
+
+Audio audio;
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ pinMode(SD_CS, OUTPUT);
+ digitalWrite(SD_CS, HIGH);
+ SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+ SPI.setFrequency(1000000);
+ Serial.begin(115200);
+ SD.begin(SD_CS);
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(12); // 0...21
+ audio.connecttoFS(SD, "test.wav");
+}
+
+void loop(){
+ vTaskDelay(1);
+ audio.loop();
+}
diff --git a/libraries/ESP32-audioI2S/examples/I2Saudio_SD_MMC/I2Saudio_SD_MMC.cpp b/libraries/ESP32-audioI2S/examples/I2Saudio_SD_MMC/I2Saudio_SD_MMC.cpp
new file mode 100644
index 0000000..b1b2d3b
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/I2Saudio_SD_MMC/I2Saudio_SD_MMC.cpp
@@ -0,0 +1,38 @@
+#include "Arduino.h"
+#include "Audio.h"
+#include "SD_MMC.h"
+
+#define I2S_DOUT 9
+#define I2S_BCLK 3
+#define I2S_LRC 1
+#define SD_MMC_D0 11
+#define SD_MMC_CLK 13
+#define SD_MMC_CMD 14
+
+Audio audio;
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ pinMode(SD_MMC_D0, INPUT_PULLUP);
+ SD_MMC.setPins(SD_MMC_CLK, SD_MMC_CMD, SD_MMC_D0);
+ SD_MMC.begin("/sdcard", true);
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(12); // default 0...21
+ audio.connecttoFS(SD_MMC, "/test.wav");
+}
+
+void loop() {
+ audio.loop();
+ vTaskDelay(1);
+}
+
+// optional
+void audio_info(const char *info){
+ Serial.print("info "); Serial.println(info);
+}
\ No newline at end of file
diff --git a/libraries/ESP32-audioI2S/examples/M5Core2/M5Core2.ino b/libraries/ESP32-audioI2S/examples/M5Core2/M5Core2.ino
new file mode 100644
index 0000000..d69b564
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/M5Core2/M5Core2.ino
@@ -0,0 +1,77 @@
+//**********************************************************************************************************
+//* audioI2S-- I2S audiodecoder for M5Stack Core2 *
+//**********************************************************************************************************
+//
+// first release on May.12/2021
+//
+//
+// THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT.
+// FOR PERSONAL USE IT IS SUPPLIED 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 AUTHOR
+// OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+//
+
+#include
+#include "Audio.h"
+
+// Digital I/O used
+#define SD_CS 4
+#define SD_MOSI 23
+#define SD_MISO 38
+#define SD_SCK 18
+#define I2S_DOUT 2
+#define I2S_BCLK 12
+#define I2S_LRC 0
+
+Audio audio;
+String ssid = "xxxxxx";
+String password = "xxxxxx";
+
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ M5.begin(true, true, true, true);
+ M5.Axp.SetSpkEnable(true);
+ M5.Lcd.fillScreen(BLACK);
+ M5.Lcd.setTextColor(WHITE);
+ M5.Lcd.setTextSize(2);
+
+ pinMode(SD_CS, OUTPUT);
+ digitalWrite(SD_CS, HIGH);
+ SPI.begin(SD_SCK, SD_MISO, SD_MOSI);
+ SPI.setFrequency(1000000);
+ SD.begin(SD_CS);
+
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(15); // 0...21
+
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid.c_str(), password.c_str());
+ while (!WiFi.isConnected()) {
+ delay(10);
+ }
+ ESP_LOGI(TAG, "Connected");
+ ESP_LOGI(TAG, "Starting MP3...\n");
+
+// audio.connecttoFS(SD, "/320k_test.mp3");
+// audio.connecttoFS(SD, "test.wav");
+ audio.connecttohost("http://air.ofr.fm:8008/jazz/mp3/128");
+// audio.connecttospeech("Миска вареників з картоплею та шкварками, змащених салом!", "uk-UA");
+}
+
+void loop() {
+ vTaskDelay(1);
+ audio.loop();
+ if(Serial.available()){ // put streamURL in serial monitor
+ audio.stopSong();
+ String r=Serial.readString();
+ r.trim();
+ if(r.length()>5) audio.connecttohost(r.c_str());
+ log_i("free heap=%i", ESP.getFreeHeap());
+ }
+}
diff --git a/libraries/ESP32-audioI2S/examples/M5StackNode/M5StackNode.ino b/libraries/ESP32-audioI2S/examples/M5StackNode/M5StackNode.ino
new file mode 100644
index 0000000..3d9ec8d
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/M5StackNode/M5StackNode.ino
@@ -0,0 +1,53 @@
+
+// M5Stack Node support
+// thanks to Cellie - issue #35 25.Apr.2020
+// M5Stack board with Node base also need a MCLK signal on GPIO0.
+
+#include /* https://github.com/CelliesProjects/wm8978-esp32 */
+#include /* https://github.com/schreibfaul1/ESP32-audioI2S */
+
+/* M5Stack Node WM8978 I2C pins */
+#define I2C_SDA 21
+#define I2C_SCL 22
+
+/* M5Stack Node I2S pins */
+#define I2S_BCK 5
+#define I2S_WS 13
+#define I2S_DOUT 2
+#define I2S_DIN 34
+
+/* M5Stack WM8978 MCLK gpio number */
+#define I2S_MCLKPIN 0
+
+WM8978 dac;
+Audio audio;
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ /* Setup wm8978 I2C interface */
+ if (!dac.begin(I2C_SDA, I2C_SCL)) {
+ log_e("Error setting up dac. System halted");
+ while (1) delay(100);
+ }
+
+ dac.setSPKvol(40); /* max 63 */
+ dac.setHPvol(32, 32);
+
+ /* Setup wm8978 I2S interface */
+ audio.setPinout(I2S_BCK, I2S_WS, I2S_DOUT, I2S_MCLKPIN);
+
+ WiFi.begin("xxx", "xxx");
+ while (!WiFi.isConnected()) { delay(10); }
+
+ log_i("Connected\nStarting MP3...\n");
+ audio.connecttohost("http://icecast.omroep.nl/3fm-bb-mp3");
+}
+
+void loop() {
+ vTaskDelay(1);
+ audio.loop();
+}
diff --git a/libraries/ESP32-audioI2S/examples/M5StickCPlus/M5StickCPlus.ino b/libraries/ESP32-audioI2S/examples/M5StickCPlus/M5StickCPlus.ino
new file mode 100644
index 0000000..4f98f84
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/M5StickCPlus/M5StickCPlus.ino
@@ -0,0 +1,59 @@
+//**********************************************************************************************************
+//* audioI2S-- I2S audiodecoder for M5StickC Plus and SPK HAT *
+//**********************************************************************************************************
+//
+// first release on May.12/2021
+//
+//
+// THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT.
+// FOR PERSONAL USE IT IS SUPPLIED 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 AUTHOR
+// OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
+//
+
+#include
+#include "Audio.h"
+
+Audio audio = Audio(true);
+
+String ssid = "xxxxxxxx";
+String password = "xxxxxxxx";
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ M5.begin(false); // Lcd disabled to reduce noise
+ M5.Axp.ScreenBreath(1); // Lower Lcd backlight
+ pinMode(36, INPUT);
+ gpio_pulldown_dis(GPIO_NUM_25);
+ gpio_pullup_dis(GPIO_NUM_25);
+ M5.Beep.tone(44100); // Built-in buzzer tone
+ M5.Beep.end(); // disabled
+
+ audio.setVolume(15); // 0...21
+
+ WiFi.mode(WIFI_STA);
+ WiFi.begin(ssid.c_str(), password.c_str());
+ while (!WiFi.isConnected()) { delay(10); }
+ ESP_LOGI(TAG, "Connected");
+ ESP_LOGI(TAG, "Starting MP3...\n");
+
+ audio.connecttohost("http://air.ofr.fm:8008/jazz/mp3/128");
+ // audio.connecttospeech("Миска вареників з картоплею та шкварками, змащених салом!", "uk-UA");
+}
+
+void loop() {
+ vTaskDelay(1);
+ audio.loop();
+ if(Serial.available()){ // put streamURL in serial monitor
+ audio.stopSong();
+ String r=Serial.readString();
+ r.trim();
+ if(r.length()>5) audio.connecttohost(r.c_str());
+ log_i("free heap=%i", ESP.getFreeHeap());
+ }
+}
diff --git a/libraries/ESP32-audioI2S/examples/biquad/biquad.py b/libraries/ESP32-audioI2S/examples/biquad/biquad.py
new file mode 100644
index 0000000..7564916
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/biquad/biquad.py
@@ -0,0 +1,44 @@
+import numpy as np
+# import matplotlib
+# matplotlib.use("TkAgg") # oder "QtAgg", falls Qt installiert ist
+import matplotlib.pyplot as plt
+from scipy.signal import freqz
+
+# =========================
+# PARAMETER
+# =========================
+fs = 44100 # Samplerate
+N = 8192 # FFT-Auflösung
+
+# Liste von Biquads (Reihenfolge = Signalfluss)
+biquads = [
+([1.026721, -1.919612, 0.901093], [1.0, -1.922258, 0.925168]), # LOWSHELF
+([0.802626, -1.424665, 0.670208 ], [1.0, -1.424665, 0.472834 ]), # PEAKINGEQ
+([2.092489, -2.262849, 0.808592], [1.0, -0.597000, 0.235232]), # HIGHSHELF
+]
+
+w = np.linspace(0, np.pi, N)
+H = np.ones_like(w, dtype=complex)
+
+for b, a in biquads:
+ _, h = freqz(b, a, worN=w)
+ H *= h
+
+f = w * fs / (2*np.pi)
+mag_db = 20 * np.log10(np.abs(H) + 1e-12)
+
+# =========================
+# PLOT
+# =========================
+plt.figure(figsize=(9,5))
+plt.semilogx(f, mag_db)
+plt.xlim(20, fs/2)
+plt.ylim(-15, 15)
+plt.grid(True, which='both')
+plt.xlabel("Frequency (Hz)")
+plt.ylabel("Amplitude (dB)")
+plt.title("Biquad frequency response")
+plt.tight_layout()
+# plt.show()
+plt.savefig("biquad_response.png", dpi=150)
+print("Plot gespeichert als biquad_response.png")
\ No newline at end of file
diff --git a/libraries/ESP32-audioI2S/examples/biquad/biquad_response.png b/libraries/ESP32-audioI2S/examples/biquad/biquad_response.png
new file mode 100644
index 0000000..2dd5458
Binary files /dev/null and b/libraries/ESP32-audioI2S/examples/biquad/biquad_response.png differ
diff --git a/libraries/ESP32-audioI2S/examples/plays all files in a directory/plays_all_files_in_a_directory.ino b/libraries/ESP32-audioI2S/examples/plays all files in a directory/plays_all_files_in_a_directory.ino
new file mode 100644
index 0000000..0f4a8fd
--- /dev/null
+++ b/libraries/ESP32-audioI2S/examples/plays all files in a directory/plays_all_files_in_a_directory.ino
@@ -0,0 +1,98 @@
+#include "Arduino.h"
+#include "Audio.h"
+#include "SD_MMC.h"
+#include "FS.h"
+#include
+
+#define I2S_LRC 26
+#define I2S_DOUT 25
+#define I2S_BCLK 27
+
+#define SD_MMC_D0 2
+#define SD_MMC_CLK 14
+#define SD_MMC_CMD 15
+
+void listDir(fs::FS &fs, const char * dirname, uint8_t levels); //proto
+std::vector v_audioContent;
+int pirPin = 4;
+Audio audio;
+
+File dir;
+const char audioDir[] = "/mp3";
+
+void my_audio_info(Audio::msg_t m) {
+ Serial.printf("%s: %s\n", m.s, m.msg);
+}
+
+void setup() {
+ Audio::audio_info_callback = my_audio_info;
+ Serial.begin(115200);
+ pinMode(SD_MMC_D0, INPUT_PULLUP);
+ SD_MMC.setPins(SD_MMC_CLK,SD_MMC_CMD, SD_MMC_D0);
+ if(!SD_MMC.begin( "/sdmmc", true, false, 20000)){
+ Serial.println("Card Mount Failed");
+ return;
+ }
+ audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
+ audio.setVolume(17); // 0...21 Will need to add a volume setting in the app
+ dir = SD_MMC.open(audioDir);
+ listDir(SD_MMC, audioDir, 1);
+ if(v_audioContent.size() > 0){
+ const char* s = (const char*)v_audioContent[v_audioContent.size() -1];
+ Serial.printf("playing %s\n", s);
+ audio.connecttoFS(SD_MMC, s);
+ v_audioContent.pop_back();
+ }
+}
+
+void loop(){
+ audio.loop();
+ vTaskDelay(1); // Audio is distoreted without this
+}
+
+void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
+ Serial.printf("Listing directory: %s\n", dirname);
+
+ File root = fs.open(dirname);
+ if(!root){
+ Serial.println("Failed to open directory");
+ return;
+ }
+ if(!root.isDirectory()){
+ Serial.println("Not a directory");
+ return;
+ }
+
+ File file = root.openNextFile();
+ while(file){
+ if(file.isDirectory()){
+ Serial.print(" DIR : ");
+ Serial.println(file.name());
+ if(levels){
+ listDir(fs, file.path(), levels -1);
+ }
+ } else {
+ Serial.print(" FILE: ");
+ Serial.print(file.name());
+ Serial.print(" SIZE: ");
+ Serial.println(file.size());
+ v_audioContent.insert(v_audioContent.begin(), strdup(file.path()));
+ }
+ file = root.openNextFile();
+ }
+ Serial.printf("num files %i", v_audioContent.size());
+ root.close();
+ file.close();
+}
+
+void vector_clear_and_shrink(vector&vec){
+ uint size = vec.size();
+ for (int i = 0; i < size; i++) {
+ if(vec[i]){
+ free(vec[i]);
+ vec[i] = NULL;
+ }
+ }
+ vec.clear();
+ vec.shrink_to_fit();
+}
diff --git a/libraries/ESP32-audioI2S/library.json b/libraries/ESP32-audioI2S/library.json
new file mode 100644
index 0000000..aaa9fc6
--- /dev/null
+++ b/libraries/ESP32-audioI2S/library.json
@@ -0,0 +1,23 @@
+{
+ "name": "ESP32-audioI2S",
+ "version": "3.4.6",
+ "description": "With this library You can easily build a WebRadio with a ESP32 board and a I2S-module",
+ "keywords": "audio, i2s, esp32, esp32-s3, esp32-p4",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/schreibfaul1/ESP32-audioI2S.git"
+ },
+ "authors": [
+ {
+ "name": "schreibfaul1"
+ }
+ ],
+ "license": "GPL-3.0",
+ "homepage": "https://github.com/schreibfaul1/ESP32-audioI2S",
+ "dependencies": {},
+ "frameworks": [
+ "arduino",
+ "espidf"
+ ],
+ "platforms": "espressif32"
+}
diff --git a/libraries/ESP32-audioI2S/library.properties b/libraries/ESP32-audioI2S/library.properties
new file mode 100644
index 0000000..307fe9c
--- /dev/null
+++ b/libraries/ESP32-audioI2S/library.properties
@@ -0,0 +1,9 @@
+name=ESP32-audioI2S-master
+version=3.4.6
+author=schreibfaul1
+maintainer=schreibfaul1
+sentence=With this library You can easily build a audio app with a ESP32, ESP32-S3, ESP32-P4 board with PSRAM and a external DAC or I2S-BT-module.
+paragraph=Data format can be only mp3, aac, flac, opus, vorbis or m4a.
+category=Device Control
+url=https://github.com/schreibfaul1/ESP32-audioI2S
+architectures=esp32
diff --git a/libraries/ESP32-audioI2S/src/Audio.cpp b/libraries/ESP32-audioI2S/src/Audio.cpp
new file mode 100644
index 0000000..36ebc42
--- /dev/null
+++ b/libraries/ESP32-audioI2S/src/Audio.cpp
@@ -0,0 +1,7821 @@
+
+/*****************************************************************************************************************************************************
+ audio.cpp
+
+ Created on: 28.10.2018 */
+char audioI2SVers[] = "\
+ Version 3.4.6z ";
+/* Updated on: Jun 26, 2026
+
+ Author: Wolle (schreibfaul1)
+ Audio library for ESP32, ESP32-S3 or ESP32-P4
+ Arduino Vers. V3 is mandatory
+ PSRAM is mandatory
+ external DAC is mandatory
+
+*****************************************************************************************************************************************************/
+
+#include "Audio.h"
+#include "aac_decoder/aac_decoder.h"
+#include "flac_decoder/flac_decoder.h"
+#include "mp3_decoder/mp3_decoder.h"
+#include "opus_decoder/opus_decoder.h"
+#include "psram_unique_ptr.hpp"
+#include "vorbis_decoder/vorbis_decoder.h"
+#include "wav_decoder/wav_decoder.h"
+
+// constants
+constexpr size_t m_frameSizeWav = 4096;
+constexpr size_t m_frameSizeMP3 = 18000; // more than one icy-metaint
+constexpr size_t m_frameSizeAAC = 18000; // more than one icy-metaint
+constexpr size_t m_frameSizeFLAC = UINT16_MAX; // max ogg size
+constexpr size_t m_frameSizeOPUS = UINT16_MAX; // max ogg size
+constexpr size_t m_frameSizeVORBIS = UINT16_MAX; // OGG length is normally 4080 bytes, but can be reach 64KB in the metadata block
+constexpr size_t m_outbuffSize = 4608 * 2;
+constexpr size_t m_resamplesBuffSize = m_outbuffSize * 8; // SRmin: 6KHz -> SRmax: 48K
+
+constexpr size_t AUDIO_STACK_SIZE = 3500;
+
+// static allocations for Audio task
+StaticTask_t __attribute__((unused)) xAudioTaskBuffer;
+StackType_t __attribute__((unused)) xAudioStack[AUDIO_STACK_SIZE];
+
+// weak default implementation - can be overridden by user
+__attribute__((weak)) void audio_process_i2s(int32_t* outBuff, int16_t validSamples, bool* continueI2S) {
+ // Default: do nothing. User can provide their own implementation to process audio data.
+}
+__attribute__((weak)) void audio_process_raw_samples(int32_t* outBuff, int16_t validSamples) {
+ // Default: do nothing. User can provide their own implementation to process audio data.
+}
+
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+// 📌📌📌 A U D I O B U F F E R 📌📌📌
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+
+// AudioBuffer will be allocated in PSRAM
+//
+// m_startPtr m_readPtr m_writePtr m_endPtr
+// | |<------maxAvailableBytes----->|<--------------------- writeSpace ----------------------->|
+// ▼ ▼ ▼ ▼
+// ---------------------------------------------------------------------------------------------------------------
+// | <--m_mainBuffSize--> | <--m_resBuffSize --> |
+// ---------------------------------------------------------------------------------------------------------------
+// |<---freeSpace---->|<------------filled---------->|<-------freeSpace-------->|
+// ▲
+// |
+// m_buffEnd
+// if resBuff is full copy data from resBuff to the beginning
+// if m_readPtr >= m_buff|<---------------maxAvailableBytes-------------->|
+// ▼ ▼ ▼ ▼ ▼
+// ---------------------------------------------------------------------------------------------------------------
+// | <--m_mainBuffSize--> | <--m_resBuffSize --> |
+// ---------------------------------------------------------------------------------------------------------------
+// |<------------filled------------->|<-------freeSpace------->|<----filled---->▲
+// |
+// m_buffEnd
+//
+
+AudioBuffer::AudioBuffer() {
+ m_mainBuffSize = UINT16_MAX * 10;
+ m_resBuffSize = UINT16_MAX;
+}
+
+AudioBuffer::~AudioBuffer() {
+ ;
+}
+
+size_t AudioBuffer::getBufsize() {
+ return m_mainBuffSize;
+}
+
+size_t AudioBuffer::init() {
+ m_buffer.alloc(m_mainBuffSize + m_resBuffSize, "AudioBuffer");
+ m_log.set_name("\nAudiobuffer_Log");
+ m_mutex = xSemaphoreCreateBinary();
+ xSemaphoreGive(m_mutex);
+ m_init = true;
+ m_startPtr = m_buffer.get();
+ m_endPtr = m_buffer.get() + m_mainBuffSize;
+ m_buffEnd = m_endPtr + m_resBuffSize;
+ reset();
+ return m_mainBuffSize;
+}
+
+void AudioBuffer::setMaxBlocksize(uint32_t mbs) {
+ m_maxBlockSize = mbs;
+}
+
+size_t AudioBuffer::getMaxBlockSize() {
+ return m_maxBlockSize;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+size_t AudioBuffer::freeSpace() {
+ if (!m_init) return 0;
+ if (m_readPtr == m_writePtr) {
+ if (m_isEmpty) { return m_mainBuffSize; }
+ if (m_isFull) { return 0; }
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writePtr == readPtr, writePtr {}, readPtr {}", __FILE__, __LINE__, m_writePtr - m_startPtr, m_readPtr - m_startPtr);
+ m_log.println();
+ }
+ if (m_readPtr < m_writePtr) {
+ if (m_writePtr > m_endPtr) {
+ return (m_readPtr - m_startPtr);
+ } else {
+ return (m_endPtr - m_writePtr) + (m_readPtr - m_startPtr);
+ }
+ }
+ return m_readPtr - m_writePtr;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+size_t AudioBuffer::bufferFilled() {
+ if (!m_init) return 0;
+ xSemaphoreTake(m_mutex, portMAX_DELAY);
+ size_t bufferFilled = 0;
+ if (m_readPtr == m_writePtr) {
+ if (m_isEmpty) {
+ bufferFilled = 0;
+ goto end;
+ }
+ if (m_isFull) {
+ bufferFilled = m_mainBuffSize;
+ goto end;
+ }
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writePtr == readPtr, writePtr {}, readPtr {}", __FILE__, __LINE__, m_writePtr - m_startPtr, m_readPtr - m_startPtr);
+ m_log.println();
+ }
+ if (m_readPtr < m_writePtr) {
+ bufferFilled = (size_t)(m_writePtr - m_readPtr);
+ goto end;
+ }
+ bufferFilled = (m_endPtr - m_readPtr) + (m_writePtr - m_startPtr);
+end:
+ xSemaphoreGive(m_mutex);
+ return bufferFilled;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+size_t AudioBuffer::writeSpace() {
+ if (!m_init) return 0;
+ xSemaphoreTake(m_mutex, portMAX_DELAY);
+ m_writeSpace = 0;
+
+ // Check whether a complete block still fits in at the end
+ size_t spaceToEnd = m_buffEnd - m_writePtr;
+
+ if (m_isFull) {
+ m_writeSpace = 0;
+ goto end;
+ }
+
+ if (m_isEmpty) {
+ m_writeSpace = min(m_maxRet, spaceToEnd);
+ goto end;
+ }
+
+ if (spaceToEnd == 0) { // be sure that the resBuff is full
+ // Only copy if the read pointer is not in the way
+ if (m_readPtr > m_startPtr + m_resBuffSize) {
+ memcpy(m_startPtr, m_endPtr, m_resBuffSize);
+ m_writePtr = m_startPtr + m_resBuffSize;
+ }
+ }
+
+ if (m_writePtr < m_readPtr) {
+ if (m_readPtr >= m_endPtr) { // readPtr is in resBuff?
+ m_writeSpace = min(m_maxRet, (size_t)(m_endPtr - m_writePtr)); // writePtr does not enter resbuff, wait for copy im readspace
+ } else {
+ m_writeSpace = min(m_maxRet, (size_t)(m_readPtr - m_writePtr));
+ }
+ goto end;
+ }
+ if (m_writePtr > m_readPtr) {
+ m_writeSpace = min(m_maxRet, spaceToEnd);
+ goto end;
+ }
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writePtr == readPtr, writePtr {}, readPtr {}", __FILE__, __LINE__, m_writePtr - m_startPtr, m_readPtr - m_startPtr);
+ m_log.println();
+
+end:
+ xSemaphoreGive(m_mutex);
+ return m_writeSpace;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+void AudioBuffer::bytesWritten(size_t bw) {
+ if (!m_init) return;
+ xSemaphoreTake(m_mutex, portMAX_DELAY);
+ if (!bw) goto end;
+
+ if (bw > m_writeSpace) {
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writeSpace < bw, writeSpace {}, bw {}", __FILE__, __LINE__, m_writeSpace, bw); // bw must not be larger than the queried m_writeSpace
+ m_log.println();
+ }
+ if (m_writePtr < m_readPtr && m_writePtr + bw > m_readPtr) {
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writePtr overrruns readPtr, writePtr {}, readPtr {}, bw {}", __FILE__, __LINE__, m_writePtr - m_startPtr, m_readPtr - m_startPtr, bw);
+ m_log.println();
+ m_writePtr = m_readPtr;
+ goto end;
+ }
+ if (m_writePtr + bw > m_buffEnd) {
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writePtr overrruns buffEnd, writePtr {}, buffEnd {}, bw {}", __FILE__, __LINE__, m_writePtr - m_startPtr, m_buffEnd - m_startPtr, bw);
+ m_log.println();
+ m_writePtr = m_buffEnd;
+ goto end;
+ }
+
+ m_writePtr += bw;
+ if (bw) {
+ if (m_writePtr == m_readPtr) m_isFull = true;
+ m_isEmpty = false;
+ }
+
+end:
+ xSemaphoreGive(m_mutex);
+ return;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+size_t AudioBuffer::readSpace() {
+ if (!m_init) return 0;
+ xSemaphoreTake(m_mutex, portMAX_DELAY);
+
+ if (m_readPtr >= m_endPtr && m_writePtr <= m_endPtr) {
+ size_t len = m_readPtr - m_endPtr;
+ if (m_writePtr > m_startPtr + len) {
+ // set new readptr
+ m_readPtr = m_startPtr + len;
+ }
+ }
+
+ m_readSpace = 0;
+ if (m_isEmpty) { goto end; }
+
+ if (m_isFull) {
+ m_readSpace = min(m_maxRet, (size_t)(m_buffEnd - m_readPtr));
+ goto end;
+ }
+
+ if (m_readPtr < m_writePtr) {
+ m_readSpace = min(m_maxRet, (size_t)(m_writePtr - m_readPtr));
+ goto end;
+ }
+
+ if (m_readPtr > m_writePtr) {
+ m_readSpace = min(m_maxRet, (size_t)(m_buffEnd - m_readPtr));
+ goto end;
+ }
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " writePtr == readPtr, writePtr {}, readPtr {}", __FILE__, __LINE__, m_writePtr - m_startPtr, m_readPtr - m_startPtr);
+ m_log.println();
+end:
+ xSemaphoreGive(m_mutex);
+ return m_readSpace;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+void AudioBuffer::bytesWasRead(size_t br) {
+ if (!m_init) return;
+ xSemaphoreTake(m_mutex, portMAX_DELAY);
+
+ if (!br) goto end;
+
+ if (m_readSpace < br) {
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " readSpace < br, rspc {}, br {}", __FILE__, __LINE__, m_readSpace, br); // br must not be larger than the queried m_readSpace
+ m_log.println();
+ vTaskDelay(100);
+ goto end;
+ }
+
+ if (m_readPtr < m_writePtr && m_readPtr + br > m_writePtr) {
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " readPtr overrruns writePtr, readPtr {}, writePtr {}, br {}", __FILE__, __LINE__, m_readPtr - m_startPtr, m_writePtr - m_startPtr, br);
+ m_log.println();
+ m_readPtr = m_writePtr;
+ vTaskDelay(100);
+ goto end;
+ }
+ if (m_readPtr + br > m_buffEnd) {
+ m_log.assignf("[{}:{}]" ANSI_ESC_RED " readPtr overrruns buffEnd, readPtr {}, buffEnd {}, bw {}", __FILE__, __LINE__, m_readPtr - m_startPtr, m_buffEnd - m_startPtr, br);
+ m_log.println();
+ m_readPtr = m_buffEnd;
+ vTaskDelay(100);
+ goto end;
+ }
+
+ m_readPtr += br;
+
+ if (br) {
+ if (m_readPtr == m_writePtr) {
+ m_isEmpty = true;
+ // m_log.assignf("readPtr {}, writePtr {}, br {}", m_readPtr - m_startPtr, m_writePtr - m_startPtr, br); m_log.println();
+ }
+ m_isFull = false;
+ }
+
+end:
+ xSemaphoreGive(m_mutex);
+ return;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------
+uint8_t* AudioBuffer::getWritePtr() {
+ return m_writePtr;
+}
+
+uint8_t* AudioBuffer::getReadPtr() {
+ return m_readPtr;
+}
+
+void AudioBuffer::reset() {
+ m_writePtr = m_buffer.get();
+ m_readPtr = m_buffer.get();
+ m_isEmpty = true;
+ m_isFull = false;
+ m_readSpace = 0;
+ m_writeSpace = min(m_maxRet, (size_t)(m_buffEnd - m_writePtr));
+}
+
+void AudioBuffer::showStatus() {
+ m_log.assignf("\nfilled {}, free {}\n", bufferFilled(), freeSpace());
+ m_log.appendf("writeSpace {}, readSpace {}\n", writeSpace(), readSpace());
+ m_log.appendf("writePtr {}, readPtr {}\n", m_writePtr - m_startPtr, m_readPtr - m_startPtr);
+ m_log.appendf("isEmpty {}, isFull {}\n\n", m_isEmpty, m_isFull);
+ m_log.print();
+}
+
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+// 📌📌📌 A U D I O 📌📌📌
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+Audio::Audio(uint8_t i2sPort) {
+
+ m_f_I2S_init = false;
+ mutex_playAudioData = xSemaphoreCreateMutex();
+ mutex_audioTask = xSemaphoreCreateMutex();
+ mutex_audioTaskIsDecoding = xSemaphoreCreateMutex();
+
+ clientsecure.setInsecure();
+ m_i2s_items.i2s_num = i2sPort; // i2s port number
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+Audio::~Audio() {
+ stopSong();
+ setDefaults();
+
+ i2s_channel_disable(m_i2s_tx_handle);
+ i2s_del_channel(m_i2s_tx_handle);
+ stopAudioTask();
+ vSemaphoreDelete(mutex_playAudioData);
+ vSemaphoreDelete(mutex_audioTask);
+ vSemaphoreDelete(mutex_audioTaskIsDecoding);
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::destroy_decoder() {
+ if (m_decoder && m_decoder->isValid()) {
+ info(*this, evt_info, "{}Decoder has been destroyed", m_decoder->whoIsIt());
+ m_decoder->reset();
+ m_decoder.reset();
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+std::unique_ptr Audio::createDecoder(const std::string& type) {
+ destroy_decoder();
+ if (type == "MP3") return std::make_unique(*this);
+ if (type == "FLAC") return std::make_unique(*this);
+ if (type == "OPUS") return std::make_unique(*this);
+ if (type == "AAC") return std::make_unique(*this);
+ if (type == "VORBIS") return std::make_unique(*this);
+ if (type == "WAV") return std::make_unique(*this);
+ return nullptr;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::initInBuff() {
+ if (!InBuff.isInitialized()) {
+ size_t size = InBuff.init();
+ if (size > 0) { info(*this, evt_info, "inputBufferSize: {} bytes", size - 1); }
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+esp_err_t Audio::I2Sstart() {
+ zeroI2Sbuff();
+ esp_err_t err = ESP_FAIL;
+ if (!m_f_i2s_channel_enabled) {
+ err = i2s_channel_enable(m_i2s_tx_handle);
+ if (err == ESP_OK) m_f_i2s_channel_enabled = true;
+ }
+ return err;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+esp_err_t Audio::I2Sstop() {
+ m_outBuff.clear(); // Clear OutputBuffer
+ m_resamplesBuff.clear(); // Clear m_resamplesBuff
+ esp_err_t err = ESP_FAIL;
+ if (m_f_i2s_channel_enabled) err = i2s_channel_disable(m_i2s_tx_handle);
+ m_f_i2s_channel_enabled = false;
+ return err;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::zeroI2Sbuff() {
+ uint8_t buff[2] = {0, 0}; // From IDF V5 there is no longer the zero_dma_buff() function.
+ size_t bytes_loaded = 0; // As a replacement, we write a small amount of zeros in the buffer and thus reset the entire buffer.
+ i2s_channel_preload_data(m_i2s_tx_handle, buff, 2, &bytes_loaded);
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setDefaults() {
+ stopSong();
+ initInBuff(); // initialize InputBuffer if not already done
+
+ InBuff.reset();
+ m_streamTitle.reset();
+ m_streamURL.reset();
+ m_playlistBuff.reset();
+ m_m3u8_host.reset();
+
+ m_outBuff.clear(); // Clear OutputBuffer
+ m_resamplesBuff.clear(); // Clear m_resamplesBuff
+ m_syltTimeStamp.clear();
+
+ vector_clear_and_shrink(m_playlistURL);
+ vector_clear_and_shrink(m_playlistContent);
+ vector_clear_and_shrink(m_syltLines);
+
+ client.stop();
+ clientsecure.stop();
+ m_client = static_cast(&client); /* default to *something* so that no NULL deref can happen */
+
+ m_f_timeout = false;
+ m_f_chunked = false; // Assume not chunked
+ m_f_firstmetabyte = false;
+ m_f_playing = false;
+ m_f_tts = false;
+ m_f_firstCall = true; // InitSequence for processWebstream and processLocalFile
+ m_cat.firstCall = true; // InitSequence for calculateAudioTime
+ m_pplM3U8.firstCall = true; // InitSequence for parsePlaylist_M3U8
+ m_f_firstPlayCall = true; // InitSequence for playAudioData
+ m_f_firstLoop = true;
+ m_f_unsync = false; // set within ID3 tag but not used
+ m_f_exthdr = false; // ID3 extended header
+ m_f_rtsp = false; // RTSP (m3u8)stream
+ m_f_m3u8data = false; // set again in processM3U8entries() if necessary
+ m_f_continue = false;
+ m_f_ts = false;
+ m_f_ogg = false;
+ m_f_m4aID3dataAreRead = false;
+ m_f_stream = false;
+ m_f_decode_ready = false;
+ m_f_eof = false;
+ m_f_ID3v1TagFound = false;
+ m_f_lockInBuffer = false;
+ m_f_acceptRanges = false;
+ m_f_connectionClose = false;
+ m_f_allDataReceived = false;
+
+ m_codec = CODEC_NONE;
+ m_dataMode = AUDIO_NONE;
+ m_streamType = ST_NONE;
+ m_playlistFormat = FORMAT_NONE;
+ m_m3u8Codec = CODEC_AAC;
+
+ m_validSamples = 0;
+ m_audioCurrentTime = 0;
+ m_audioFileDuration = 0;
+ m_resumeFilePos = -1;
+ m_audioDataStart = 0;
+ m_audioDataSize = 0;
+ m_audioFileSize = 0;
+ m_avr_bitrate = 0;
+ m_nominal_bitrate = 0;
+ m_bytesNotConsumed = 0; // counts all not decodable bytes
+ m_chunkcount = 0; // for chunked streams
+ m_curSample = 0;
+ m_LFcount = 0; // For end of header detection
+ m_controlCounter = 0; // Status within readID3data() and readWaveHeader()
+ m_channels = 2; // assume stereo #209
+ m_ID3Size = 0;
+ m_haveNewFilePos = 0;
+ m_validSamples = 0;
+ m_M4A_chConfig = 0;
+ m_M4A_objectType = 0;
+ m_M4A_sampleRate = 0;
+ m_lastGranulePosition = 0;
+ m_validSamples = 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setConnectionTimeout(uint16_t timeout_ms, uint16_t timeout_ms_ssl) {
+ if (timeout_ms) m_timeout_ms = timeout_ms;
+ if (timeout_ms_ssl) m_timeout_ms_ssl = timeout_ms_ssl;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+
+/*
+ Text to speech API provides a speech endpoint based on our TTS (text-to-speech) model.
+ More info: https://platform.openai.com/docs/guides/text-to-speech/text-to-speech
+
+ Request body:
+ model (string) [Required] - One of the available TTS models: tts-1 or tts-1-hd
+ input (string) [Required] - The text to generate audio for. The maximum length is 4096 characters.
+ instructions (string) [Optional] - A description of the desired characteristics of the generated audio.
+ voice (string) [Required] - The voice to use when generating the audio. Supported voices are alloy, echo, fable, onyx, nova, and shimmer.
+ response_format (string) [Optional] - Defaults to mp3. The format to audio in. Supported formats are mp3, opus, aac, and flac.
+ speed (number) [Optional] - Defaults to 1. The speed of the generated audio. Select a value from 0.25 to 4.0. 1.0 is the default.
+
+ Usage: audio.openai_speech(OPENAI_API_KEY, "tts-1", input, instructions, "shimmer", "mp3", "1");
+*/
+bool Audio::openai_speech(const String& api_key, const String& model, const String& input, const String& instructions, const String& voice, const String& response_format, const String& speed) {
+ ps_ptr host;
+ host.assign("api.openai.com");
+ char path[] = "/v1/audio/speech";
+
+ if (input == "") {
+ AUDIO_LOG_WARN("input text is empty");
+ stopSong();
+ return false;
+ }
+ xSemaphoreTakeRecursive(mutex_playAudioData, 0.3 * configTICK_RATE_HZ);
+
+ setDefaults();
+ m_f_ssl = true;
+
+ m_speechtxt.assign(input.c_str());
+
+ // Escape special characters in input
+ String input_clean = "";
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (c == '\"') {
+ input_clean += "\\\"";
+ } else if (c == '\n') {
+ input_clean += "\\n";
+ } else if (c == '\r') {
+ input_clean += "\\r";
+ } else if (c == '\t') {
+ input_clean += "\\t";
+ } else if (c == '\\') {
+ input_clean += "\\\\";
+ } else if (c == '\b') {
+ input_clean += "\\b";
+ } else if (c == '\f') {
+ input_clean += "\\f";
+ } else {
+ input_clean += c;
+ }
+ }
+
+ // Escape special characters in instructions
+ String instructions_clean = "";
+ for (int i = 0; i < instructions.length(); i++) {
+ char c = instructions.charAt(i);
+ if (c == '\"') {
+ instructions_clean += "\\\"";
+ } else if (c == '\n') {
+ instructions_clean += "\\n";
+ } else if (c == '\r') {
+ instructions_clean += "\\r";
+ } else if (c == '\t') {
+ instructions_clean += "\\t";
+ } else if (c == '\\') {
+ instructions_clean += "\\\\";
+ } else if (c == '\b') {
+ instructions_clean += "\\b";
+ } else if (c == '\f') {
+ instructions_clean += "\\f";
+ } else {
+ instructions_clean += c;
+ }
+ }
+
+ String post_body = "{"
+ "\"model\": \"" +
+ model + "\"," + "\"stream\": true," + // add
+ "\"input\": \"" + input_clean + "\"," + "\"instructions\": \"" + instructions_clean + "\"," + "\"voice\": \"" + voice + "\"," + "\"response_format\": \"" + response_format +
+ "\"," + "\"speed\": " + speed + "}";
+
+ String http_request =
+ // "POST " + String(path) + " HTTP/1.0\r\n" // UNKNOWN ERROR CODE (0050) - crashing on HTTP/1.1 need to use HTTP/1.0
+ "POST " + String(path) + " HTTP/1.1\r\n" + "Host: " + host.get() + "\r\n" + "Authorization: Bearer " + api_key + "\r\n" + "Accept-Encoding: identity;q=1,*;q=0\r\n" +
+ "User-Agent: nArija/1.0\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: " + post_body.length() +
+ "\r\n"
+ // + "Connection: close\r\n" + "\r\n"
+ + "\r\n" + post_body + "\r\n";
+
+ bool res = true;
+ int port = 443;
+ m_client = static_cast(&clientsecure);
+
+ uint32_t t = millis();
+ info(*this, evt_info, "Connect to: \"{}\"", host.get());
+ res = m_client->connect(host.get(), port, m_timeout_ms_ssl);
+ if (res) {
+ uint32_t dt = millis() - t;
+ m_lastHost.assign(host.get());
+ m_currentHost.clone_from(host);
+ info(*this, evt_info, "{} has been established in {} ms", m_f_ssl ? "SSL" : "Connection", dt);
+ m_f_running = true;
+ }
+
+ m_expectedCodec = CODEC_NONE;
+ m_expectedPlsFmt = FORMAT_NONE;
+
+ if (res) {
+ m_client->print(http_request);
+ if (response_format == "mp3") m_expectedCodec = CODEC_MP3;
+ if (response_format == "opus") m_expectedCodec = CODEC_OPUS;
+ if (response_format == "aac") m_expectedCodec = CODEC_AAC;
+ if (response_format == "flac") m_expectedCodec = CODEC_FLAC;
+
+ m_dataMode = HTTP_RESPONSE_HEADER;
+ m_f_tts = true;
+ } else {
+ AUDIO_LOG_WARN("Request {} failed!", host.get());
+ }
+ xSemaphoreGiveRecursive(mutex_playAudioData);
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+audiolib::hwoe_t Audio::dismantle_host(const char* host) {
+ if (!host) return {};
+
+ audiolib::hwoe_t result;
+
+ const char* p = host;
+
+ // 🔐 1. SSL check
+ if (strncmp(p, "https://", 8) == 0) {
+ result.ssl = true;
+ p += 8;
+ } else if (strncmp(p, "http://", 7) == 0) {
+ result.ssl = false;
+ p += 7;
+ } else { // No valid scheme -> error or acceptance http
+ result.ssl = false;
+ }
+
+ // ❓ 2. extract host (from p to ':' or '/' or '?')
+ const char* host_start = p;
+ const char* port_sep = strchr(p, ':');
+ const char* path_sep = strchr(p, '/');
+ const char* query_sep = strchr(p, '?');
+
+ const char* host_end = p + strlen(p); // default: end of string
+ if (port_sep && port_sep < host_end) host_end = port_sep;
+ if (path_sep && path_sep < host_end) host_end = path_sep;
+ if (query_sep && query_sep < host_end) host_end = query_sep;
+ result.hwoe.copy_from(host_start, host_end - host_start);
+ result.rqh_host.clone_from(result.hwoe);
+
+ // ❓ 3. extract port
+ result.port = result.ssl ? 443 : 80; // default
+ if (port_sep && (!path_sep || port_sep < path_sep)) {
+ result.port = atoi(port_sep + 1);
+ result.rqh_host.appendf(":{}", result.port);
+ }
+
+ // ❓ 4. extract extension (path)
+ if (path_sep) {
+ const char* path_start = path_sep + 1;
+ const char* path_end = query_sep ? query_sep : host + strlen(host);
+ result.extension.copy_from(path_start, path_end - path_start);
+ }
+
+ // ❓ 5. extract query string
+ if (query_sep) { result.query_string.assign(query_sep + 1); }
+
+ return result;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::connecttohost(const char* host, const char* user, const char* pwd) { // user and pwd for authentification only, can be empty
+
+ ps_ptr c_host = host; // copy of host
+ ps_ptr c_user = user; // copy of user
+ ps_ptr c_pwd = pwd; // copy of password
+
+ if (!c_host.valid()) {
+ AUDIO_LOG_ERROR("Hostaddress is empty");
+ stopSong();
+ return false;
+ }
+
+ c_host.trim();
+
+ if (c_host.strlen() < 8) {
+ AUDIO_LOG_ERROR("Hostaddress is too short");
+ stopSong();
+ return false;
+ }
+
+ if (c_host.strlen() > 2048) {
+ AUDIO_LOG_ERROR("Hostaddress is too long");
+ stopSong();
+ return false;
+ } // max length in Chrome DevTools
+
+ const char* user_agent_0 = "Mozilla/5.0 (X11; Linux x86_64) Chrome/146.0.0.0 Safari/537.36";
+ const char* user_agent_1 = "VLC/3.0.21 LibVLC/3.0.21 AppleWebKit/537.36 (KHTML, like Gecko)";
+
+ bool res = false; // return value
+ uint16_t port = 0; // port number
+ uint16_t authLen = 0; // length of authorization
+ uint32_t timestamp = 0; // timeout surveillance
+
+ ps_ptr hwoe; // host without extension
+ ps_ptr rqh_host; // host for request header
+ ps_ptr extension; // extension
+ ps_ptr query_string; // parameter
+ ps_ptr path; // extension + '?' + parameter
+ ps_ptr rqh; // request header
+
+ xSemaphoreTakeRecursive(mutex_playAudioData, 0.3 * configTICK_RATE_HZ);
+
+ auto dismantledHost = dismantle_host(c_host.get());
+
+ // https://edge.live.mp3.mdn.newmedia.nacamar.net:8000/ps-charivariwb/livestream.mp3;?user=ps-charivariwb;&pwd=ps-charivariwb-------
+ // | | | | |
+ // | | | | | (query string)
+ // ssl?| |<-----host without extension-------->|port|<----- --extension----------->|<-first parameter->|<-second parameter->.......
+ // |
+ // |<-----------------------------path------------------------------------>......
+
+ m_f_ssl = dismantledHost.ssl;
+ port = dismantledHost.port;
+ hwoe = dismantledHost.hwoe.c_get();
+ rqh_host = dismantledHost.rqh_host.c_get();
+ extension = dismantledHost.extension.c_get();
+ query_string = dismantledHost.query_string.c_get();
+
+ if (query_string.strlen()) extension.appendf("?{}", query_string);
+ path = urlencode(extension.get(), true);
+
+ // optional basic authorization
+ if (c_user.valid() && c_pwd.valid()) authLen = c_user.strlen() + c_pwd.strlen();
+ ps_ptr authorization;
+ ps_ptr toEncode;
+ authorization.calloc(base64_encode_expected_len(authLen + 1) + 1, "authorization");
+ if (authLen > 0) {
+ toEncode.assignf("{}:{}", c_user, c_pwd);
+ b64encode((const char*)toEncode.get(), toEncode.strlen(), authorization.get());
+ }
+
+ setDefaults();
+
+ rqh.assignf("GET /{}", path);
+ rqh.append(" HTTP/1.1\r\n");
+ rqh.appendf("Host: {}\r\n", rqh_host);
+ rqh.append("Icy-MetaData:1\r\n");
+ rqh.append("Pragma: no-cache\r\n");
+ rqh.append("Cache-Control: no-cache\r\n");
+ rqh.append("Range: bytes=0-\r\n");
+ rqh.append("Accept: */*\r\n");
+ rqh.appendf("User-Agent: {}\r\n", m_f_alt_user_agent ? user_agent_0 : user_agent_1);
+ if (authLen > 0) {
+ rqh.append("Authorization: Basic ");
+ rqh.append(authorization);
+ rqh.append("\r\n");
+ }
+ rqh.append("Accept-Encoding: identity;q=1,*;q=0\r\n");
+ rqh.append("Connection: keep-alive\r\n\r\n");
+
+ if (m_f_ssl) {
+ m_client = static_cast(&clientsecure);
+ if (port == 80) port = 443;
+ } else {
+ m_client = static_cast(&client);
+ }
+
+ timestamp = millis();
+ m_client->setTimeout(m_f_ssl ? m_timeout_ms_ssl : m_timeout_ms);
+
+ info(*this, evt_info, "connect to: \"{}\" on port {} path \"/{}\"", hwoe.get(), port, path.get());
+ res = m_client->connect(hwoe.get(), port);
+
+ m_expectedCodec = CODEC_NONE;
+ m_expectedPlsFmt = FORMAT_NONE;
+
+ if (res) {
+ uint32_t dt = millis() - timestamp;
+ info(*this, evt_info, "{} has been established in {} ms", m_f_ssl ? "SSL" : "Connection", dt);
+ m_f_running = true;
+ m_client->print(rqh.get());
+ if (extension.ends_with_icase(".mp3")) m_expectedCodec = CODEC_MP3;
+ if (extension.ends_with_icase(".aac")) m_expectedCodec = CODEC_AAC;
+ if (extension.ends_with_icase(".wav")) m_expectedCodec = CODEC_WAV;
+ if (extension.ends_with_icase(".m4a")) m_expectedCodec = CODEC_M4A;
+ if (extension.ends_with_icase(".ogg")) m_expectedCodec = CODEC_OGG;
+ if (extension.ends_with_icase(".flac")) m_expectedCodec = CODEC_FLAC;
+ if (extension.ends_with_icase("-flac")) m_expectedCodec = CODEC_FLAC;
+ if (extension.ends_with_icase(".opus")) m_expectedCodec = CODEC_OPUS;
+ if (extension.ends_with_icase("/opus")) m_expectedCodec = CODEC_OPUS;
+ if (extension.ends_with_icase(".asx")) m_expectedPlsFmt = FORMAT_ASX;
+ if (extension.ends_with_icase(".m3u")) m_expectedPlsFmt = FORMAT_M3U;
+ if (extension.ends_with_icase(".pls")) m_expectedPlsFmt = FORMAT_PLS;
+ if (extension.contains(".m3u8")) m_expectedPlsFmt = FORMAT_M3U8;
+
+ m_currentHost = c_host;
+ m_lastHost = c_host;
+ info(*this, evt_lasthost, "{}", m_lastHost.c_get());
+ m_dataMode = HTTP_RESPONSE_HEADER; // Handle header
+ m_streamType = ST_WEBSTREAM;
+ } else {
+ AUDIO_LOG_ERROR("Request \"{}\" failed!", c_host.get());
+ m_f_running = false;
+ }
+ xSemaphoreGiveRecursive(mutex_playAudioData);
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::httpPrint(const char* host) {
+
+ ps_ptr c_host = host; // copy of host
+
+ if (!c_host.valid()) {
+ AUDIO_LOG_ERROR("Hostaddress is empty");
+ stopSong();
+ return false;
+ }
+
+ uint16_t port = 0; // port number
+ ps_ptr hwoe; // host without extension
+ ps_ptr rqh_host; // host in request header
+ ps_ptr extension; // extension
+ ps_ptr query_string; // parameter
+ ps_ptr path; // extension + '?' + parameter
+ ps_ptr rqh; // request header
+ ps_ptr cur_hwoe; // m_currenthost without extension
+
+ c_host.trim();
+ auto dismantledHost = dismantle_host(c_host.get());
+
+ // https://edge.live.mp3.mdn.newmedia.nacamar.net:8000/ps-charivariwb/livestream.mp3;?user=ps-charivariwb;&pwd=ps-charivariwb-------
+ // | | | | |
+ // | | | | | (query string)
+ // ssl?| |<-----host without extension-------->|port|<----- --extension----------->|<-first parameter->|<-second parameter->.......
+ // |
+ // |<-----------------------------path------------------------------------------->
+
+ m_f_ssl = dismantledHost.ssl;
+ port = dismantledHost.port;
+ hwoe = dismantledHost.hwoe;
+ rqh_host = dismantledHost.rqh_host;
+ extension = dismantledHost.extension;
+ query_string = dismantledHost.query_string;
+
+ if (query_string.strlen()) extension.appendf("?{}", query_string);
+ path = urlencode(extension.get(), true);
+
+ if (!m_currentHost.valid()) m_currentHost.assign("");
+ auto dismantledLastHost = dismantle_host(m_currentHost.get());
+ cur_hwoe = dismantledLastHost.hwoe;
+
+ bool f_equal = true;
+ if (hwoe == cur_hwoe && port == dismantledLastHost.port) {
+ f_equal = true;
+ } else {
+ f_equal = false;
+ }
+
+ rqh.assignf("GET /{}", path);
+ rqh.append(" HTTP/1.1\r\n");
+ rqh.appendf("Host: {}\r\n", rqh_host);
+ rqh.append("Icy-MetaData:1\r\n");
+ rqh.append("Accept:*/*\r\n");
+ rqh.append("User-Agent: VLC/3.0.21 LibVLC/3.0.21 AppleWebKit/537.36 (KHTML, like Gecko)\r\n");
+ rqh.append("Accept-Encoding: identity;q=1,*;q=0\r\n");
+ rqh.append("Connection: keep-alive\r\n\r\n");
+
+ info(*this, evt_info, "next URL: \"{}\"", c_host.get());
+
+ if (f_equal == false) {
+ if (m_client->connected()) m_client->stop();
+ }
+ if (!m_client->connected()) {
+ if (m_f_ssl) {
+ m_client = static_cast(&clientsecure);
+ if (m_f_ssl && port == 80) port = 443;
+ } else {
+ m_client = static_cast(&client);
+ }
+ if (f_equal) info(*this, evt_info, "The host has disconnected, reconnecting");
+
+ if (!m_client->connect(hwoe.get(), port)) {
+ AUDIO_LOG_ERROR("connection lost {}", c_host.c_get());
+ stopSong();
+ return false;
+ }
+ }
+ m_currentHost = c_host;
+ m_client->print(rqh.get());
+
+ if (extension.ends_with_icase(".mp3"))
+ m_expectedCodec = CODEC_MP3;
+ else if (extension.ends_with_icase(".aac"))
+ m_expectedCodec = CODEC_AAC;
+ else if (extension.ends_with_icase(".wav"))
+ m_expectedCodec = CODEC_WAV;
+ else if (extension.ends_with_icase(".m4a"))
+ m_expectedCodec = CODEC_M4A;
+ else if (extension.ends_with_icase(".flac"))
+ m_expectedCodec = CODEC_FLAC;
+ else if (extension.ends_with_icase(".m4s")) { // is MP4-Container
+ stopSong();
+ AUDIO_LOG_WARN("MP4-Container not supported");
+ return false;
+ } else
+ m_expectedCodec = CODEC_NONE;
+
+ if (extension.ends_with_icase(".asx"))
+ m_expectedPlsFmt = FORMAT_ASX;
+ else if (extension.ends_with_icase(".m3u"))
+ m_expectedPlsFmt = FORMAT_M3U;
+ else if (extension.contains(".m3u8"))
+ m_expectedPlsFmt = FORMAT_M3U8;
+ else if (extension.ends_with_icase(".pls"))
+ m_expectedPlsFmt = FORMAT_PLS;
+ else
+ m_expectedPlsFmt = FORMAT_NONE;
+
+ m_audioFileSize = 0;
+ m_dataMode = HTTP_RESPONSE_HEADER; // Handle header
+ m_streamType = ST_WEBSTREAM;
+ m_f_chunked = false;
+ AUDIO_LOG_DEBUG("playlistFormat {}, dataMode {}, streamType: {}", plsFmtStr[m_playlistFormat], dataModeStr[m_dataMode], streamTypeStr[m_streamType]);
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::httpRange(uint32_t seek, uint32_t length) {
+
+ if (!m_f_running) return false;
+
+ uint16_t port = 0; // port number
+ ps_ptr c_host; // copy of host
+ ps_ptr hwoe; // host without extension
+ ps_ptr rqh_host; // host in request header
+ ps_ptr extension; // extension
+ ps_ptr query_string; // parameter
+ ps_ptr path; // extension + '?' + parameter
+ ps_ptr rqh; // request header
+ ps_ptr cur_hwoe; // m_currenthost without extension
+ ps_ptr range; // e.g. "Range: bytes=124-"
+
+ c_host.clone_from(m_currentHost);
+ c_host.trim();
+ auto dismantledHost = dismantle_host(c_host.get());
+
+ // https://edge.live.mp3.mdn.newmedia.nacamar.net:8000/ps-charivariwb/livestream.mp3;?user=ps-charivariwb;&pwd=ps-charivariwb-------
+ // | | | | |
+ // | | | | | (query string)
+ // ssl?| |<-----host without extension-------->|port|<----- --extension----------->|<-first parameter->|<-second parameter->.......
+ // |
+ // |<-----------------------------path------------------------------------------->
+
+ m_f_ssl = dismantledHost.ssl;
+ port = dismantledHost.port;
+ if (dismantledHost.hwoe.valid()) hwoe.clone_from(dismantledHost.hwoe);
+ if (dismantledHost.rqh_host.valid()) rqh_host.clone_from(dismantledHost.rqh_host);
+ if (dismantledHost.extension.valid()) extension.clone_from(dismantledHost.extension);
+ if (dismantledHost.query_string.valid()) query_string.clone_from(dismantledHost.query_string);
+
+ if (extension.valid()) path.assign(extension.get());
+ if (query_string.valid()) {
+ path.append("?");
+ path.append(query_string.get());
+ }
+ if (!hwoe.valid()) hwoe.assign("");
+ if (!extension.valid()) extension.assign("");
+ if (!path.valid()) path.assign("");
+
+ path = urlencode(path.get(), true);
+
+ if (!m_currentHost.valid()) m_currentHost.assign("");
+ auto dismantledLastHost = dismantle_host(m_currentHost.get());
+ cur_hwoe.clone_from(dismantledLastHost.hwoe);
+
+ if (length == UINT32_MAX)
+ range.assignf("Range: bytes={}-\r\n", seek);
+ else
+ range.assignf("Range: bytes={}-{}\r\n", seek, seek + length);
+
+ rqh.assignf("GET /{} HTTP/1.1\r\n", path.get());
+ rqh.appendf("Host: {}\r\n", rqh_host.get());
+ rqh.append("Accept: */*\r\n");
+ rqh.append("Accept-Encoding: identity;q=1,*;q=0\r\n");
+ rqh.append("Cache-Control: no-cache\r\n");
+ rqh.append("Connection: keep-alive\r\n");
+ rqh.appendf(range.c_get());
+ rqh.appendf("Referer: {}\r\n", m_currentHost.c_get());
+ rqh.append("Sec-GPC: 1\r\n");
+ rqh.append("User-Agent: VLC/3.0.21 LibVLC/3.0.21 AppleWebKit/537.36 (KHTML, like Gecko)\r\n\r\n");
+
+ if (m_client->connected()) { m_client->stop(); }
+ if (m_f_ssl) {
+ m_client = static_cast(&clientsecure);
+ if (m_f_ssl && port == 80) port = 443;
+ } else {
+ m_client = static_cast(&client);
+ }
+
+ if (!m_client->connect(hwoe.get(), port)) {
+ AUDIO_LOG_ERROR("connection lost {}", c_host.c_get());
+ stopSong();
+ return false;
+ }
+
+ // AUDIO_LOG_INFO("rqh \n{}", rqh.get());
+
+ m_client->print(rqh.c_get());
+ // m_resumeFilePos = seek; // used in processWebFile()
+ m_dataMode = HTTP_RANGE_HEADER;
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::connecttoFS(fs::FS& fs, const char* path, int32_t fileStartTime) {
+
+ xSemaphoreTakeRecursive(mutex_playAudioData, 0.3 * configTICK_RATE_HZ);
+ ps_ptr c_path;
+ ps_ptr audioPath;
+ bool res = false;
+ m_fileStartTime = fileStartTime;
+ m_codec = CODEC_NONE;
+
+ if (!path) {
+ AUDIO_LOG_ERROR("file path is not set");
+ goto exit;
+ } // guard
+ c_path.copy_from(path); // copy from path
+ c_path.trim();
+ if (!c_path.contains(".")) {
+ AUDIO_LOG_ERROR("No file extension found");
+ goto exit;
+ } // guard
+ setDefaults(); // free buffers an set defaults
+
+ if (c_path.ends_with_icase(".mp3")) m_codec = CODEC_MP3;
+ if (c_path.ends_with_icase(".m4a")) m_codec = CODEC_M4A;
+ if (c_path.ends_with_icase(".aac")) m_codec = CODEC_AAC;
+ if (c_path.ends_with_icase(".wav")) m_codec = CODEC_WAV;
+ if (c_path.ends_with_icase(".flac")) m_codec = CODEC_FLAC;
+ if (c_path.ends_with_icase(".opus")) m_codec = CODEC_OGG;
+ if (c_path.ends_with_icase(".ogg")) m_codec = CODEC_OGG;
+ if (c_path.ends_with_icase(".oga")) m_codec = CODEC_OGG;
+
+ if (m_codec == CODEC_OGG) m_f_ogg = true;
+ if (m_codec == CODEC_NONE) { // guard
+ int dotPos = c_path.last_index_of('.');
+ AUDIO_LOG_WARN("The {} format is not supported", path + dotPos);
+ goto exit;
+ }
+
+ if (!c_path.starts_with("/")) c_path.insert("/", 0);
+
+ if (!fs.exists(c_path.get())) {
+ AUDIO_LOG_WARN("file not found: {}", c_path.get());
+ goto exit;
+ }
+ info(*this, evt_info, "Reading file: \"{}\"", c_path.get());
+ m_audiofile = fs.open(c_path.get());
+ m_dataMode = AUDIO_LOCALFILE;
+ m_audioFileSize = m_audiofile.size();
+ m_f_running = true;
+ res = true;
+
+exit:
+ xSemaphoreGiveRecursive(mutex_playAudioData);
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::connecttospeech(const char* speech, const char* lang) {
+ xSemaphoreTakeRecursive(mutex_playAudioData, 0.3 * configTICK_RATE_HZ);
+
+ setDefaults();
+ char host[] = "translate.google.com.vn";
+ char path[] = "/translate_tts";
+
+ m_speechtxt.assign(speech); // unique pointer takes care of the memory management
+ auto urlStr = urlencode(speech, false); // percent encoding
+
+ ps_ptr req; // request header
+ req.assign("GET ");
+ req.append(path);
+ req.append("?ie=UTF-8&tl=");
+ req.append(lang);
+ req.append("&client=tw-ob&q=");
+ req.append(urlStr.get());
+ req.append(" HTTP/1.1\r\n");
+ req.append("Host: ");
+ req.append(host);
+ req.append("\r\n");
+ req.append("User-Agent: Mozilla/5.0 \r\n");
+ req.append("Accept-Encoding: identity\r\n");
+ req.append("Accept: text/html\r\n");
+ req.append("Connection: close\r\n\r\n");
+
+ m_client = static_cast(&client);
+ info(*this, evt_info, "connect to \"{}\"", host);
+ if (!m_client->connect(host, 80)) {
+ AUDIO_LOG_ERROR("Connection failed");
+ xSemaphoreGiveRecursive(mutex_playAudioData);
+ return false;
+ }
+ m_client->print(req.get());
+
+ m_f_running = true;
+ m_f_ssl = false;
+ m_f_tts = true;
+ m_dataMode = HTTP_RESPONSE_HEADER;
+ m_lastHost.assign(host);
+ m_currentHost.copy_from(host);
+ xSemaphoreGiveRecursive(mutex_playAudioData);
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::showID3Tag(const char* tag, const char* value) {
+ ps_ptr id3tag;
+ id3tag.set_name("id3tag");
+ // V2.2
+ if (!strcmp(tag, "CNT")) id3tag.assignf("Play counter: {}", value);
+ if (!strcmp(tag, "COM")) id3tag.assignf("Comments: {}", value);
+ if (!strcmp(tag, "CRA")) id3tag.assignf("Audio encryption: {}", value);
+ if (!strcmp(tag, "CRM")) id3tag.assignf("Encrypted meta frame: {}", value);
+ if (!strcmp(tag, "ETC")) id3tag.assignf("Event timing codes: {}", value);
+ if (!strcmp(tag, "EQU")) id3tag.assignf("Equalization: {}", value);
+ if (!strcmp(tag, "IPL")) id3tag.assignf("Involved people list: {}", value);
+ if (!strcmp(tag, "PIC")) id3tag.assignf("Attached picture: {}", value);
+ if (!strcmp(tag, "SLT")) id3tag.assignf("Synchronized lyric/text: {}", value);
+ if (!strcmp(tag, "TAL")) id3tag.assignf("Album/Movie/Show title: {}", value);
+ if (!strcmp(tag, "TBP")) id3tag.assignf("BPM (Beats Per Minute): {}", value);
+ if (!strcmp(tag, "TCM")) id3tag.assignf("Composer: {}", value);
+ if (!strcmp(tag, "TCO")) id3tag.assignf("Content type: {}", value);
+ if (!strcmp(tag, "TCR")) id3tag.assignf("Copyright message: {}", value);
+ if (!strcmp(tag, "TDA")) id3tag.assignf("Date: {}", value);
+ if (!strcmp(tag, "TDY")) id3tag.assignf("Playlist delay: {}", value);
+ if (!strcmp(tag, "TEN")) id3tag.assignf("Encoded by: {}", value);
+ if (!strcmp(tag, "TFT")) id3tag.assignf("File type: {}", value);
+ if (!strcmp(tag, "TIM")) id3tag.assignf("Time: {}", value);
+ if (!strcmp(tag, "TKE")) id3tag.assignf("Initial key: {}", value);
+ if (!strcmp(tag, "TLA")) id3tag.assignf("Language(s): {}", value);
+ if (!strcmp(tag, "TLE")) id3tag.assignf("Length: {}", value);
+ if (!strcmp(tag, "TMT")) id3tag.assignf("Media type: {}", value);
+ if (!strcmp(tag, "TOA")) id3tag.assignf("Original artist(s)/performer(s): {}", value);
+ if (!strcmp(tag, "TOF")) id3tag.assignf("Original filename: {}", value);
+ if (!strcmp(tag, "TOL")) id3tag.assignf("Original Lyricist(s)/text writer(s): {}", value);
+ if (!strcmp(tag, "TOR")) id3tag.assignf("Original release year: {}", value);
+ if (!strcmp(tag, "TOT")) id3tag.assignf("Original album/Movie/Show title: {}", value);
+ if (!strcmp(tag, "TP1")) id3tag.assignf("Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group: {}", value);
+ if (!strcmp(tag, "TP2")) id3tag.assignf("Band/Orchestra/Accompaniment: {}", value);
+ if (!strcmp(tag, "TP3")) id3tag.assignf("Conductor/Performer refinement: {}", value);
+ if (!strcmp(tag, "TP4")) id3tag.assignf("Interpreted, remixed, or otherwise modified by: {}", value);
+ if (!strcmp(tag, "TPA")) id3tag.assignf("Part of a set: {}", value);
+ if (!strcmp(tag, "TPB")) id3tag.assignf("Publisher: {}", value);
+ if (!strcmp(tag, "TRC")) id3tag.assignf("ISRC (International Standard Recording Code): {}", value);
+ if (!strcmp(tag, "TRD")) id3tag.assignf("Recording dates: {}", value);
+ if (!strcmp(tag, "TRK")) id3tag.assignf("Track number/Position in set: {}", value);
+ if (!strcmp(tag, "TSI")) id3tag.assignf("Size: {}", value);
+ if (!strcmp(tag, "TSS")) id3tag.assignf("Software/hardware and settings used for encoding: {}", value);
+ if (!strcmp(tag, "TT1")) id3tag.assignf("Content group description: {}", value);
+ if (!strcmp(tag, "TT2")) id3tag.assignf("Title/Songname/Content description: {}", value);
+ if (!strcmp(tag, "TT3")) id3tag.assignf("Subtitle/Description refinement: {}", value);
+ if (!strcmp(tag, "TXT")) id3tag.assignf("Lyricist/text writer: {}", value);
+ if (!strcmp(tag, "TXX")) id3tag.assignf("User defined text information frame: {}", value);
+ if (!strcmp(tag, "TYE")) id3tag.assignf("Year: {}", value);
+ if (!strcmp(tag, "UFI")) id3tag.assignf("Unique file identifier: {}", value);
+ if (!strcmp(tag, "ULT")) id3tag.assignf("Unsychronized lyric/text transcription: {}", value);
+ if (!strcmp(tag, "WAF")) id3tag.assignf("Official audio file webpage: {}", value);
+ if (!strcmp(tag, "WAR")) id3tag.assignf("Official artist/performer webpage: {}", value);
+ if (!strcmp(tag, "WAS")) id3tag.assignf("Official audio source webpage: {}", value);
+ if (!strcmp(tag, "WCM")) id3tag.assignf("Commercial information: {}", value);
+ if (!strcmp(tag, "WCP")) id3tag.assignf("Copyright/Legal information: {}", value);
+ if (!strcmp(tag, "WPB")) id3tag.assignf("Publishers official webpage: {}", value);
+ if (!strcmp(tag, "WXX")) id3tag.assignf("User defined URL link frame: {}", value);
+
+ // V2.3 V2.4 tags
+ if (!strcmp(tag, "COMM")) id3tag.assignf("Comment: {}", value);
+ if (!strcmp(tag, "OWNE")) id3tag.assignf("Ownership: {}", value);
+ if (!strcmp(tag, "PRIV")) id3tag.assignf("Private: {}", value);
+ if (!strcmp(tag, "SYLT")) id3tag.assignf("SynLyrics: {}", value);
+ if (!strcmp(tag, "TALB")) id3tag.assignf("Album: {}", value);
+ if (!strcmp(tag, "TBPM")) id3tag.assignf("BeatsPerMinute: {}", value);
+ if (!strcmp(tag, "TCAT")) id3tag.assignf("PodcastCategory: {}", value);
+ if (!strcmp(tag, "TCMP")) id3tag.assignf("Compilation: {}", value);
+ if (!strcmp(tag, "TCOM")) id3tag.assignf("Composer: {}", value);
+ if (!strcmp(tag, "TCON")) id3tag.assignf("ContentType: {}", value);
+ if (!strcmp(tag, "TCOP")) id3tag.assignf("Copyright: {}", value);
+ if (!strcmp(tag, "TDAT")) id3tag.assignf("Date: {}", value);
+ if (!strcmp(tag, "TDES")) id3tag.assignf("PodcastDescription: {}", value);
+ if (!strcmp(tag, "TDOR")) id3tag.assignf("Original Release Year: {}", value);
+ if (!strcmp(tag, "TDRC")) id3tag.assignf("Publication date: {}", value);
+ if (!strcmp(tag, "TDRL")) id3tag.assignf("ReleaseTime: {}", value);
+ if (!strcmp(tag, "TENC")) id3tag.assignf("Encoded by: {}", value);
+ if (!strcmp(tag, "TEXT")) {} // id3tag.assignf("Lyricist: {}", value);
+ if (!strcmp(tag, "TGID")) id3tag.assignf("PodcastID: {}", value);
+ if (!strcmp(tag, "TIME")) id3tag.assignf("Time: {}", value);
+ if (!strcmp(tag, "TIT1")) id3tag.assignf("Grouping: {}", value);
+ if (!strcmp(tag, "TIT2")) id3tag.assignf("Title: {}", value);
+ if (!strcmp(tag, "TIT3")) id3tag.assignf("Subtitle: {}", value);
+ if (!strcmp(tag, "TLAN")) id3tag.assignf("Language: {}", value);
+ if (!strcmp(tag, "TLEN")) id3tag.assignf("Length (ms): {}", value);
+ if (!strcmp(tag, "TMED")) id3tag.assignf("Media: {}", value);
+ if (!strcmp(tag, "TOAL")) id3tag.assignf("OriginalAlbum: {}", value);
+ if (!strcmp(tag, "TOPE")) id3tag.assignf("OriginalArtist: {}", value);
+ if (!strcmp(tag, "TOLY")) id3tag.assignf("OriginalLyricist: {}", value);
+ if (!strcmp(tag, "TORY")) id3tag.assignf("OriginalReleaseYear: {}", value);
+ if (!strcmp(tag, "TPE1")) id3tag.assignf("Artist: {}", value);
+ if (!strcmp(tag, "TPE2")) id3tag.assignf("Band: {}", value);
+ if (!strcmp(tag, "TPE3")) id3tag.assignf("Conductor: {}", value);
+ if (!strcmp(tag, "TPE4")) id3tag.assignf("InterpretedBy: {}", value);
+ if (!strcmp(tag, "TPOS")) id3tag.assignf("PartOfSet: {}", value);
+ if (!strcmp(tag, "TPUB")) id3tag.assignf("Publisher: {}", value);
+ if (!strcmp(tag, "TRCK")) id3tag.assignf("Track: {}", value);
+ if (!strcmp(tag, "TRSN")) id3tag.assignf("InternetRadioStationName: {}", value);
+ if (!strcmp(tag, "TSSE")) id3tag.assignf("SettingsForEncoding: {}", value);
+ if (!strcmp(tag, "TRDA")) id3tag.assignf("RecordingDates: {}", value);
+ if (!strcmp(tag, "TSO2")) id3tag.assignf("AlbumArtistSortOrder: {}", value);
+ if (!strcmp(tag, "TSOA")) id3tag.assignf("AlbumSortOrder: {}", value);
+ if (!strcmp(tag, "TSOP")) id3tag.assignf("PerformerSortOrder: {}", value);
+ if (!strcmp(tag, "TSOC")) id3tag.assignf("ComposerSortOrder: {}", value);
+ if (!strcmp(tag, "TSOT")) id3tag.assignf("TitleSortOrder: {}", value);
+ if (!strcmp(tag, "TXXX")) id3tag.assignf("UserDefinedText: {}", value);
+ if (!strcmp(tag, "TYER")) id3tag.assignf("Year: {}", value);
+ if (!strcmp(tag, "USER")) id3tag.assignf("TermsOfUse: {}", value);
+ if (!strcmp(tag, "USLT")) id3tag.assignf("Lyrics: {}", value);
+ if (!strcmp(tag, "WCOM")) id3tag.assignf("Commercial URL: {}", value);
+ if (!strcmp(tag, "WFED")) id3tag.assignf("Podcast URL: {}", value);
+ if (!strcmp(tag, "WOAF")) id3tag.assignf("File URL: {}", value);
+ if (!strcmp(tag, "WOAR")) id3tag.assignf("OfficialArtistWebpage: {}", value);
+ if (!strcmp(tag, "WOAS")) id3tag.assignf("Source URL: {}", value);
+ if (!strcmp(tag, "WORS")) id3tag.assignf("InternetRadioStationURL: {}", value);
+ if (!strcmp(tag, "WXXX")) id3tag.assignf("User defined URL link frame: {}", value);
+ if (!strcmp(tag, "XDOR")) id3tag.assignf("OriginalReleaseTime: {}", value);
+
+ if (!id3tag.valid()) {
+ AUDIO_LOG_DEBUG("unknown tag: {}", tag);
+ return;
+ }
+
+ latinToUTF8(id3tag);
+
+ if (id3tag.contains("?xml")) {
+ showstreamtitle(id3tag.get());
+ return;
+ }
+
+ if (id3tag.starts_with("Grouping")) {
+ showstreamtitle(id3tag.get());
+ return;
+ }
+ if (id3tag.strlen()) { info(*this, evt_id3data, "{}", id3tag.get()); }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::latinToUTF8(ps_ptr& buff, bool UTF8check) {
+ // most stations send strings in UTF-8 but a few sends in latin. To standardize this, all latin strings are
+ // converted to UTF-8. If UTF-8 is already present, nothing is done and true is returned.
+ // A conversion to UTF-8 extends the string. Therefore it is necessary to know the buffer size. If the converted
+ // string does not fit into the buffer, false is returned
+
+ uint16_t pos = 0;
+ uint16_t in = 0;
+ uint16_t out = 0;
+ bool isUTF8 = true;
+
+ // We cannot detect if a given string (or byte sequence) is a UTF-8 encoded text as for example each and every series
+ // of UTF-8 octets is also a valid (if nonsensical) series of Latin-1 (or some other encoding) octets.
+ // However not every series of valid Latin-1 octets are valid UTF-8 series. So you can rule out strings that do not conform
+ // to the UTF-8 encoding schema:
+
+ if (UTF8check) {
+ const size_t strLen = buff.strlen();
+ while (pos < strLen) {
+ if ((uint8_t)buff[pos] <= 0x7F) { // 0xxxxxxx: ASCII
+ pos++;
+ } else if ((buff[pos] & 0xE0) == 0xC0) {
+ // 110xxxxx 10xxxxxx: 2-byte
+ if (pos + 1 >= strLen || (uint8_t)buff[pos] < 0xC2 || ((uint8_t)buff[pos + 1] & 0xC0) != 0x80) {
+ isUTF8 = false;
+ break;
+ }
+ pos += 2;
+ } else if ((buff[pos] & 0xF0) == 0xE0) {
+ // 1110xxxx 10xxxxxx 10xxxxxx: 3-byte
+ if (pos + 2 >= strLen || ((uint8_t)buff[pos + 1] & 0xC0) != 0x80 || ((uint8_t)buff[pos + 2] & 0xC0) != 0x80) {
+ isUTF8 = false;
+ break;
+ }
+ if ((uint8_t)buff[pos] == 0xE0 && (uint8_t)buff[pos + 1] < 0xA0) {
+ isUTF8 = false;
+ break;
+ } // Overlong
+ if ((uint8_t)buff[pos] == 0xED && (uint8_t)buff[pos + 1] >= 0xA0) {
+ isUTF8 = false;
+ break;
+ } // UTF-16 surrogate
+ pos += 3;
+ } else if ((buff[pos] & 0xF8) == 0xF0) {
+ // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx: 4-byte
+ if (pos + 3 >= strLen || ((uint8_t)buff[pos + 1] & 0xC0) != 0x80 || ((uint8_t)buff[pos + 2] & 0xC0) != 0x80 || ((uint8_t)buff[pos + 3] & 0xC0) != 0x80) {
+ isUTF8 = false;
+ break;
+ }
+ if ((uint8_t)buff[pos] == 0xF0 && (uint8_t)buff[pos + 1] < 0x90) {
+ isUTF8 = false;
+ break;
+ } // Overlong
+ if ((uint8_t)buff[pos] > 0xF4 || ((uint8_t)buff[pos] == 0xF4 && (uint8_t)buff[pos + 1] > 0x8F)) {
+ isUTF8 = false;
+ break;
+ } // > U+10FFFF
+ pos += 4;
+ } else {
+ isUTF8 = false;
+ break; // Invalid first byte (continuation byte or 0xF8-0xFF)
+ }
+ }
+ if (isUTF8) return; // is UTF-8, do nothing
+ }
+ ps_ptr iso8859_1;
+ iso8859_1.assign(buff.get());
+
+ // Worst-case: all chars are latin1 > 0x7F became 2 Bytes → max length is twice +1
+ std::size_t requiredSize = strlen(iso8859_1.get()) * 2 + 1;
+
+ if (buff.size() < requiredSize) {
+ buff.realloc(requiredSize);
+ if (buff.size() < requiredSize) {
+ AUDIO_LOG_ERROR("latinToUTF8: realloc failed (need {} bytes, have {})", requiredSize, buff.size());
+ return; // keep original string, avoid buffer overflow
+ }
+ }
+
+ // coding into UTF-8
+ while (iso8859_1[in] != '\0') {
+ if (iso8859_1[in] < 0x80) {
+ buff[out++] = iso8859_1[in++];
+ } else {
+ buff[out++] = (0xC0 | (iso8859_1[in] >> 6));
+ buff[out++] = (0x80 | (iso8859_1[in] & 0x3F));
+ in++;
+ }
+ }
+ buff[out] = '\0';
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::htmlToUTF8(char* str) { // convert HTML to UTF-8
+
+ typedef struct { // --- EntityMap Definition ---
+ const char* name;
+ uint32_t codepoint;
+ } EntityMap;
+
+ const EntityMap entities[] = {
+ {"amp", 0x0026}, // &
+ {"lt", 0x003C}, // <
+ {"gt", 0x003E}, // >
+ {"quot", 0x0022}, // "
+ {"apos", 0x0027}, // '
+ {"nbsp", 0x00A0}, // non-breaking space
+ {"euro", 0x20AC}, // €
+ {"copy", 0x00A9}, // ©
+ {"reg", 0x00AE}, // ®
+ {"trade", 0x2122}, // ™
+ {"hellip", 0x2026}, // …
+ {"ndash", 0x2013}, // –
+ {"mdash", 0x2014}, // —
+ {"sect", 0x00A7}, // §
+ {"para", 0x00B6} // ¶
+ };
+
+ // --- EntityMap Lookup ---
+ auto find_entity = [&](const char* p, uint32_t* codepoint, int* entity_len) {
+ for (size_t i = 0; i < sizeof(entities) / sizeof(entities[0]); i++) {
+ const char* name = entities[i].name;
+ size_t len = strlen(name);
+ if (strncmp(p + 1, name, len) == 0 && p[len + 1] == ';') {
+ *codepoint = entities[i].codepoint;
+ *entity_len = (int)(len + 2); // &name;
+ return 1;
+ }
+ }
+ return 0;
+ };
+
+ auto codepoint_to_utf8 = [&](uint32_t cp, char* dst) { // Convert a Codepoint (Unicode) to UTF-8, writes in DST, there is number of bytes back
+ if (cp <= 0x7F) {
+ dst[0] = cp;
+ return 1;
+ } else if (cp <= 0x7FF) {
+ dst[0] = 0xC0 | (cp >> 6);
+ dst[1] = 0x80 | (cp & 0x3F);
+ return 2;
+ } else if (cp <= 0xFFFF) {
+ dst[0] = 0xE0 | (cp >> 12);
+ dst[1] = 0x80 | ((cp >> 6) & 0x3F);
+ dst[2] = 0x80 | (cp & 0x3F);
+ return 3;
+ } else if (cp <= 0x10FFFF) {
+ dst[0] = 0xF0 | (cp >> 18);
+ dst[1] = 0x80 | ((cp >> 12) & 0x3F);
+ dst[2] = 0x80 | ((cp >> 6) & 0x3F);
+ dst[3] = 0x80 | (cp & 0x3F);
+ return 4;
+ }
+ return -1; // invalid Codepoint
+ };
+
+ char* p = str;
+ while (*p != '\0') {
+ if (p[0] == '&') {
+ uint32_t cp;
+ int consumed;
+ if (find_entity(p, &cp, &consumed)) { // looking for entity, such as ©
+ char utf8[5] = {0};
+ int len = codepoint_to_utf8(cp, utf8);
+ if (len > 0) {
+ size_t tail_len = strlen(p + consumed);
+ memmove(p + len, p + consumed, tail_len + 1);
+ memcpy(p, utf8, len);
+ p += len;
+ continue;
+ }
+ }
+ }
+ if (p[0] == '&' && p[1] == '#') {
+ char* endptr;
+ uint32_t codepoint = strtol(p + 2, &endptr, 10);
+
+ if (*endptr == ';' && codepoint <= 0x10FFFF) {
+ char utf8[5] = {0};
+ int utf8_len = codepoint_to_utf8(codepoint, utf8);
+ if (utf8_len > 0) {
+ // size_t entity_len = endptr - p + 1;
+ size_t tail_len = strlen(endptr + 1);
+
+ // Show residual ring to the left
+ memmove(p + utf8_len, endptr + 1, tail_len + 1); // +1 because of '\0'
+
+ // Copy UTF-8 characters
+ memcpy(p, utf8, utf8_len);
+
+ // weiter bei neuem Zeichen
+ p += utf8_len;
+ continue;
+ }
+ }
+ }
+ p++;
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+size_t Audio::readAudioHeader(uint32_t bytes) {
+ size_t bytesReaded = 0;
+ if (m_codec == CODEC_WAV) {
+ int res = read_WAV_Header(InBuff.getReadPtr(), bytes);
+ if (res >= 0)
+ bytesReaded = res;
+ else { // error, skip header
+ m_controlCounter = 100;
+ }
+ }
+ if (m_codec == CODEC_MP3) {
+ int res = read_ID3_Header(InBuff.getReadPtr(), bytes);
+ if (res > 0 || m_controlCounter < 100)
+ bytesReaded = res;
+ else { // error, skip header
+ m_controlCounter = 100;
+ }
+ }
+ if (m_codec == CODEC_M4A) {
+ int res = read_M4A_Header(InBuff.getReadPtr(), bytes);
+ if (res >= 0)
+ bytesReaded = res;
+ else { // error, skip header
+ m_controlCounter = 100;
+ }
+ }
+ if (m_codec == CODEC_AAC) {
+ // stream only, no header
+ m_audioDataSize = m_audioFileSize;
+ m_controlCounter = 100;
+ }
+ if (m_codec == CODEC_FLAC) {
+ int res = read_FLAC_Header(InBuff.getReadPtr(), bytes);
+ if (res >= 0)
+ bytesReaded = res;
+ else { // error
+ stopSong();
+ }
+ }
+ if (m_codec == CODEC_OPUS) { m_controlCounter = 100; }
+ if (m_codec == CODEC_VORBIS) { m_controlCounter = 100; }
+ if (m_codec == CODEC_OGG) { m_controlCounter = 100; }
+ if (!isRunning()) {
+ AUDIO_LOG_ERROR("Processing stopped due to invalid audio header");
+ return 0;
+ }
+ return bytesReaded;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::read_WAV_Header(uint8_t* data, size_t len) {
+
+ if (m_controlCounter == 0) {
+ m_rwh.cs = 0;
+ m_rwh.bts = 0;
+ m_controlCounter++;
+ if ((*data != 'R') || (*(data + 1) != 'I') || (*(data + 2) != 'F') || (*(data + 3) != 'F')) {
+ AUDIO_LOG_ERROR("file has no RIFF tag");
+ m_rwh.headerSize = 0;
+ return -1; // false;
+ } else {
+ m_rwh.headerSize = 4;
+ return 4; // ok
+ }
+ }
+
+ if (m_controlCounter == 1) {
+ m_controlCounter++;
+ m_rwh.cs = (uint32_t)(*data + (*(data + 1) << 8) + (*(data + 2) << 16) + (*(data + 3) << 24) - 8);
+ m_rwh.headerSize += 4;
+ return 4; // ok
+ }
+
+ if (m_controlCounter == 2) {
+ m_controlCounter++;
+ if ((*data != 'W') || (*(data + 1) != 'A') || (*(data + 2) != 'V') || (*(data + 3) != 'E')) {
+ AUDIO_LOG_ERROR("format tag is not WAVE");
+ return -1; // false;
+ } else {
+ m_rwh.headerSize += 4;
+ return 4;
+ }
+ }
+
+ if (m_controlCounter == 3) {
+ if ((*data == 'f') && (*(data + 1) == 'm') && (*(data + 2) == 't')) {
+ m_controlCounter++;
+ m_rwh.headerSize += 4;
+ return 4;
+ } else {
+ m_rwh.headerSize += 4;
+ return 4;
+ }
+ }
+
+ if (m_controlCounter == 4) {
+ m_controlCounter++;
+ m_rwh.cs = (uint32_t)(*data + (*(data + 1) << 8));
+ if (m_rwh.cs > 40) return -1; // false, something going wrong
+ m_rwh.bts = m_rwh.cs - 16; // bytes to skip if fmt chunk is >16
+ m_rwh.headerSize += 4;
+ return 4;
+ }
+
+ if (m_controlCounter == 5) {
+ m_controlCounter++;
+ uint16_t fc = (uint16_t)(*(data + 0) + (*(data + 1) << 8)); // Format code
+ uint16_t ch = (uint16_t)(*(data + 2) + (*(data + 3) << 8)); // Number of interleaved channels
+ uint32_t sr = (uint32_t)(*(data + 4) + (*(data + 5) << 8) + (*(data + 6) << 16) + (*(data + 7) << 24)); // Samplerate
+ uint32_t dr = (uint32_t)(*(data + 8) + (*(data + 9) << 8) + (*(data + 10) << 16) + (*(data + 11) << 24)); // Datarate
+ uint16_t dbs = (uint16_t)(*(data + 12) + (*(data + 13) << 8)); // Data block size
+ uint16_t bps = (uint16_t)(*(data + 14) + (*(data + 15) << 8)); // Bits per sample
+
+ info(*this, evt_info, "FormatCode: {}", fc);
+ // info(*this, evt_info, "Channel: {}", nic);
+ // info(*this, evt_info, "SampleRate (Hz): {}", sr);
+ info(*this, evt_info, "DataRate: {}", dr);
+ info(*this, evt_info, "DataBlockSize: {}", dbs);
+ info(*this, evt_info, "BitsPerSample: {}", bps);
+
+ if ((bps != 8) && (bps != 16) && (bps != 24) && (bps != 32)) {
+ info(*this, evt_info, "BitsPerSample is {}, must be 8, 16, 24 or 32", bps);
+ stopSong();
+ return -1;
+ }
+ if ((ch != 1) && (ch != 2)) {
+ info(*this, evt_info, "num channels is {}, must be 1 or 2", ch);
+ stopSong();
+ return -1;
+ }
+ if (fc != 1) {
+ AUDIO_LOG_ERROR("format code is not 1 (PCM)");
+ stopSong();
+ return -1; // false;
+ }
+ m_decoder->setRawBlockParams(ch, sr, bps, 0, 0);
+ m_nominal_bitrate = ch * sr * bps;
+ m_rwh.headerSize += 16;
+ return 16; // ok
+ }
+
+ if (m_controlCounter == 6) {
+ m_controlCounter++;
+ m_rwh.headerSize += m_rwh.bts;
+ return m_rwh.bts; // skip to data
+ }
+
+ if (m_controlCounter == 7) {
+ if ((*(data + 0) == 'd') && (*(data + 1) == 'a') && (*(data + 2) == 't') && (*(data + 3) == 'a')) {
+ m_controlCounter++;
+ // vTaskDelay(30);
+ m_rwh.headerSize += 4;
+ return 4;
+ } else {
+ m_rwh.headerSize++;
+ return 1;
+ }
+ }
+
+ if (m_controlCounter == 8) {
+ m_controlCounter++;
+ size_t cs = *(data + 0) + (*(data + 1) << 8) + (*(data + 2) << 16) + (*(data + 3) << 24); // read chunkSize
+ m_rwh.headerSize += 4;
+ if (cs) {
+ m_audioDataSize = cs - 44;
+ } else { // sometimes there is nothing here
+ m_audioDataSize = m_audioFileSize - m_rwh.headerSize;
+ }
+
+ m_audioFileDuration = m_audioDataSize / (getSampleRate() * getChannels());
+ if (getBitsPerSample() == 16) m_audioFileDuration /= 2;
+ info(*this, evt_info, "Duration (s): {}", m_audioFileDuration);
+ return 4;
+ }
+ m_controlCounter = 100; // header succesfully read
+ m_audioDataStart = m_rwh.headerSize;
+ info(*this, evt_info, "Audio-Data-Start: {}", m_audioDataStart);
+ info(*this, evt_info, "Audio-Length: {}", m_audioDataSize);
+ return 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::read_FLAC_Header(uint8_t* data, size_t len) {
+
+ if (m_rflh.retvalue && m_controlCounter != 0) {
+ if (m_rflh.retvalue > len) { // if returnvalue > bufferfillsize
+ if (len > InBuff.getMaxBlockSize()) len = InBuff.getMaxBlockSize();
+ m_rflh.retvalue -= len; // and wait for more bufferdata
+ return len;
+ } else {
+ size_t tmp = m_rflh.retvalue;
+ m_rflh.retvalue = 0;
+ return tmp;
+ }
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_BEGIN) { // init
+ m_rflh.reset();
+ m_controlCounter = FLAC_MAGIC;
+ m_rflh.picVec.clear();
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_MAGIC) { /* check MAGIC STRING */
+ if (specialIndexOf(data, "OggS", 10) == 0) { // is ogg
+ m_rflh.headerSize = 0;
+ m_rflh.retvalue = 0;
+ m_controlCounter = FLAC_OKAY;
+ return 0;
+ }
+ if (specialIndexOf(data, "fLaC", 10) != 0) {
+ if (specialIndexOf(data, "ID3", 10) == 0) { // has ID3 before fLaC
+ // Synchsafe-Size
+ uint32_t size = (data[6] << 21) | (data[7] << 14) | (data[8] << 7) | data[9];
+ size += 10; // header + body
+ m_rflh.retvalue = size; // skip ID3 header + body
+ return 0;
+ }
+ AUDIO_LOG_ERROR("Magic String 'fLaC' not found in header");
+ stopSong();
+ return -1;
+ }
+
+ m_controlCounter = FLAC_MBH; // METADATA_BLOCK_HEADER
+ m_rflh.headerSize = 4;
+ m_rflh.retvalue = 4;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_MBH) { /* METADATA_BLOCK_HEADER */
+ uint8_t blockType = *data;
+
+ if (!m_rflh.f_lastMetaBlock) {
+ if ((blockType & 127) == 0) m_controlCounter = FLAC_SINFO;
+ if ((blockType & 127) == 1) m_controlCounter = FLAC_PADDING;
+ if ((blockType & 127) == 2) m_controlCounter = FLAC_APP;
+ if ((blockType & 127) == 3) m_controlCounter = FLAC_SEEK;
+ if ((blockType & 127) == 4) m_controlCounter = FLAC_VORBIS;
+ if ((blockType & 127) == 5) m_controlCounter = FLAC_CUESHEET;
+ if ((blockType & 127) == 6) m_controlCounter = FLAC_PICTURE;
+ if ((blockType & 128)) { m_rflh.f_lastMetaBlock = true; }
+ m_rflh.headerSize += 1;
+ m_rflh.retvalue = 1;
+ return 0;
+ }
+
+ m_controlCounter = FLAC_OKAY;
+
+ m_audioDataStart = m_rflh.headerSize;
+ m_audioDataSize = m_audioFileSize - m_audioDataStart;
+ m_decoder->setRawBlockParams(m_rflh.numChannels, m_rflh.sampleRate, m_rflh.bitsPerSample, m_rflh.totalSamplesInStream, (uint32_t)m_audioDataSize);
+ if (m_rflh.picLen) {
+ size_t pos = m_audioDataStart;
+ info(*this, evt_image, m_rflh.picVec);
+ }
+
+ info(*this, evt_info, "Audio-Data-Start: {}", m_audioDataStart);
+ info(*this, evt_info, "Audio-Length: {}", m_audioDataSize);
+ if (m_rflh.duration) {
+ m_rflh.nominalBitrate = (m_audioDataSize * 8) / m_rflh.duration;
+ m_nominal_bitrate = m_rflh.nominalBitrate;
+ m_audioFileDuration = m_rflh.duration;
+ info(*this, evt_info, "Duration (s): {}", m_rflh.duration);
+ }
+ m_rflh.retvalue = 0;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_SINFO) { /* Stream info block */
+ size_t l = bigEndian(data, 3);
+ vTaskDelay(2);
+ m_rflh.maxBlockSize = bigEndian(data + 5, 2);
+ info(*this, evt_info, "FLAC maxBlockSize: {}", m_rflh.maxBlockSize);
+ vTaskDelay(2);
+ m_rflh.maxFrameSize = bigEndian(data + 10, 3);
+ if (m_rflh.maxFrameSize) {
+ info(*this, evt_info, "FLAC maxFrameSize: {}", m_rflh.maxFrameSize);
+ } else {
+ info(*this, evt_info, "FLAC maxFrameSize: N/A");
+ }
+ if (m_rflh.maxFrameSize > InBuff.getMaxBlockSize()) {
+ AUDIO_LOG_ERROR("FLAC maxFrameSize too large!");
+ stopSong();
+ return -1;
+ }
+ vTaskDelay(2);
+ uint32_t nextval = bigEndian(data + 13, 3);
+ m_rflh.sampleRate = nextval >> 4;
+ info(*this, evt_info, "FLAC sampleRate (Hz): {}", m_rflh.sampleRate);
+ vTaskDelay(2);
+ m_rflh.numChannels = ((nextval & 0x06) >> 1) + 1;
+ info(*this, evt_info, "FLAC numChannels: {}", m_rflh.numChannels);
+ vTaskDelay(2);
+ uint8_t bps = (nextval & 0x01) << 4;
+ bps += (*(data + 16) >> 4) + 1;
+ m_rflh.bitsPerSample = bps;
+ if ((bps != 8) && (bps != 16) && (bps != 24)) {
+ AUDIO_LOG_ERROR("bits per sample must be 8, 16 or 24, is {}", bps);
+ stopSong();
+ return -1;
+ }
+ info(*this, evt_info, "FLAC bitsPerSample: {}", m_rflh.bitsPerSample);
+ m_rflh.totalSamplesInStream = bigEndian(data + 17, 4);
+ if (m_rflh.totalSamplesInStream) {
+ info(*this, evt_info, "total samples in stream: {}", m_rflh.totalSamplesInStream);
+ } else {
+ info(*this, evt_info, "total samples in stream: N/A");
+ }
+ if (bps != 0 && m_rflh.totalSamplesInStream && m_rflh.sampleRate) { m_rflh.duration = m_rflh.totalSamplesInStream / m_rflh.sampleRate; }
+ m_controlCounter = FLAC_MBH; // METADATA_BLOCK_HEADER
+ m_rflh.retvalue = l + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_PADDING) { /* PADDING */
+ size_t l = bigEndian(data, 3);
+ m_controlCounter = FLAC_MBH;
+ m_rflh.retvalue = l + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_APP) { /* APPLICATION */
+ size_t l = bigEndian(data, 3);
+ m_controlCounter = FLAC_MBH;
+ m_rflh.retvalue = l + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_SEEK) { /* SEEKTABLE */
+ size_t l = bigEndian(data, 3);
+ m_controlCounter = FLAC_MBH;
+ m_rflh.retvalue = l + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_VORBIS) { /* VORBIS COMMENT */ // field names
+
+ auto parse_flac_timestamp = [&](const char* s) {
+ if (!s || *s != '[') return -1;
+ int mm = 0, ss = 0, hh = 0;
+ if (sscanf(s, "[%d:%d.%d]", &mm, &ss, &hh) == 3) { return mm * 60000 + ss * 1000 + hh * 10; }
+ return -1;
+ };
+
+ ps_ptr vendorString;
+ ps_ptr commentString;
+ ps_ptr lyricsBuffer;
+ size_t vendorLength = bigEndian(data, 3);
+ size_t idx = 0;
+ data += 3;
+ idx += 3;
+ size_t vendorStringLength = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
+ if (vendorStringLength) {
+ data += 4;
+ idx += 4;
+ }
+ if (vendorStringLength > 495) vendorStringLength = 495; // guard
+ vendorString.assign((const char*)data, vendorStringLength);
+ vendorString.insert("VENDOR_STRING: ", 0);
+ info(*this, evt_id3data, "{}", vendorString.get());
+ data += vendorStringLength;
+ idx += vendorStringLength;
+ size_t commentListLength = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
+ data += 4;
+ idx += 4;
+
+ for (int i = 0; i < commentListLength; i++) {
+ (void)i;
+ size_t commentLength = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
+ data += 4;
+ idx += 4;
+ if (commentLength) { // guard
+ commentString.assign((const char*)data, commentLength);
+ if (commentString.starts_with("LYRICS="))
+ lyricsBuffer.clone_from(commentString);
+ else
+ info(*this, evt_id3data, "{}", commentString.get());
+ }
+ data += commentLength;
+ idx += commentLength;
+ if (idx > vendorLength + 3) { AUDIO_LOG_ERROR("VORBIS COMMENT section is too long"); }
+ }
+ ps_ptr tmp;
+ ps_ptr timestamp;
+ timestamp.alloc(12, "timestamp");
+ if (lyricsBuffer.valid()) {
+ lyricsBuffer.remove_prefix("LYRICS=");
+ idx = 0;
+ while (idx < lyricsBuffer.size()) {
+ int pos = lyricsBuffer.index_of('\n', idx);
+ if (pos == -1) break;
+ int len = pos - idx + 1;
+ tmp.copy_from(lyricsBuffer.get() + idx, len);
+ idx += len;
+ if (tmp.ends_with("\n")) tmp.truncate_at('\n');
+ // tmp content e.g.: [00:27.07]Jetzt kommt die Zeit
+ // [00:28.84]Auf die ihr euch freut
+ timestamp.copy_from(tmp.get(), 10);
+
+ int ms = parse_flac_timestamp(timestamp.c_get());
+
+ // timestamp.remove_chars("[]:.");
+ // timestamp.append("0"); // to ms 0028840
+ tmp.remove_before(10, true);
+ if (tmp.strlen() > 0) {
+ m_syltLines.push_back(std::move(tmp));
+ m_syltTimeStamp.push_back(ms);
+ }
+ }
+ info(*this, evt_info, "audiofile contains synchronized lyrics");
+ // for(int i = 0; i < m_syltLines.size(); i++){
+ // info(*this, evt_lyrics, "{:07} ms, {}", m_syltTimeStamp[i], m_syltLines[i].c_get());
+ // }
+ }
+ m_controlCounter = FLAC_MBH;
+ m_rflh.retvalue = vendorLength + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_CUESHEET) { /* CUESHEET */
+ size_t l = bigEndian(data, 3);
+ m_controlCounter = FLAC_MBH;
+ m_rflh.retvalue = l + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == FLAC_PICTURE) { /* PICTURE */
+ m_rflh.picLen = bigEndian(data, 3);
+ m_rflh.picPos = m_rflh.headerSize + 3;
+ m_rflh.picVec.push_back(m_rflh.picPos);
+ m_rflh.picVec.push_back(m_rflh.picLen);
+ AUDIO_LOG_DEBUG("FLAC PICTURE, pos {}, size {}", m_rflh.picPos, m_rflh.picLen);
+ m_controlCounter = FLAC_MBH;
+ m_rflh.retvalue = m_rflh.picLen + 3;
+ m_rflh.headerSize += m_rflh.retvalue;
+ return 0;
+ }
+ return 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+
+int Audio::read_ID3_Header(uint8_t* data, size_t len) {
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_BEGIN) { /* read ID3 tag and ID3 header size */
+ m_controlCounter = MP3_ID3HEADER;
+ m_ID3Hdr.reset(); // reset all
+ m_ID3Hdr.iBuffSize = 4096;
+ m_ID3Hdr.iBuff.alloc(m_ID3Hdr.iBuffSize + 10, "m_ID3Hdr.iBuff");
+ memset(m_ID3Hdr.tag, 0, sizeof(m_ID3Hdr.tag));
+ return 0;
+ }
+ if (m_controlCounter == MP3_NEXTID3) {
+ m_controlCounter = MP3_ID3HEADER;
+ // reset specific
+ m_ID3Hdr.id3Size = 0;
+ // m_ID3Hdr.totalId3Size = 0; // if we have more header, id3_1_size + id3_2_size + ....
+ m_ID3Hdr.remainingHeaderBytes = 0;
+ m_ID3Hdr.v22_tag_length = 0;
+ m_ID3Hdr.ID3version = 0;
+ m_ID3Hdr.ehsz = 0;
+ m_ID3Hdr.framesize = 0;
+ m_ID3Hdr.compressed = false;
+ m_ID3Hdr.SYLT.size = 0;
+ m_ID3Hdr.SYLT.pos = 0;
+ m_ID3Hdr.iBuffSize = 4096;
+ m_ID3Hdr.iBuff.alloc(m_ID3Hdr.iBuffSize + 10, "m_ID3Hdr.iBuff");
+ memset(m_ID3Hdr.tag, 0, sizeof(m_ID3Hdr.tag));
+ return 0;
+ }
+
+ if (m_controlCounter == MP3_ID3HEADER) {
+ m_controlCounter = MP3_EXTHEADER;
+ int retval = 0;
+ if (!m_f_m3u8data) info(*this, evt_info, "File-Size: {}", m_audioFileSize);
+
+ m_ID3Hdr.remainingHeaderBytes = 0;
+ m_ID3Hdr.ehsz = 0;
+ if (specialIndexOf(data, "ID3", 4) != 0) { // ID3 not found
+ if (!m_f_m3u8data) { info(*this, evt_info, "file has no ID3 tag, skip metadata"); }
+ m_audioDataSize = m_audioFileSize;
+ // if(!m_f_m3u8data) info(*this, evt_info, "Audio-Length: {}", m_audioDataSize);
+ m_controlCounter = MP3_XING; // have xing?
+ return 0; // error, no ID3 signature found
+ }
+ m_ID3Hdr.ID3version = *(data + 3);
+ switch (m_ID3Hdr.ID3version) {
+ case 2:
+ m_f_unsync = (*(data + 5) & 0x80);
+ m_f_exthdr = false;
+ break;
+ case 3:
+ case 4:
+ m_f_unsync = (*(data + 5) & 0x80); // bit7
+ m_f_exthdr = (*(data + 5) & 0x40); // bit6 extended header
+ break;
+ };
+ m_ID3Hdr.id3Size = bigEndian(data + 6, 4, 7); // ID3v2 size 4 * %0xxxxxxx (shift left seven times!!)
+ m_ID3Hdr.id3Size += 10;
+
+ retval = 10;
+
+ // Every read from now may be unsync'd
+ if (!m_f_m3u8data) info(*this, evt_info, "ID3 framesSize: {}", m_ID3Hdr.id3Size);
+ if (!m_f_m3u8data) info(*this, evt_info, "ID3 version: 2.{}", m_ID3Hdr.ID3version);
+
+ if (m_ID3Hdr.ID3version == 2) { m_controlCounter = MP3_ID3V22; }
+ m_ID3Hdr.remainingHeaderBytes = m_ID3Hdr.id3Size;
+ m_ID3Size = m_ID3Hdr.id3Size;
+ m_ID3Hdr.remainingHeaderBytes -= 10;
+
+ if (m_f_exthdr) {
+ m_ID3Hdr.ehsz = bigEndian(data + 10, 4, 7); // syncSave
+ info(*this, evt_info, "ID3 extended header, size: {}", m_ID3Hdr.ehsz);
+ m_ID3Hdr.ehsz -= 4;
+ m_ID3Hdr.remainingHeaderBytes -= 4;
+ m_ID3Hdr.id3Size += 4;
+ retval += 4;
+ }
+ return retval;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_EXTHEADER) { // skip extended header if exists
+ if (m_ID3Hdr.ehsz > len) {
+ m_ID3Hdr.ehsz -= len;
+ m_ID3Hdr.remainingHeaderBytes -= len;
+ return len;
+ } // Throw it away
+ else {
+ m_controlCounter = MP3_ID3FRAME;
+ m_ID3Hdr.remainingHeaderBytes -= m_ID3Hdr.ehsz;
+ return m_ID3Hdr.ehsz;
+ } // Throw it away
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_ID3FRAME) { // read a ID3 frame, get the tag
+ if (m_ID3Hdr.remainingHeaderBytes == 0) {
+ m_controlCounter = MP3_XING;
+ return 0;
+ }
+ m_controlCounter = MP3_FRAMESIZE;
+ m_ID3Hdr.frameid[0] = *(data + 0);
+ m_ID3Hdr.frameid[1] = *(data + 1);
+ m_ID3Hdr.frameid[2] = *(data + 2);
+ m_ID3Hdr.frameid[3] = *(data + 3);
+ m_ID3Hdr.frameid[4] = 0;
+ for (uint8_t i = 0; i < 4; i++) m_ID3Hdr.tag[i] = m_ID3Hdr.frameid[i]; // tag = frameid
+ if (m_ID3Hdr.frameid[0] == 0 && m_ID3Hdr.frameid[1] == 0 && m_ID3Hdr.frameid[2] == 0 && m_ID3Hdr.frameid[3] == 0) {
+ // We're in padding
+ m_controlCounter = MP3_LASTFRAMES; // all ID3 metadata processed
+ }
+ m_ID3Hdr.remainingHeaderBytes -= 4;
+ return 4;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_FRAMESIZE) { // get the frame size
+ m_controlCounter = MP3_TAG;
+
+ if (m_ID3Hdr.ID3version == 4) {
+ m_ID3Hdr.framesize = bigEndian(data, 4, 7); // << 7
+ } else {
+ m_ID3Hdr.framesize = bigEndian(data, 4); // << 8
+ }
+
+ uint8_t frameFlag_0 = *(data + 4);
+ uint8_t frameFlag_1 = *(data + 5);
+ (void)frameFlag_0;
+ m_ID3Hdr.compressed = frameFlag_1 & 0x80; // Frame is compressed using [#ZLIB zlib] with 4 bytes for 'decompressed
+ uint32_t decompsize = 0;
+ if (m_ID3Hdr.compressed) {
+ // AUDIO_LOG_INFO("iscompressed");
+ decompsize = bigEndian(data + 6, 4);
+ (void)decompsize;
+ // AUDIO_LOG_INFO("decompsize={}", decompsize);
+ m_ID3Hdr.remainingHeaderBytes -= 6 + 4;
+ return 6 + 4;
+ }
+ m_ID3Hdr.remainingHeaderBytes -= 6;
+ return 6;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_SKIP) { // If the frame is larger than m_ID3Hdr.framesize, skip the rest
+ if (m_ID3Hdr.framesize > len) {
+ m_ID3Hdr.framesize -= len;
+ m_ID3Hdr.remainingHeaderBytes -= len;
+ return len;
+ } else {
+ m_controlCounter = MP3_ID3FRAME; // check next frame
+ m_ID3Hdr.remainingHeaderBytes -= m_ID3Hdr.framesize;
+ return m_ID3Hdr.framesize;
+ }
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_TAG) { // Read the value
+ m_controlCounter = MP3_SKIP; // only read 256 bytes
+
+ uint8_t textEncodingByte = *(data + 0); // ID3v2 Text-Encoding-Byte
+ // $00 – ISO-8859-1 (LATIN-1, Identical to ASCII for values smaller than 0x80).
+ // $01 – UCS-2 encoded Unicode with BOM (Byte Order Mark), in ID3v2.2 and ID3v2.3.
+ // $02 – UTF-16BE encoded Unicode without BOM (Byte Order Mark) , in ID3v2.4.
+ // $03 – UTF-8 encoded Unicode, in ID3v2.4.
+
+ if (startsWith(m_ID3Hdr.tag, "APIC")) { // a image embedded in file, passing it to external function
+ m_ID3Hdr.APIC_vec.push_back(m_ID3Hdr.totalId3Size + m_ID3Hdr.id3Size - m_ID3Hdr.remainingHeaderBytes);
+ m_ID3Hdr.APIC_vec.push_back(m_ID3Hdr.framesize);
+ AUDIO_LOG_DEBUG("APIC_pos {}, APIC_size {}", m_ID3Hdr.APIC_vec[2 * m_ID3Hdr.numID3Header], m_ID3Hdr.APIC_vec[2 * m_ID3Hdr.numID3Header + 1]);
+ return 0;
+ }
+
+ if (startsWith(m_ID3Hdr.tag, "SYLT") || startsWith(m_ID3Hdr.tag, "USLT")) { // any lyrics embedded in file, passing it to external function
+ m_controlCounter = MP3_SYLT;
+ return 0;
+ }
+
+ if ( // proprietary not standard information
+ startsWith(m_ID3Hdr.tag, "PRIV")) {
+ ; // AUDIO_LOG_ERROR("PRIV");
+ return 0;
+ }
+ if (startsWith(m_ID3Hdr.tag, "TSIZ") || startsWith(m_ID3Hdr.tag, "XTRA")) { // tag with invalid length, skip everything and find the first sync word
+ m_ID3Hdr.framesize = 0;
+ size_t tmp = m_ID3Hdr.remainingHeaderBytes;
+ m_ID3Hdr.remainingHeaderBytes = 0;
+ return tmp;
+ }
+
+ if (m_ID3Hdr.framesize == 0) return 0;
+
+ ps_ptr tmp;
+ tmp.set_name("tmp");
+ ps_ptr content_descriptor;
+ content_descriptor.set_name("content_descriptor");
+ size_t fs = m_ID3Hdr.framesize; // fs = size of the frame data field as read from header
+ size_t bytesToCopy = fs;
+ size_t textDataLength = 0;
+ uint16_t idx = 0;
+
+ if (bytesToCopy >= m_ID3Hdr.iBuffSize) { bytesToCopy = m_ID3Hdr.iBuffSize - 1; } // make sure a zero terminator fits
+ if (bytesToCopy > 0) { textDataLength = bytesToCopy - 1; } // Only if there are data that we can shorten
+ for (int i = 0; i < textDataLength; i++) {
+ m_ID3Hdr.iBuff[i] = *(data + i + 1); // Skipped the first byte (Encoding)
+ }
+
+ if (textEncodingByte == 1 || textEncodingByte == 2) { // is UTF-16LE or UTF-16BE
+ m_ID3Hdr.iBuff[textDataLength] = 0; // UTF-16: set double zero terminator
+ m_ID3Hdr.iBuff[textDataLength + 1] = 0; // second '\0' for UTF-16
+ } else {
+ m_ID3Hdr.iBuff[textDataLength] = 0; // only one '\0' for ISO-8859-1 or UTF-8
+ }
+
+ m_ID3Hdr.framesize -= fs;
+
+ uint16_t dataLength = fs - 1;
+ bool isBigEndian = (textEncodingByte == 2);
+
+ char encodingTab[4][12] = {"ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-8"};
+
+ if (startsWith(m_ID3Hdr.tag, "COMM")) { // language code
+ m_ID3Hdr.lang[0] = m_ID3Hdr.iBuff[0];
+ m_ID3Hdr.lang[1] = m_ID3Hdr.iBuff[1];
+ m_ID3Hdr.lang[2] = m_ID3Hdr.iBuff[2];
+ m_ID3Hdr.lang[3] = '\0';
+
+ idx = 4;
+ if (textEncodingByte == 1 || textEncodingByte == 2) idx++;
+
+ uint16_t cd_len = 0;
+ if (textEncodingByte == 0)
+ cd_len = 1 + content_descriptor.copy_from_iso8859_1((const uint8_t*)(m_ID3Hdr.iBuff.get() + idx)); // iso8859_1
+ else if (textEncodingByte == 3)
+ cd_len = 1 + content_descriptor.copy_from((const char*)(m_ID3Hdr.iBuff.get() + idx)); // utf-8
+ else
+ cd_len = 2 + content_descriptor.copy_from_utf16((const uint8_t*)(m_ID3Hdr.iBuff.get() + idx), isBigEndian); // utf-16
+ if (cd_len > 2) info(*this, evt_info, "Comment: text encoding; {}, language {}, content_descriptor: {}", encodingTab[textEncodingByte], m_ID3Hdr.lang, content_descriptor.c_get());
+
+ idx += cd_len;
+ // AUDIO_LOG_INFO("Tag: {}, Length: {}, Format: {}", m_ID3Hdr.tag, textDataLength, encodingTab[textEncodingByte]);
+ } else {
+ if (textEncodingByte == 0) {
+ tmp.copy_from_iso8859_1((const uint8_t*)m_ID3Hdr.iBuff.get() + idx);
+ } // ISO-8859-1
+ else if (textEncodingByte == 1 || textEncodingByte == 2) {
+ tmp.copy_from_utf16((const uint8_t*)m_ID3Hdr.iBuff.get() + idx, isBigEndian);
+ } // UTF-16LE oder UTF-16BE
+ else if (textEncodingByte == 3) {
+ tmp.copy_from(m_ID3Hdr.iBuff.get() + idx);
+ } // UTF-8 copy directly because no conversion is necessary
+ }
+ showID3Tag(m_ID3Hdr.tag, tmp.c_get());
+ m_ID3Hdr.remainingHeaderBytes -= fs;
+ return fs;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_SYLT) { // SYLT
+ m_controlCounter = MP3_SKIP;
+ if (m_dataMode == AUDIO_LOCALFILE || (m_streamType == ST_WEBFILE)) {
+ ps_ptr tmp;
+ ps_ptr content_descriptor;
+ ps_ptr syltBuff;
+ bool isBigEndian = true;
+ size_t len = 0;
+ int idx = 0;
+ m_ID3Hdr.SYLT.pos = m_ID3Hdr.id3Size - m_ID3Hdr.remainingHeaderBytes;
+ m_ID3Hdr.SYLT.size = m_ID3Hdr.framesize;
+ if (m_ID3Hdr.SYLT.size < len) return 0;
+ syltBuff.copy_from((const char*)data, m_ID3Hdr.SYLT.size);
+ m_ID3Hdr.SYLT.text_encoding = syltBuff[0]; // 0=ISO-8859-1, 1=UTF-16, 2=UTF-16BE, 3=UTF-8
+ if (m_ID3Hdr.SYLT.text_encoding == 1) isBigEndian = false;
+ if (m_ID3Hdr.SYLT.text_encoding > 3) {
+ AUDIO_LOG_ERROR("unknown text encoding: {}", m_ID3Hdr.SYLT.text_encoding);
+ m_ID3Hdr.SYLT.text_encoding = 0;
+ }
+ char encodingTab[4][12] = {"ISO-8859-1", "UTF-16", "UTF-16BE", "UTF-8"};
+ memcpy(m_ID3Hdr.SYLT.lang, syltBuff.get() + 1, 3);
+ m_ID3Hdr.SYLT.lang[3] = '\0';
+ info(*this, evt_info, "Lyrics: text_encoding: {}, language: {}, size {}", encodingTab[m_ID3Hdr.SYLT.text_encoding], m_ID3Hdr.SYLT.lang, m_ID3Hdr.SYLT.size);
+ m_ID3Hdr.SYLT.time_stamp_format = syltBuff[4];
+ m_ID3Hdr.SYLT.content_type = syltBuff[5];
+ idx = 6;
+ uint16_t cd_len = 0;
+ if (m_ID3Hdr.SYLT.text_encoding == 0)
+ cd_len = 1 + content_descriptor.copy_from_iso8859_1((const uint8_t*)(syltBuff.get() + idx)); // iso8859_1
+ else if (m_ID3Hdr.SYLT.text_encoding == 3)
+ cd_len = 1 + content_descriptor.copy_from((const char*)(syltBuff.get() + idx)); // utf-8
+ else
+ cd_len = 2 + content_descriptor.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian); // utf-16
+ if (cd_len > 2) info(*this, evt_info, "Lyrics: content_descriptor: {}", content_descriptor.c_get());
+
+ idx += cd_len;
+ while (idx < m_ID3Hdr.SYLT.size) {
+ if (m_ID3Hdr.SYLT.text_encoding == 0)
+ idx += 1 + tmp.copy_from_iso8859_1((const uint8_t*)syltBuff.get() + idx); // ISO8859_1
+ else if (m_ID3Hdr.SYLT.text_encoding == 3)
+ idx += 1 + tmp.copy_from((const char*)syltBuff.get() + idx); // UTF-8
+ else
+ idx += 2 + tmp.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian); // UTF-16LE, UTF-16BE
+
+ if (tmp.starts_with("\n")) tmp.remove_before(1);
+ m_syltLines.push_back(std::move(tmp));
+ if (idx + 4 > m_ID3Hdr.SYLT.size) break; // no more 4 bytes?
+ uint32_t timestamp = bigEndian((uint8_t*)syltBuff.get() + idx, 4);
+ m_syltTimeStamp.push_back(timestamp);
+ idx += 4;
+ }
+ info(*this, evt_info, "audiofile contains synchronized lyrics");
+ // for(int i = 0; i < m_syltLines.size(); i++){
+ // info(*this, evt_lyrics, "{:07} ms, {}", m_syltTimeStamp[i], m_syltLines[i].c_get());
+ // }
+ }
+ return 0;
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ // --- section V2.2 only , higher Vers above ----
+ // see https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.2.html
+ if (m_controlCounter == MP3_ID3V22) { // frames in V2.2, 3bytes identifier, 3bytes size descriptor
+
+ if (m_ID3Hdr.v22_tag_length > 0) {
+ if (m_ID3Hdr.v22_tag_length > len) {
+ m_ID3Hdr.v22_tag_length -= len;
+ return len;
+ } // Throw it away
+ else {
+ uint32_t t = m_ID3Hdr.v22_tag_length;
+ m_ID3Hdr.v22_tag_length = 0;
+ return t;
+ } // Throw it away
+ }
+
+ if (m_ID3Hdr.remainingHeaderBytes == 0) {
+ m_controlCounter = MP3_LASTFRAMES;
+ return 0;
+ }
+
+ ps_ptr tag;
+ tag.set_name("tag");
+ ps_ptr value;
+ value.set_name("value");
+ if (data[0] == 0) { // we are in padding
+ m_controlCounter = MP3_LASTFRAMES;
+ uint16_t padding = m_ID3Hdr.remainingHeaderBytes;
+ m_ID3Hdr.remainingHeaderBytes = 0;
+ return padding;
+ }
+ tag.copy_from((const char*)data, 3);
+ m_ID3Hdr.v22_tag_length = bigEndian(data + 3, 3) + 6;
+ value.copy_from((const char*)data + 7, min(m_ID3Hdr.v22_tag_length - 7, (size_t)1024));
+
+ if (tag.starts_with("PIC")) { // image embedded in header
+ size_t pic_len = bigEndian(data + 3, 3);
+ uint32_t pic_start = m_ID3Hdr.totalId3Size + m_ID3Hdr.id3Size - m_ID3Hdr.remainingHeaderBytes;
+
+ m_ID3Hdr.APIC_vec.push_back(pic_start + 6);
+ m_ID3Hdr.APIC_vec.push_back(pic_len);
+ } else if (startsWith(m_ID3Hdr.tag, "SLT")) { // lyrics embedded in header
+ if (m_dataMode == AUDIO_LOCALFILE) {
+
+ ps_ptr tmp;
+ ps_ptr content_descriptor;
+ ps_ptr syltBuff;
+ bool isBigEndian = true;
+ size_t len = 0;
+ int idx = 0;
+
+ m_ID3Hdr.SYLT.pos = m_ID3Hdr.id3Size - m_ID3Hdr.remainingHeaderBytes;
+ m_ID3Hdr.SYLT.size = m_ID3Hdr.v22_tag_length;
+ if (m_ID3Hdr.SYLT.size < len) return 0;
+ syltBuff.copy_from((const char*)data, m_ID3Hdr.SYLT.size);
+ m_ID3Hdr.SYLT.text_encoding = syltBuff[0];
+ memcpy(m_ID3Hdr.SYLT.lang, syltBuff.get() + 1, 3);
+ m_ID3Hdr.SYLT.lang[3] = '\0';
+ info(*this, evt_info, "Lyrics: text_encoding: {}, language: {}, size {}",
+ m_ID3Hdr.SYLT.text_encoding == 0 ? "ASCII"
+ : m_ID3Hdr.SYLT.text_encoding == 3 ? "UTF-8"
+ : "?",
+ m_ID3Hdr.SYLT.lang, m_ID3Hdr.SYLT.size);
+ m_ID3Hdr.SYLT.time_stamp_format = syltBuff[4];
+ m_ID3Hdr.SYLT.content_type = syltBuff[5];
+
+ idx = 6;
+
+ if (m_ID3Hdr.SYLT.text_encoding == 0 || m_ID3Hdr.SYLT.text_encoding == 3) { // utf-8
+ len = content_descriptor.copy_from((const char*)(syltBuff.get() + idx));
+ } else { // utf-16
+ len = content_descriptor.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian);
+ }
+ if (len > 2) info(*this, evt_info, "Lyrics: content_descriptor: {}", content_descriptor.c_get());
+ idx += len;
+
+ while (idx < m_ID3Hdr.SYLT.size) {
+ // UTF-16LE, UTF-16BE
+ if (m_ID3Hdr.SYLT.text_encoding == 1 || m_ID3Hdr.SYLT.text_encoding == 2) {
+ idx += tmp.copy_from_utf16((const uint8_t*)(syltBuff.get() + idx), isBigEndian);
+ } else {
+ // ISO-8859-1 / UTF-8
+ idx += tmp.copy_from((const char*)syltBuff.get() + idx);
+ }
+ if (tmp.starts_with("\n")) tmp.remove_before(1);
+ m_syltLines.push_back(std::move(tmp));
+
+ if (idx + 4 > m_ID3Hdr.SYLT.size) break; // no more 4 bytes?
+
+ uint32_t timestamp = bigEndian((uint8_t*)syltBuff.get() + idx, 4);
+ m_syltTimeStamp.push_back(timestamp);
+
+ idx += 4;
+ }
+ info(*this, evt_info, "audiofile contains synchronized lyrics");
+ // for(int i = 0; i < m_syltLines.size(); i++){
+ // info(*this, evt_lyrics, "{:07} ms, {}", m_syltTimeStamp[i], m_syltLines[i].c_get());
+ // }
+ }
+ } else {
+ showID3Tag(tag.c_get(), value.c_get());
+ }
+
+ m_ID3Hdr.remainingHeaderBytes -= m_ID3Hdr.v22_tag_length;
+
+ return 0;
+ }
+ // -- end section V2.2 -----------
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_LASTFRAMES) { // skip all ID3 metadata (mostly spaces)
+ if (m_ID3Hdr.remainingHeaderBytes > len) {
+ m_ID3Hdr.remainingHeaderBytes -= len;
+ return len;
+ } // Throw it away
+ else {
+ m_controlCounter = MP3_XING;
+ return m_ID3Hdr.remainingHeaderBytes;
+ } // Throw it away
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == MP3_XING) { // exist another ID3tag?
+
+ static const int samplerate_table[4][3] = {
+ {11025, 12000, 8000}, // MPEG 2.5
+ {0, 0, 0}, // reserved
+ {22050, 24000, 16000}, // MPEG 2
+ {44100, 48000, 32000} // MPEG 1
+ };
+
+ static const int samples_per_frame[4][4] = {// layer: 0 1 2 3
+ /* V2.5 */ {0, 576, 1152, 384},
+ /* res. */ {0, 0, 0, 0},
+ /* V2 */ {0, 576, 1152, 384},
+ /* V1 */ {0, 1152, 1152, 384}};
+
+ m_audioDataStart += m_ID3Hdr.id3Size;
+ m_ID3Hdr.totalId3Size += m_ID3Hdr.id3Size;
+ m_ID3Hdr.id3Size = 0;
+
+ if ((*(data + 0) == 'I') && (*(data + 1) == 'D') && (*(data + 2) == '3')) {
+ m_controlCounter = MP3_NEXTID3;
+ m_ID3Hdr.numID3Header++;
+ return 0;
+ } else {
+ // padding after ID3 body?
+ uint32_t padding_counter = 0;
+ while (data[padding_counter] == 0) {
+ padding_counter++;
+ m_audioDataStart++;
+ m_audioDataSize--;
+ }
+ if (padding_counter) return padding_counter;
+
+ m_controlCounter = MP3_OKAY; // 100 -> ok
+ m_audioDataSize = m_audioFileSize - m_audioDataStart;
+ if (!m_f_m3u8data) info(*this, evt_info, "Audio-Data-Start: {}", m_audioDataStart);
+ if (!m_f_m3u8data) info(*this, evt_info, "Audio-Length: {}", m_audioDataSize);
+
+ uint32_t hdr = bigEndian(data, 4);
+ if ((hdr & 0xFFE00000) != 0xFFE00000) AUDIO_LOG_ERROR("Syncword not found"); // check sync
+ int versionID = (hdr >> 19) & 0x3; // 0=MPEG2.5, 2=MPEG2, 3=MPEG1
+ int layerIndex = (hdr >> 17) & 0x3; // 1=Layer3
+ int bitrateIdx = (hdr >> 12) & 0xF;
+ int samplerateIdx = (hdr >> 10) & 0x3;
+ int mode = (hdr >> 6) & 0x3; // 0=stereo,3=mono
+ int samplerate = samplerate_table[versionID][samplerateIdx];
+ int spf = samples_per_frame[versionID][layerIndex];
+
+ // Xing or Info Header present?
+ int8_t mp3_xing = specialIndexOf(data, "Xing", 50);
+ int8_t mp3_info = specialIndexOf(data, "Info", 50);
+
+ uint8_t xingPos = 0;
+ if (mp3_xing > 0) xingPos = mp3_xing;
+ if (mp3_info > 0) xingPos = mp3_info;
+
+ if (xingPos > 0 && layerIndex == 1) { // layer III only
+ uint32_t frames = bigEndian(data + xingPos + 8, 4);
+ AUDIO_LOG_DEBUG("frames {}", frames);
+ uint32_t bytes = bigEndian(data + xingPos + 12, 4);
+ AUDIO_LOG_DEBUG("bytes {}", bytes);
+ uint32_t duration = frames * spf / samplerate;
+ info(*this, evt_info, "Duration (s): {}", duration);
+ m_audioFileDuration = duration;
+ uint32_t bitrate = bytes * 8 / duration;
+ info(*this, evt_info, "Bitrate (b/s): {}", bitrate);
+ m_nominal_bitrate = bitrate;
+ }
+
+ if (m_ID3Hdr.APIC_vec.size()) { // if we have a APIC
+ info(*this, evt_image, m_ID3Hdr.APIC_vec);
+ m_ID3Hdr.APIC_vec.clear();
+ AUDIO_LOG_DEBUG("m_ID3Hdr.totalId3Size {}, m_audioDataStart {}", m_ID3Hdr.totalId3Size, m_audioDataStart);
+ if (m_ID3Hdr.totalId3Size != m_audioDataStart) audioFileSeek(m_audioDataStart);
+ }
+ m_ID3Hdr.numID3Header = 0;
+ m_ID3Hdr.totalId3Size = 0;
+ m_ID3Hdr.iBuff.reset();
+ return 0;
+ }
+ }
+ return 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::read_M4A_Header(uint8_t* data, size_t len) {
+ bool atom_struct = false;
+ ps_ptr atom_name;
+ ps_ptr atom_size;
+ uint32_t idx = 0;
+
+ /*
+ ftyp
+ | - moov -> trak -> ... -> mp4a contains raw block parameters
+ | L... -> ilst contains artist, composer ....
+ free (optional) // jump to another atoms at the end of mdat
+ |
+ mdat contains the audio data */
+
+ if (m_m4aHdr.retvalue) {
+ if (m_m4aHdr.retvalue > UINT16_MAX) {
+ int res = audioFileSeek(m_m4aHdr.headerSize);
+ if (res >= 0) {
+ AUDIO_LOG_INFO("skip {} bytes", m_m4aHdr.retvalue);
+ InBuff.reset();
+ m_m4aHdr.retvalue = 0;
+ return 0;
+ }
+ }
+ if (m_m4aHdr.retvalue > len) {
+ m_m4aHdr.retvalue -= len;
+ m_m4aHdr.cnt += len;
+ return len;
+ } else {
+ size_t tmp = m_m4aHdr.retvalue;
+ m_m4aHdr.retvalue = 0;
+ m_m4aHdr.cnt += tmp;
+ return tmp;
+ }
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_BEGIN) { // init
+ memset(&m_m4aHdr, 0, sizeof(audiolib::m4aHdr_t));
+
+ atom_size.big_endian(data, 4);
+ atom_name.copy_from((const char*)data + 4, 4);
+
+ if (atom_name.equals("ftyp")) {
+ if (atom_struct) {
+ AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, (uint32_t)atom_size.to_uint32(16), m_m4aHdr.headerSize + (size_t)atom_size.to_uint32(16));
+ m_m4aHdr.sizeof_ftyp = atom_size.to_uint32(16);
+ }
+ m_controlCounter = M4A_FTYP;
+ } else {
+ AUDIO_LOG_ERROR("begin: ftyp not found");
+ stopSong();
+ return -1;
+ }
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_FTYP) { /* check_m4a_file */
+
+ int m4a = specialIndexOf(data, "M4A ", 20);
+ int isom = specialIndexOf(data, "isom", 20);
+ int mp42 = specialIndexOf(data, "mp42", 20);
+
+ if ((m4a != 8) && (isom != 8) && (mp42 != 8)) {
+ AUDIO_LOG_ERROR("subtype 'MA4 ', 'isom' or 'mp42' expected, but found '{} '", (data + 8));
+ stopSong();
+ return -1;
+ }
+
+ m_m4aHdr.retvalue += m_m4aHdr.sizeof_ftyp;
+ m_m4aHdr.headerSize += m_m4aHdr.sizeof_ftyp;
+ m_controlCounter = M4A_CHK;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_CHK) { /* check Tag */
+ atom_size.big_endian(data, 4);
+ atom_name.copy_from((const char*)data + 4, 4);
+
+ if (atom_name.equals("moov")) {
+ if (!m_m4aHdr.mdat_seen) m_m4aHdr.progressive = true; // moov before mdat
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_moov = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_MOOV;
+ return 0;
+ }
+
+ if (atom_name.equals("mdat")) {
+ m_m4aHdr.mdat_seen = true;
+ if (!m_m4aHdr.progressive) {
+ m_m4aHdr.mdat_startPos = m_m4aHdr.headerSize + 8;
+ m_m4aHdr.sizeof_mdat = atom_size.to_uint32(16);
+ if (atom_struct) {
+ AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.mdat_startPos, m_m4aHdr.sizeof_mdat, m_m4aHdr.mdat_startPos + m_m4aHdr.sizeof_mdat);
+ }
+ info(*this, evt_info, "Audiofile is non progressive");
+ m_m4aHdr.retvalue += m_m4aHdr.sizeof_mdat;
+ m_m4aHdr.headerSize += m_m4aHdr.sizeof_mdat;
+ return 0;
+ stopSong();
+ return -1;
+ }
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_mdat = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_MDAT;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ m_m4aHdr.sizeof_moov -= atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_MOOV) { // moov
+ if (atom_struct) { AUDIO_LOG_WARN("moov size remain {}", m_m4aHdr.sizeof_moov); }
+ if (m_m4aHdr.sizeof_moov == 0) {
+ if (m_m4aHdr.progressive) {
+ m_controlCounter = M4A_CHK;
+ AUDIO_LOG_DEBUG("goto CHK");
+ } else {
+ audioFileSeek(0); // non progressive, back to mdat
+ InBuff.reset();
+ m_m4aHdr.headerSize = m_m4aHdr.mdat_startPos;
+ m_m4aHdr.retvalue = m_m4aHdr.mdat_startPos; // set InBuff.getReadPtr to audiodatastart
+ if (atom_struct) AUDIO_LOG_WARN("goto MDAT at {}", m_m4aHdr.mdat_startPos + 8);
+ m_controlCounter = M4A_MDAT;
+ }
+ return 0;
+ } // go back
+
+ atom_name.copy_from((const char*)data + 4, 4);
+ atom_size.big_endian(data, 4);
+ m_m4aHdr.sizeof_moov -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("trak")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_trak = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_TRAK;
+ return 0;
+ }
+
+ if (atom_name.equals("udta")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_udta = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_UDTA;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_TRAK) { // trak
+ if (atom_struct) { AUDIO_LOG_WARN("trak size remain {}", m_m4aHdr.sizeof_trak); }
+ if (m_m4aHdr.sizeof_trak == 0) {
+ m_controlCounter = M4A_MOOV;
+ return 0;
+ } // go back
+
+ atom_name.copy_from((const char*)data + 4, 4);
+ atom_size.big_endian(data, 4);
+ m_m4aHdr.sizeof_trak -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("mdia")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_mdia = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_MDIA;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_MDIA) { // mdia
+ if (atom_struct) { AUDIO_LOG_WARN("mdia size remain {}", m_m4aHdr.sizeof_mdia); }
+ if (m_m4aHdr.sizeof_mdia == 0) {
+ m_controlCounter = M4A_TRAK;
+ return 0;
+ } // go back
+
+ atom_name.copy_from((const char*)data + 4, 4);
+ atom_size.big_endian(data, 4);
+ m_m4aHdr.sizeof_mdia -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("mdhd")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_mdhd = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_MDHD;
+ return 0;
+ }
+
+ if (atom_name.equals("minf")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_minf = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_MINF;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_MINF) { // minf
+ if (atom_struct) { AUDIO_LOG_WARN("minf size remain {}", m_m4aHdr.sizeof_minf); }
+ if (m_m4aHdr.sizeof_minf == 0) {
+ m_controlCounter = M4A_MDIA;
+ return 0;
+ } // go back
+
+ atom_name.copy_from((const char*)data + 4, 4);
+ atom_size.big_endian(data, 4);
+ m_m4aHdr.sizeof_minf -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("stbl")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_stbl = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_STBL;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_MDHD) { // mdhd
+ if (atom_struct) { AUDIO_LOG_WARN("mdhd size remain {}", m_m4aHdr.sizeof_mdhd); }
+ if (m_m4aHdr.sizeof_mdhd == 0) {
+ m_controlCounter = M4A_MDIA;
+ return 0;
+ } // go back
+
+ ps_ptr mdhd_buffer; // read ESDS content in a buffer
+ mdhd_buffer.copy_from((char*)data, m_m4aHdr.sizeof_mdhd); // read MDHD content (without header)
+ // mdhd_buffer.hex_dump(m_m4aHdr.sizeof_mdhd);
+ /* version; 1 Byte offset 0 0 -> 32 bit, 1 -> 64 bit
+ flags[3]; 3 Bytes offset 1
+ creation_time; 4 Bytes offset 4
+ modification_time; 4 Bytes offset 8
+ timescale; 4 Bytes offset 12
+ duration; 4 Bytes offset 16
+ language; 2 Bytes offset 20
+ pre_defined; 2 Bytes offset 22 */
+ m_m4aHdr.timescale = bigEndian((uint8_t*)mdhd_buffer.get() + 12, 4);
+ m_m4aHdr.duration = bigEndian((uint8_t*)mdhd_buffer.get() + 16, 4);
+ if (m_m4aHdr.timescale) {
+ m_audioFileDuration = m_m4aHdr.duration / m_m4aHdr.timescale;
+ info(*this, evt_info, "Duration (s): {}", m_audioFileDuration);
+ }
+ m_m4aHdr.retvalue += m_m4aHdr.sizeof_mdhd;
+ m_m4aHdr.headerSize += m_m4aHdr.sizeof_mdhd;
+ m_controlCounter = M4A_MDIA;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_STBL) { // minf
+ if (atom_struct) { AUDIO_LOG_WARN("stbl size remain {}", m_m4aHdr.sizeof_stbl); }
+ if (m_m4aHdr.sizeof_stbl == 0) {
+ m_controlCounter = M4A_MINF;
+ return 0;
+ } // go back
+
+ atom_size.big_endian(data, 4);
+ atom_name.copy_from((const char*)data + 4, 4);
+ m_m4aHdr.sizeof_stbl -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("stsd")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ uint8_t header_size = 8;
+ m_m4aHdr.sizeof_stsd = atom_size.to_uint32(16) - 8 - header_size;
+ m_m4aHdr.retvalue += 8 + header_size;
+ m_m4aHdr.headerSize += 8 + header_size;
+ m_controlCounter = M4A_STSD;
+ return 0;
+ }
+
+ if (atom_name.equals("stsz")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ uint8_t header_size = 8;
+ m_m4aHdr.sizeof_stsz = atom_size.to_uint32(16) - 8 - header_size;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_STSZ;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_STSD) {
+ if (atom_struct) { AUDIO_LOG_WARN("stsd size remain {}", m_m4aHdr.sizeof_stsd); }
+ if (m_m4aHdr.sizeof_stsd == 0) {
+ m_controlCounter = M4A_STBL;
+ return 0;
+ } // go back
+
+ atom_size.big_endian(data, 4);
+ atom_name.copy_from((const char*)data + 4, 4);
+ m_m4aHdr.sizeof_stsd -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("mp4a")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_mp4a = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_MP4A;
+ return 0;
+ }
+
+ if (atom_name.equals("alac")) {
+ AUDIO_LOG_ERROR("Apple loseless audio codec (ALAC) not supported");
+ stopSong();
+ return 0;
+ }
+
+ if (atom_name.equals("ac-3")) {
+ AUDIO_LOG_ERROR("Dolby Digital not supported");
+ stopSong();
+ return 0;
+ }
+
+ if (atom_name.equals("ec-3")) {
+ AUDIO_LOG_ERROR("Dolby Digital Plud not supported");
+ stopSong();
+ return 0;
+ }
+
+ if (atom_name.equals("Opus")) {
+ // todo
+ stopSong();
+ return 0;
+ }
+
+ if (atom_name.equals("mp3 ")) {
+ // todo
+ stopSong();
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_CHPL) {
+ if (atom_struct) { AUDIO_LOG_WARN("chpl size remain {}", m_m4aHdr.sizeof_chpl); }
+ if (m_m4aHdr.sizeof_chpl == 0) {
+ m_controlCounter = M4A_UDTA;
+ return 0;
+ } // go back
+
+ ps_ptr title_name;
+ // data[0] version
+ // data[1] 4 flags
+ uint32_t nr_of_chapters = bigEndian(data + 5, 4); // Number of chapters
+ // data[9] uint64 timestamp
+ uint8_t title_length = data[17];
+ title_name.copy_from((char*)data + 18, title_length);
+
+ info(*this, evt_info, "Number of chapters: {}", nr_of_chapters);
+ info(*this, evt_info, "Chapter name: {}", title_name.c_get());
+
+ m_m4aHdr.retvalue += m_m4aHdr.sizeof_chpl;
+ m_m4aHdr.headerSize += m_m4aHdr.sizeof_chpl;
+ m_m4aHdr.sizeof_chpl = 0; // there are no subatoms
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_MP4A) {
+ if (atom_struct) { AUDIO_LOG_WARN("mp4a size remain {}", m_m4aHdr.sizeof_mp4a); }
+ if (m_m4aHdr.sizeof_mp4a == 0) {
+ m_controlCounter = M4A_STSD;
+ return 0;
+ } // go back
+
+ if (!m_m4aHdr.sample_rate) { // read the header first
+ // data[0]...[5] reserved
+ // data[6] & [7] Data reference buffer_index
+ // data[8]...[15] reserved
+ m_m4aHdr.channel_count = data[16] * 256 + data[17];
+ AUDIO_LOG_DEBUG("channels {}", m_m4aHdr.channel_count);
+ m_m4aHdr.sample_size = data[18] * 256 + data[19];
+ AUDIO_LOG_DEBUG("bit per sample {}", m_m4aHdr.sample_size);
+ // data[20]...data[23] reserved
+ m_m4aHdr.sample_rate = data[24] * 256 + data[25];
+ AUDIO_LOG_DEBUG("sample rate {}", m_m4aHdr.sample_rate);
+ // data[26]...data[27] sample rate fixed point is 0x00, 0x00
+ m_m4aHdr.offset = 28;
+ m_m4aHdr.retvalue += m_m4aHdr.offset;
+ m_m4aHdr.headerSize += m_m4aHdr.offset;
+ m_m4aHdr.sizeof_mp4a -= m_m4aHdr.offset;
+ return 0;
+ }
+ atom_size.big_endian(data, 4);
+ atom_name.copy_from((const char*)data + 4, 4);
+ m_m4aHdr.sizeof_mp4a -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("esds")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_esds = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_ESDS;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_UDTA) { // udta
+ if (atom_struct) { AUDIO_LOG_WARN("udta size remain {}", m_m4aHdr.sizeof_udta); }
+ if (m_m4aHdr.sizeof_udta == 0) {
+ m_controlCounter = M4A_MOOV;
+ return 0;
+ } // go back
+
+ atom_size.big_endian(data, 4);
+ atom_name.copy_from((const char*)data + 4, 4);
+ m_m4aHdr.sizeof_udta -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("meta")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_meta = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_META;
+ return 0;
+ }
+
+ if (atom_name.equals("chpl")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_chpl = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_CHPL;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_META) { // meta
+ if (atom_struct) { AUDIO_LOG_WARN("meta size remain {}", m_m4aHdr.sizeof_meta); }
+ if (m_m4aHdr.sizeof_meta == 0) {
+ m_controlCounter = M4A_UDTA;
+ return 0;
+ } // go back, 4bytes typ + 4 bytes size
+
+ if (!m_m4aHdr.version_flags) {
+ // 0x00, 0x00, 0x00, 0x94, 0x6D, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00,
+ // size | m e t a | flags
+ m_m4aHdr.version_flags = true;
+ m_m4aHdr.sizeof_meta -= 4;
+ m_m4aHdr.retvalue += 4;
+ m_m4aHdr.headerSize += 4;
+ idx += 4;
+ }
+ atom_size.big_endian(data + idx, 4);
+ atom_name.copy_from((const char*)data + 4 + idx, 4);
+ m_m4aHdr.sizeof_meta -= atom_size.to_uint32(16);
+
+ if (atom_name.equals("ilst")) {
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.sizeof_ilst = atom_size.to_uint32(16) - 8;
+ m_m4aHdr.retvalue += 8;
+ m_m4aHdr.headerSize += 8;
+ m_controlCounter = M4A_ILST;
+ return 0;
+ }
+
+ if (atom_struct) { AUDIO_LOG_WARN("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), m_m4aHdr.headerSize, atom_size.to_uint32(16), m_m4aHdr.headerSize + atom_size.to_uint32(16)); }
+ m_m4aHdr.retvalue += atom_size.to_uint32(16);
+ m_m4aHdr.headerSize += atom_size.to_uint32(16);
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_ESDS) { // Elementary Stream Descriptor
+ ps_ptr esds_buffer; // read ESDS content in a buffer
+ esds_buffer.copy_from((char*)data, m_m4aHdr.sizeof_esds); // read ESDS content (without header)
+ // esds_buffer.hex_dump(m_m4aHdr.sizeof_esds);
+
+ // search for decoderConfigDescriptor (tag 0x04)
+ int32_t dec_config_descriptor_offset = esds_buffer.special_index_of("\x04\x80\x80\x80", 4, (uint32_t)m_m4aHdr.sizeof_esds);
+ if (dec_config_descriptor_offset > 0) { // decoderConfigDescriptor found
+ uint8_t dec_config_descriptor_length = ((uint8_t*)esds_buffer.get())[dec_config_descriptor_offset + 4]; // Length after Tag + 3 Extended Length Bytes
+ m_m4aHdr.objectTypeIndicator = ((uint8_t*)esds_buffer.get())[dec_config_descriptor_offset + 5]; // 0x40 (AAC)
+ m_m4aHdr.streamType = ((uint8_t*)esds_buffer.get())[dec_config_descriptor_offset + 6]; // 0x05 (Audio)
+ m_m4aHdr.bufferSizeDB = bigEndian((uint8_t*)esds_buffer.get() + dec_config_descriptor_offset + 7, 3); // 24 bit
+ m_m4aHdr.maxBitrate = bigEndian((uint8_t*)esds_buffer.get() + dec_config_descriptor_offset + 10, 4); // 32 bit
+ m_m4aHdr.nomBitrate = bigEndian((uint8_t*)esds_buffer.get() + dec_config_descriptor_offset + 14, 4); // 32 bit
+ // info(*this, evt_info, "maxBitrate {}, nominalBitrate {}", m_m4aHdr.maxBitrate, m_m4aHdr.nomBitrate);
+ }
+
+ // search for decoderspecificinfo (tag 0x05)
+ int32_t dec_specific_offset = esds_buffer.special_index_of("\x05\x80\x80\x80", 4, m_m4aHdr.sizeof_esds);
+ if (dec_specific_offset >= 0) { // decoderSpecificInfo found
+ uint8_t dec_specific_length = ((uint8_t*)esds_buffer.get())[dec_specific_offset + 4]; // Länge nach Tag + 3 Extended Length Bytes
+ AUDIO_LOG_DEBUG("DecoderSpecificInfo found at offset {}, length: {}", dec_specific_offset, dec_specific_length);
+
+ // extract decoderSpecificInfo-data
+ ps_ptr dec_specific_data;
+ dec_specific_data.alloc(dec_specific_length, "dec_specific_data");
+ memcpy(dec_specific_data.get(), (uint8_t*)esds_buffer.get() + dec_specific_offset + 5, dec_specific_length);
+ m_m4aHdr.aac_profile = (uint8_t)dec_specific_data.get()[0] >> 3;
+ if (m_m4aHdr.aac_profile > 4) {
+ AUDIO_LOG_ERROR("unsupported AAC Profile {}", m_m4aHdr.aac_profile);
+ stopSong();
+ return 0;
+ }
+ AUDIO_LOG_DEBUG("DecoderSpecificInfo data: 0x{:02X} 0x{:02X}", (uint8_t)dec_specific_data.get()[0], (uint8_t)dec_specific_data.get()[1]);
+ char profile[5][33] = {"unknown", "AAC Main", "AAC LC (Low Complexity)", "AAC SSR (Scalable Sample Rate)", "AAC LTP (Long Term Prediction)"};
+ info(*this, evt_info, "AAC Profile: {}", profile[m_m4aHdr.aac_profile]);
+ info(*this, evt_info, "AAC Channels; {}", m_m4aHdr.channel_count);
+ info(*this, evt_info, "AAC Sample Rate: {}", m_m4aHdr.sample_rate);
+ info(*this, evt_info, "AAC Bits Per Sample: {}", m_m4aHdr.sample_size);
+ m_M4A_chConfig = m_m4aHdr.channel_count;
+ m_M4A_sampleRate = m_m4aHdr.sample_rate;
+ m_M4A_objectType = m_m4aHdr.aac_profile;
+ } else {
+ AUDIO_LOG_WARN("No DecoderSpecificInfo found in esds");
+ }
+ m_m4aHdr.retvalue += m_m4aHdr.sizeof_esds;
+ m_m4aHdr.headerSize += m_m4aHdr.sizeof_esds;
+ m_controlCounter = M4A_MP4A;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_ILST) {
+
+ auto parse_m4a_timestamp = [&](const char* s) {
+ if (!s || *s != '[') return -1;
+ int mm = 0, ss = 0, hh = 0;
+ if (sscanf(s, "[%d:%d.%d]", &mm, &ss, &hh) == 3) { return mm * 60000 + ss * 1000 + hh * 10; }
+ return -1;
+ };
+
+ struct TagInfo { // Definition of the Taginfo structure for iTunes-style metadata
+ const uint8_t tag[4];
+ const char* name;
+ const char* descr;
+ };
+ const TagInfo tags[] = {
+ // List of all usual tags
+ {{0xA9, 0x6E, 0x61, 0x6D}, "©nam", "Title"}, {{0xA9, 0x41, 0x52, 0x54}, "©ART", "Artist"}, {{0xA9, 0x61, 0x72, 0x74}, "©art", "Artist"},
+ {{0xA9, 0x61, 0x6C, 0x62}, "©alb", "Album"}, {{0xA9, 0x74, 0x6F, 0x6F}, "©too", "Encoder"}, {{0xA9, 0x63, 0x6D, 0x74}, "©cmt", "Comment"},
+ {{0xA9, 0x77, 0x72, 0x74}, "©wrt", "Composer"}, {{0x74, 0x6D, 0x70, 0x6F}, "tmpo", "Tempo (BPM)"}, {{0x74, 0x72, 0x6B, 0x6E}, "trkn", "Track-Number"},
+ {{0xA9, 0x64, 0x61, 0x79}, "©day", "Year"}, {{0x63, 0x70, 0x69, 0x6C}, "cpil", "Compilation-Flag"}, {{0x61, 0x41, 0x52, 0x54}, "aART", "Album Artist"},
+ {{0xA9, 0x67, 0x65, 0x6E}, "©gen", "Genre"}, {{0x63, 0x6F, 0x76, 0x72}, "covr", "Cover Art"}, {{0x64, 0x69, 0x73, 0x6B}, "disk", "Disk-Nummer"},
+ {{0xA9, 0x6C, 0x79, 0x72}, "©lyr", "Songtext"}, {{0xA9, 0x70, 0x72, 0x74}, "cprt", "Copyright"}, {{0x67, 0x6E, 0x72, 0x65}, "gnre", "Genre-ID"},
+ {{0x72, 0x74, 0x6E, 0x67}, "rtng", "Evaluation"}, {{0x70, 0x67, 0x61, 0x70}, "pgap", "Gapless Playback"},
+ };
+ const size_t tags_count = sizeof(tags) / sizeof(tags[0]); // Number of tags
+
+ ps_ptr id3tag;
+ ps_ptr lyricsBuffer;
+ uint32_t pos = m_m4aHdr.headerSize;
+ uint32_t consumed = 0;
+ while (true) {
+ atom_size.big_endian(data + consumed, 4);
+ atom_name.copy_from((const char*)data + 4 + consumed, 4);
+ uint32_t as = atom_size.to_uint32(16);
+ AUDIO_LOG_DEBUG("atom {} @ {}, size: {}, ends @ {}", atom_name.c_get(), pos, as, pos + as);
+ m_m4aHdr.ilst_pos += as;
+ pos += as;
+ consumed += as;
+
+ // 0x00, 0x00, 0x00, 0x1C, 0xA9, 0x64, 0x61, 0x79, 0x00, 0x00, 0x00, 0x14, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x32, 0x30, 0x32, 0x32,
+ // sub atom length | © d a y | sub sub atom length | d a t a | data type 1->UTF-8 | reserved | 2 0 2 2
+
+ ps_ptr sa; // sub atom
+ sa.copy_from((const char*)data + consumed - as, min(as, (uint32_t)1024));
+
+ char san[5] = {0}; // sub atom name
+ char ssan[5] = {0}; // sub sub atom name (should be 'data')
+ strncpy(san, &sa[4], 4);
+ strncpy(ssan, &sa[12], 4);
+ uint32_t ssal = bigEndian((uint8_t*)&sa[8], 4); // sub sub atom length
+ uint32_t dty = bigEndian((uint8_t*)&sa[16], 4); // data type 1-UTF8
+ if (strncmp(ssan, "data", 4) == 0) {
+ for (int i = 0; i < tags_count; i++) {
+ if (memcmp(san, tags[i].tag, 4) == 0) {
+ id3tag.reset();
+ if (dty == 0 && strlen(&sa[24]) > 0) {
+ if (strcmp(san, "covr") == 0) {
+ m_m4aHdr.picLen = as - 24;
+ m_m4aHdr.picPos = pos + 24 - as;
+ AUDIO_LOG_DEBUG("cover jpeg start: {}, len {}", m_m4aHdr.picPos, m_m4aHdr.picLen);
+ }
+ id3tag.assignf("{}: {}", tags[i].descr, &sa[24]); // binary
+ } else if (dty == 1 && strlen(&sa[24]) > 0)
+ id3tag.assignf("{}: {}", tags[i].descr, &sa[24]); // UTF-8 Text
+ else if (dty == 0x0D) {
+ m_m4aHdr.picLen = as - 24;
+ m_m4aHdr.picPos = pos + 24 - as;
+ AUDIO_LOG_DEBUG("cover jpeg start: {}, len {}", m_m4aHdr.picPos, m_m4aHdr.picLen);
+ } // jpeg
+ else if (dty == 0x0E) {
+ m_m4aHdr.picLen = as - 24;
+ m_m4aHdr.picPos = pos + 24 - as;
+ AUDIO_LOG_WARN("cover png start: {}, len {}", m_m4aHdr.picPos, m_m4aHdr.picLen);
+ } // png
+ else if (dty == 21) {
+ if (memcmp(tags[i].tag, "cpil", 4) == 0) { break; } // Compilation Flag
+ if (memcmp(tags[i].tag, "pgap", 4) == 0) { break; } // Gapless Playback
+ if (memcmp(tags[i].tag, "rtng", 4) == 0) { break; } // Evaluation
+ }
+ if (id3tag.valid()) { info(*this, evt_id3data, "{}", id3tag.get()); }
+ if (strcmp(tags[i].descr, "Songtext") == 0) { lyricsBuffer.copy_from(&sa[24]); }
+ break;
+ }
+ }
+ } else
+ AUDIO_LOG_DEBUG("{} tag not supported", san);
+
+ if (consumed > len + m_m4aHdr.ilst_already_consumed) { // ILST > len can happen
+ m_m4aHdr.ilst_already_consumed += len;
+ m_m4aHdr.retvalue = consumed;
+ m_m4aHdr.headerSize += consumed;
+ m_controlCounter = M4A_ILST;
+ return 0;
+ }
+
+ if (m_m4aHdr.sizeof_ilst <= m_m4aHdr.ilst_pos) {
+
+ ps_ptr tmp; // last todo if we have lyrics
+ ps_ptr timestamp;
+ timestamp.alloc(12, "timestamp");
+ if (lyricsBuffer.valid()) {
+ lyricsBuffer.remove_prefix("LYRICS=");
+ idx = 0;
+ while (idx < lyricsBuffer.size()) {
+ int pos = lyricsBuffer.index_of('\n', idx);
+ if (pos == -1) break;
+ int len = pos - idx + 1;
+ tmp.copy_from(lyricsBuffer.get() + idx, len);
+ idx += len;
+ if (tmp.ends_with("\n")) tmp.truncate_at('\n');
+ // tmp content e.g.: [00:27.07]Jetzt kommt die Zeit
+ // [00:28.84]Auf die ihr euch freut
+ timestamp.copy_from(tmp.get(), 10);
+
+ int ms = parse_m4a_timestamp(timestamp.c_get());
+
+ // timestamp.remove_chars("[]:.");
+ // timestamp.append("0"); // to ms 0028840
+ tmp.remove_before(10, true);
+ if (tmp.strlen() > 0) {
+ m_syltLines.push_back(std::move(tmp));
+ m_syltTimeStamp.push_back(ms);
+ }
+ }
+ info(*this, evt_info, "audiofile contains synchronized lyrics");
+ // for(int i = 0; i < m_syltLines.size(); i++){
+ // info(*this, evt_lyrics, "{:07} ms, {}", m_syltTimeStamp[i], m_syltLines[i].c_get());
+ // }
+ }
+ m_m4aHdr.retvalue = consumed;
+ m_m4aHdr.headerSize += consumed;
+ m_controlCounter = M4A_META;
+ break;
+ }
+ }
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_controlCounter == M4A_STSZ) {
+ uint8_t version = data[0];
+ (void)version;
+ uint32_t flags = bigEndian(data + 1, 3);
+ (void)flags;
+ uint32_t sample_size = bigEndian(data + 4, 4);
+ (void)sample_size;
+ m_m4aHdr.stsz_num_entries = bigEndian(data + 8, 4);
+ m_m4aHdr.stsz_table_pos = m_m4aHdr.headerSize + 12;
+ m_m4aHdr.retvalue += m_m4aHdr.sizeof_stsz + 8;
+ m_m4aHdr.headerSize += m_m4aHdr.sizeof_stsz + 8;
+ m_controlCounter = M4A_STBL;
+ return 0;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ uint8_t extLen = 0;
+ if (m_controlCounter == M4A_MDAT) { // mdat
+ // ps_ptr hd;
+ // hd.copy_from((const char*)data, 30);
+ // hd.hex_dump(30);
+ m_audioDataSize = m_m4aHdr.sizeof_mdat; // length of this atom
+
+ // Extended Size
+ // 00 00 00 01 6D 64 61 74 00 00 00 00 00 00 16 64
+ // 0001 m d a t 5732
+
+ if (m_audioDataSize == 1) { // Extended Size
+ m_audioDataSize = bigEndian(data + 8, 8);
+ m_audioDataSize -= 16;
+ extLen = 8;
+ } else {
+ m_audioDataSize -= 8;
+ }
+ m_m4aHdr.retvalue = extLen;
+ m_m4aHdr.headerSize += extLen;
+ m_controlCounter = M4A_AMRDY; // last step before starting the audio
+ return 0;
+ }
+
+ if (m_controlCounter == M4A_AMRDY) { // almost ready
+ m_audioDataStart = m_m4aHdr.headerSize;
+ if (m_m4aHdr.picLen) {
+ size_t pos = m_audioFilePosition;
+ std::vector vec;
+ vec.push_back(m_m4aHdr.picPos);
+ vec.push_back(m_m4aHdr.picLen);
+ info(*this, evt_image, vec);
+ vec.clear();
+ audioFileSeek(pos); // the filepointer could have been changed by the user, set it back
+ }
+ m_stsz_numEntries = m_m4aHdr.stsz_num_entries;
+ m_stsz_position = m_m4aHdr.stsz_table_pos;
+ info(*this, evt_info, "Audio-Data-Start: {}", m_audioDataStart);
+ info(*this, evt_info, "Audio-Length: {}", m_audioDataSize);
+ if (m_audioFileDuration) {
+ m_nominal_bitrate = (m_audioDataSize * 8) / m_audioFileDuration;
+ info(*this, evt_info, "Duration (s): {}", m_audioFileDuration);
+ }
+
+ m_controlCounter = M4A_OKAY; // that's all
+ return 0;
+ }
+ // this section should never be reached
+ AUDIO_LOG_ERROR("error");
+ return 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+size_t Audio::process_m3u8_ID3_Header(uint8_t* packet) {
+ uint8_t ID3version;
+ size_t id3Size;
+ bool m_f_unsync = false, m_f_exthdr = false;
+ uint64_t current_timestamp = 0;
+
+ (void)m_f_unsync; // suppress -Wunused-variable
+ (void)current_timestamp; // suppress -Wunused-variable
+
+ if (specialIndexOf(packet, "ID3", 4) != 0) { // ID3 not found
+ // AUDIO_LOG_INFO("m3u8 file has no mp3 tag");
+ return 0; // error, no ID3 signature found
+ }
+ ID3version = *(packet + 3);
+ switch (ID3version) {
+ case 2:
+ m_f_unsync = (*(packet + 5) & 0x80);
+ m_f_exthdr = false;
+ break;
+ case 3:
+ case 4:
+ m_f_unsync = (*(packet + 5) & 0x80); // bit7
+ m_f_exthdr = (*(packet + 5) & 0x40); // bit6 extended header
+ break;
+ };
+ id3Size = bigEndian(&packet[6], 4, 7); // ID3v2 size 4 * %0xxxxxxx (shift left seven times!!)
+ id3Size += 10;
+ // AUDIO_LOG_INFO("ID3 framesSize: {}", id3Size);
+ // AUDIO_LOG_INFO("ID3 version: 2.{}", ID3version);
+
+ if (m_f_exthdr) {
+ AUDIO_LOG_ERROR("ID3 extended header in m3u8 files not supported");
+ return 0;
+ }
+ // AUDIO_LOG_INFO("ID3 normal frames");
+
+ if (specialIndexOf(&packet[10], "PRIV", 5) != 0) { // tag PRIV not found
+ AUDIO_LOG_ERROR("tag PRIV in m3u8 Id3 Header not found");
+ return 0;
+ }
+ // if tag PRIV exists assume content is "com.apple.streaming.transportStreamTimestamp"
+ // a time stamp is expected in the header.
+
+ current_timestamp = (double)bigEndian(&packet[69], 4) / 90000; // seconds
+
+ return id3Size;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::stopSong() {
+ m_f_lockInBuffer = true; // wait for the decoding to finish
+ uint8_t maxWait = 0;
+ uint32_t currTime = getAudioCurrentTime();
+
+ xSemaphoreTake(mutex_audioTaskIsDecoding, 1 * configTICK_RATE_HZ); // wait for audioTask is ready
+ {
+ if (m_f_running) {
+ m_f_running = false;
+ if (m_client->connected()) {
+ if (m_streamType == ST_WEBSTREAM) { info(*this, evt_info, "Closing web stream \"{}\"", m_lastHost.c_get()); }
+ if (m_streamType == ST_WEBFILE) { info(*this, evt_info, "Closing web file \"{}\"", m_lastHost.c_get()); }
+ m_client->stop();
+ }
+ if (m_audiofile) {
+ info(*this, evt_info, "Closing audio file \"{}\"", m_audiofile.name());
+ m_audiofile.close();
+ }
+ }
+ destroy_decoder();
+ m_f_lockInBuffer = false;
+ }
+ xSemaphoreGive(mutex_audioTaskIsDecoding);
+ return currTime;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::pauseResume() {
+ xSemaphoreTake(mutex_audioTask, 0.3 * configTICK_RATE_HZ);
+ bool retVal = false;
+ if (m_dataMode == AUDIO_LOCALFILE || m_streamType == ST_WEBSTREAM || m_streamType == ST_WEBFILE) {
+ m_f_running = !m_f_running;
+ retVal = true;
+ if (!m_f_running) {
+ m_outBuff.clear();
+ memset(m_resamplesBuff.get(), 0, m_resamplesBuffSize * sizeof(int16_t)); // Clear SamplesBuffer
+ m_validSamples = 0;
+ }
+ }
+ xSemaphoreGive(mutex_audioTask);
+ return retVal;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+audiolib::BiquadCoeffs Audio::makeButterworthLPF_Q31(float fs) { // Calculation of the biquad coefficients for the resampler
+
+ float fc = 0.45f * fs;
+ if (fc > 20000.0f) fc = 20000.0f; // absolute upper limit (security)
+ if (fc < 3000.0f) fc = 3000.0f; // absolute lower limit (prevents “thin” sound)
+
+ constexpr float Q = 0.70710678f;
+
+ float w0 = 2.0f * M_PI * fc / fs;
+ float cw = cosf(w0);
+ float sw = sinf(w0);
+ float alpha = sw / (2.0f * Q);
+
+ float b0 = (1.0f - cw) * 0.5f;
+ float b1 = 1.0f - cw;
+ float b2 = (1.0f - cw) * 0.5f;
+ float a0 = 1.0f + alpha;
+ float a1 = -2.0f * cw;
+ float a2 = 1.0f - alpha;
+
+ // normalize
+ b0 /= a0;
+ b1 /= a0;
+ b2 /= a0;
+ a1 /= a0;
+ a2 /= a0;
+
+ // convert to Q31
+ constexpr float Q31 = 2147483648.0f; // 2^31
+
+ audiolib::BiquadCoeffs c;
+ c.b0 = (int32_t)lrintf(b0 * Q31);
+ c.b1 = (int32_t)lrintf(b1 * Q31);
+ c.b2 = (int32_t)lrintf(b2 * Q31);
+ c.a1 = (int32_t)lrintf(a1 * Q31);
+ c.a2 = (int32_t)lrintf(a2 * Q31);
+
+ return c;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::resampleI2Soutput(audiolib::resampler_t& rs, int32_t* input, uint32_t inputSamples, int32_t* output) {
+
+ auto lerp_q32 = [&](int32_t a, int32_t b, uint32_t frac) -> int32_t { return a + (int32_t)(((int64_t)(b - a) * frac) >> 32); };
+ auto biquadProcess = [&](audiolib::Biquad& s, const audiolib::BiquadCoeffs& c, int32_t x) -> int32_t {
+ // Q31 signal, Q31 coeffs → Q62 acc
+ int64_t acc = (int64_t)c.b0 * x + s.z1;
+ s.z1 = (int64_t)c.b1 * x - (int64_t)c.a1 * (acc >> 31) + s.z2;
+ s.z2 = (int64_t)c.b2 * x - (int64_t)c.a2 * (acc >> 31);
+ int64_t y = acc >> 31;
+ if (y > INT32_MAX) return INT32_MAX;
+ if (y < INT32_MIN) return INT32_MIN;
+ return (int32_t)y;
+ };
+
+ uint32_t outFrames = 0;
+ uint32_t i = 0;
+
+ // If we have a "last" from the previous frame, start with that
+ if (rs.hasLast && inputSamples > 0) {
+ int32_t l0 = rs.lastL;
+ int32_t r0 = rs.lastR;
+ int32_t l1 = input[0];
+ int32_t r1 = input[1];
+
+ // Continue processing with the old phase value
+ while ((rs.phase >> 32) == 0) {
+ uint32_t frac = (uint32_t)rs.phase;
+ int32_t l = lerp_q32(l0, l1, frac);
+ int32_t r = lerp_q32(r0, r1, frac);
+
+ l = biquadProcess(rs.lpLeft, rs.g_lpCoeffs, l);
+ r = biquadProcess(rs.lpRight, rs.g_lpCoeffs, r);
+
+ output[outFrames * 2] = l;
+ output[outFrames * 2 + 1] = r;
+ ++outFrames;
+
+ rs.phase += rs.phaseStep;
+ }
+ rs.phase -= (1ULL << 32);
+ // i remains 0, we haven't used input[0] as "l0" yet!
+ }
+
+ // Rest of the frame as usual
+ for (; i + 1 < inputSamples; ++i) {
+ int32_t l0 = input[i * 2];
+ int32_t r0 = input[i * 2 + 1];
+ int32_t l1 = input[(i + 1) * 2];
+ int32_t r1 = input[(i + 1) * 2 + 1];
+
+ while ((rs.phase >> 32) == 0) {
+ uint32_t frac = (uint32_t)rs.phase;
+ int32_t l = lerp_q32(l0, l1, frac);
+ int32_t r = lerp_q32(r0, r1, frac);
+
+ l = biquadProcess(rs.lpLeft, rs.g_lpCoeffs, l);
+ r = biquadProcess(rs.lpRight, rs.g_lpCoeffs, r);
+
+ output[outFrames * 2] = l;
+ output[outFrames * 2 + 1] = r;
+ ++outFrames;
+
+ rs.phase += rs.phaseStep;
+ }
+ rs.phase -= (1ULL << 32);
+ }
+
+ // Save last sample for next frame
+ if (inputSamples > 0) {
+ rs.lastL = input[(inputSamples - 1) * 2];
+ rs.lastR = input[(inputSamples - 1) * 2 + 1];
+ rs.hasLast = true;
+ }
+
+ return outFrames;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void IRAM_ATTR Audio::playChunk() {
+ if (m_validSamples == 0) return; // nothing to do
+
+ bool continueI2S = true;
+ m_plCh.i2s_bytesConsumed = 0;
+ m_plCh.err = ESP_OK;
+
+ constexpr int BYTES_PER_FRAME = 2 * sizeof(int32_t);
+
+ if (m_plCh.count > 0) goto i2swrite; // Not all samples could be written to I2S during the last run
+ audio_process_raw_samples(m_outBuff.get(), m_validSamples);
+ //------------------------------------------------------------------------------------------
+ {
+ const bool applyGain = settings.VOLUME_CONTROL && (m_audio_items.limiter[LEFTCHANNEL] != 1.0f || m_audio_items.limiter[RIGHTCHANNEL] != 1.0f);
+ for (int i = 0; i < m_validSamples; i++) {
+ if (settings.VU_LEVEL) calculateVUlevel(&m_outBuff[i * 2]);
+ if (settings.IIR_FILTER) IIR_filter(&m_outBuff[i * 2]);
+ if (applyGain) Gain(&m_outBuff[i * 2]);
+ }
+ }
+ if (settings.SPECTRUM) processSpectrum();
+ if (m_f_forceMono) stereo2mono(m_outBuff.get(), m_validSamples);
+ //------------------------------------------------------------------------------------------
+ if (m_output_sr && m_output_sr != m_i2s_items.sampleRate) {
+ m_validSamples = resampleI2Soutput(m_resampler, m_outBuff.get(), m_validSamples, m_resamplesBuff.get()); // have new amount of samples
+ audio_process_i2s(m_resamplesBuff.get(), m_validSamples, &continueI2S); // resampled stereo 32bps
+ } else {
+ audio_process_i2s(m_outBuff.get(), (int32_t)m_validSamples, &continueI2S);
+ }
+ //------------------------------------------------------------------------------------------------------
+ if (!continueI2S) {
+ m_validSamples = 0;
+ m_plCh.count = 0;
+ return;
+ }
+ //------------------------------------------------------------------------------------------------------
+
+i2swrite:
+ if (m_output_sr && m_output_sr != m_i2s_items.sampleRate) { // with resampler
+ m_plCh.err = i2s_channel_write(m_i2s_tx_handle, m_resamplesBuff.get() + m_plCh.count, m_validSamples * BYTES_PER_FRAME, &m_plCh.i2s_bytesConsumed, 50);
+ } else { // without resampler
+ m_plCh.err = i2s_channel_write(m_i2s_tx_handle, m_outBuff.get() + m_plCh.count, m_validSamples * BYTES_PER_FRAME, &m_plCh.i2s_bytesConsumed, 20);
+ }
+
+ if (!(m_plCh.err == ESP_OK || m_plCh.err == ESP_ERR_TIMEOUT)) goto exit;
+ m_validSamples -= m_plCh.i2s_bytesConsumed / BYTES_PER_FRAME;
+ m_plCh.count += m_plCh.i2s_bytesConsumed / 2;
+ if (m_validSamples <= 0) {
+ m_validSamples = 0;
+ m_plCh.count = 0;
+ }
+
+ // ---- statistics, bytes written to I2S (every 10s)
+ // static int cnt = 0;
+ // static uint32_t t = millis();
+
+ // if(t + 10000 < millis()){
+ // AUDIO_LOG_INFO("{}", cnt);
+ // cnt = 0;
+ // t = millis();
+ // }
+ // cnt+= i2s_bytesConsumed;
+ //-------------------------------------------
+
+ return;
+exit:
+ if (m_plCh.err == ESP_OK)
+ return;
+ else if (m_plCh.err == ESP_ERR_INVALID_ARG)
+ AUDIO_LOG_ERROR("NULL pointer or this handle is not tx handle");
+ else if (m_plCh.err == ESP_ERR_TIMEOUT)
+ AUDIO_LOG_ERROR("Writing timeout, no writing event received from ISR within ticks_to_wait");
+ else if (m_plCh.err == ESP_ERR_INVALID_STATE)
+ AUDIO_LOG_ERROR("I2S is not ready to write");
+ else
+ AUDIO_LOG_ERROR("i2s err {}", m_plCh.err);
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::loop() {
+ get_info();
+ if (!m_f_running) return;
+
+ if (m_f_firstLoop) {
+ m_f_firstLoop = false;
+ memset(&m_lVar, 0, sizeof(m_lVar));
+ }
+
+ if (m_playlistFormat != FORMAT_M3U8) { // normal process
+ switch (m_dataMode) {
+ case AUDIO_LOCALFILE: processLocalFile(); break;
+ case HTTP_RESPONSE_HEADER:
+ if (!parseHttpResponseHeader()) {
+ if (m_f_timeout && m_lVar.count < 3) {
+ m_f_timeout = false;
+ m_lVar.count++;
+ connecttohost(m_lastHost.get());
+ }
+ } else {
+ m_lVar.count = 0;
+ }
+ break;
+ case AUDIO_PLAYLISTINIT: readPlayListData(); break;
+ case AUDIO_PLAYLISTDATA:
+ if (m_playlistFormat == FORMAT_M3U) httpPrint(parsePlaylist_M3U());
+ if (m_playlistFormat == FORMAT_PLS) httpPrint(parsePlaylist_PLS());
+ if (m_playlistFormat == FORMAT_ASX) httpPrint(parsePlaylist_ASX());
+ break;
+ case AUDIO_DATA:
+ if (m_streamType == ST_WEBSTREAM) processWebStream();
+ if (m_streamType == ST_WEBFILE) processWebFile();
+ break;
+ }
+ } else { // m3u8 datastream only
+ ps_ptr host;
+ switch (m_dataMode) {
+ case HTTP_RESPONSE_HEADER:
+ if (!parseHttpResponseHeader()) {
+ if (m_lVar.count < 3) {
+ m_lVar.count++;
+ connecttohost(m_lastHost.get());
+ } else {
+ stopSong();
+ }
+ } else {
+ m_lVar.count = 0;
+ m_f_firstCall = true; // read ID3 header in processWebStreamHLS/TS()
+ }
+ break;
+ case AUDIO_PLAYLISTINIT:
+ if (readPlayListData())
+ break;
+ else { // readPlayListData == false means connect to m3u8 URL
+ httpPrint(m_m3u8_host.get());
+ m_dataMode = HTTP_RESPONSE_HEADER; // we have a new playlist now
+ break;
+ }
+ case AUDIO_PLAYLISTDATA:
+ host = parsePlaylist_M3U8();
+ if (host.valid()) { // host contains the next playlist URL
+ httpPrint(host.get());
+ m_dataMode = HTTP_RESPONSE_HEADER;
+ } else { // host == NULL means connect to m3u8 URL
+ if (m_lVar.no_host_timer > millis()) {
+ // AUDIO_LOG_DEBUG("wait");
+ break;
+ }
+ if (m_f_stream) m_lVar.no_host_timer = millis() + 10000;
+ httpPrint(m_m3u8_host.get());
+ m_dataMode = HTTP_RESPONSE_HEADER; // we have a new playlist now
+ }
+ break;
+ case AUDIO_DATA:
+ if (m_f_ts) {
+ processWebStreamTS(); // aac, mp3 or aacp with ts packets
+ } else {
+ processWebStreamHLS(); // aac, mp3 or aacp normal stream
+ }
+
+ if (m_f_continue) { // at this point m_f_continue is true, means processWebStream() needs more data
+ m_dataMode = AUDIO_PLAYLISTDATA;
+ m_f_continue = false;
+ }
+ break;
+ }
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::readPlayListData() {
+
+ int32_t chunkLen = 0;
+ uint16_t readedBytes = 0;
+ ps_ptr pl;
+ uint32_t ctl = 0;
+ size_t plSize = 0;
+
+ auto detectTimeout = [&]() -> bool {
+ uint32_t t = millis();
+ while (!m_client->available()) {
+ vTaskDelay(2);
+ if (t + 2000 < millis()) {
+ AUDIO_LOG_WARN("Playlist is incomplete, fetch again");
+ if (m_f_chunked) getChunkSize(0, true);
+ return true;
+ }
+ }
+ return false;
+ };
+
+ if (m_dataMode != AUDIO_PLAYLISTINIT) {
+ AUDIO_LOG_ERROR("wrong datamode {}", dataModeStr[m_dataMode]);
+ goto exit;
+ }
+
+ if (m_f_chunked) {
+ getChunkSize(0, true);
+ chunkLen = getChunkSize(&readedBytes);
+ if (chunkLen <= 0) {
+ AUDIO_LOG_ERROR("chunked datatransfer but chunkLen is invalid");
+ goto exit;
+ }
+ plSize = chunkLen;
+ } else {
+ plSize = m_audioFileSize;
+ }
+
+ if (!plSize) { // maybe playlist without contentLength or chunkSize
+ AUDIO_LOG_ERROR("file size is not given");
+ goto exit;
+ }
+
+ pl.alloc(2048, "pl");
+ // delete all memory in m_playlistContent
+ if (m_playlistFormat == FORMAT_M3U8 && !psramFound()) { AUDIO_LOG_ERROR("m3u8 playlists requires PSRAM enabled!"); }
+ vector_clear_and_shrink(m_playlistContent);
+
+ while (true) { // outer while
+ uint32_t ctime = millis();
+ uint32_t timeout = 2000; // ms
+
+ pl.clear(); // playlistLine
+
+ while (true) { // inner while
+ uint16_t pos = 0;
+ while (true) { // super inner while :-))
+ uint32_t t = millis();
+ if (ctl == plSize) break;
+ if (detectTimeout()) goto exit;
+ pl[pos] = audioFileRead();
+ ctl++;
+ if (pl[pos] == '\n') {
+ pl[pos] = '\0';
+ pos++;
+ break;
+ }
+ if (pl[pos] == '\r') {
+ pl[pos] = '\0';
+ pos++;
+ continue;
+ }
+ pos++;
+ if (pos == 2044) {
+ pos--;
+ continue;
+ }
+ if (ctl == plSize) {
+ pl[pos] = '\0';
+ break;
+ }
+ }
+ if (pos) {
+ pl[pos] = '\0';
+ break;
+ }
+ if (ctl == plSize) break;
+ if (detectTimeout()) goto exit;
+ } // inner while
+ AUDIO_LOG_DEBUG("PL: {}", pl.c_get());
+ if (pl.starts_with_icase("stop();
+ httpPrint(m_m3u8_host.c_get());
+ return true;
+ }
+ }
+ }
+ //--------------------------------------------------------------------------------------------------------------
+
+ // AUDIO_LOG_INFO("current playlist line: {}", pl.get());
+ if (pl.size() > 0) m_playlistContent.emplace_back(pl);
+
+ // termination conditions
+ // 1. The http response header returns a value for contentLength -> read chars until contentLength is reached
+ // 2. no contentLength, but Transfer-Encoding:chunked -> compute chunksize and read until chunksize is reached
+ // 3. no chunksize and no contentlengt, but Connection: close -> read all available chars
+ if (ctl == plSize) {
+ if (m_f_chunked) {
+ chunkLen = getChunkSize(&readedBytes); // expected: "\r\n\0\r\n\r\n" if ready
+ if (chunkLen > 0) {
+ plSize += chunkLen;
+ continue; // next round
+ }
+ }
+ break;
+ }
+ } // outer while
+
+ m_dataMode = AUDIO_PLAYLISTDATA;
+ return true;
+
+exit:
+ vector_clear_and_shrink(m_playlistContent);
+ getChunkSize(0, true);
+ return false;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+const char* Audio::parsePlaylist_M3U() {
+
+ uint8_t lines = m_playlistContent.size();
+ int pos = 0;
+ char* host = nullptr;
+
+ for (int i = 0; i < lines; i++) {
+ // m_playlistContent[i].println();
+ if (m_playlistContent[i].contains("#EXTINF:")) { // Info?
+ pos = m_playlistContent[i].index_of(","); // Comma in this line?
+ if (pos > 0) {
+ // Show artist and title if present in metadata
+ info(*this, evt_id3data, "{}", m_playlistContent[i].get() + pos + 1);
+ }
+ continue;
+ }
+ if (m_playlistContent[i].starts_with("#")) { // Commentline?
+ continue;
+ }
+
+ pos = m_playlistContent[i].index_of("http://:@", 0); // ":@"?? remove that!
+ if (pos >= 0) {
+ AUDIO_LOG_INFO("Entry in playlist found: {}", (m_playlistContent[i].get() + pos + 9));
+ host = m_playlistContent[i].get() + pos + 9;
+ break;
+ }
+ // AUDIO_LOG_INFO("Entry in playlist found: {}", pl);
+ pos = m_playlistContent[i].index_of("http", 0); // Search for "http"
+ if (pos >= 0) { // Does URL contain "http://"?
+ // AUDIO_LOG_ERROR("{} pos={}", m_playlistContent[i], pos);
+ host = m_playlistContent[i].get() + pos; // Yes, set new host
+ break;
+ }
+ }
+ // vector_clear_and_shrink(m_playlistContent);
+ return host;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+const char* Audio::parsePlaylist_PLS() {
+ uint8_t lines = m_playlistContent.size();
+ int pos = 0;
+ char* host = nullptr;
+
+ for (int i = 0; i < lines; i++) {
+ if (i == 0) {
+ if (m_playlistContent[0].strlen() == 0) goto exit; // empty line
+ if (!m_playlistContent[0].starts_with("[playlist]")) { // first entry in valid pls
+ m_dataMode = HTTP_RESPONSE_HEADER; // pls is not valid
+ AUDIO_LOG_INFO("Playlist is not valid, switch to HTTP_RESPONSE_HEADER");
+ goto exit;
+ }
+ continue;
+ }
+ if (m_playlistContent[i].starts_with("File1")) {
+ if (host) continue; // we have already a url
+ pos = m_playlistContent[i].index_of("http", 0); // File1=http://streamplus30.leonex.de:14840/;
+ if (pos >= 0) { // yes, URL contains "http"?
+ host = m_playlistContent[i].get() + pos; // Now we have an URL for a stream in host.
+ }
+ continue;
+ }
+ if (m_playlistContent[i].starts_with("Title1")) { // Title1=Antenne Tirol
+ const char* plsStationName = (m_playlistContent[i].get() + 7);
+ info(*this, evt_name, "{}", plsStationName);
+ continue;
+ }
+ if (m_playlistContent[i].starts_with("Length1")) { continue; }
+ if (m_playlistContent[i].contains("Invalid username")) { // Unable to access account:
+ goto exit; // Invalid username or password
+ }
+ }
+ return host;
+
+exit:
+ m_f_running = false;
+ stopSong();
+ vector_clear_and_shrink(m_playlistContent);
+ m_dataMode = AUDIO_NONE;
+ return nullptr;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+const char* Audio::parsePlaylist_ASX() { // Advanced Stream Redirector
+ uint8_t lines = m_playlistContent.size();
+ bool f_entry = false;
+ int pos = 0;
+ char* host = nullptr;
+
+ for (int i = 0; i < lines; i++) {
+ int p1 = m_playlistContent[i].index_of("<", 0);
+ int p2 = m_playlistContent[i].index_of(">", 1);
+ if (p1 >= 0 && p2 > p1) { // #196 set all between "< ...> to lowercase
+ for (uint8_t j = p1; j < p2; j++) { m_playlistContent[i][j] = toLowerCase(m_playlistContent[i][j]); }
+ }
+ if (m_playlistContent[i].contains("")) f_entry = true; // found entry tag (returns -1 if not found)
+ if (f_entry) {
+ if (m_playlistContent[i].contains("ref href")) { //
+ pos = m_playlistContent[i].index_of("http", 0);
+ if (pos > 0) {
+ host = (m_playlistContent[i].get() + pos); // http://87.98.217.63:24112/stream" />
+ int pos1 = indexOf(host, "\"", 0); // http://87.98.217.63:24112/stream
+ if (pos1 > 0) host[pos1] = '\0'; // Now we have an URL for a stream in host.
+ }
+ }
+ }
+ pos = m_playlistContent[i].index_of("", 0);
+ if (pos >= 0) {
+ char* plsStationName = (m_playlistContent[i].get() + pos + 7); // remove
+ pos = indexOf(plsStationName, "", 0);
+ if (pos >= 0) {
+ *(plsStationName + pos) = 0; // remove
+ }
+ info(*this, evt_name, "{}", plsStationName);
+ }
+
+ if (m_playlistContent[i].starts_with("http") && !f_entry) { // url only in asx
+ host = m_playlistContent[i].get();
+ }
+ }
+ return host;
+}
+
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+uint16_t Audio::accomplish_m3u8_url() {
+
+ for (uint16_t i = 0; i < m_linesWithURL.size(); i++) {
+
+ ps_ptr tmp;
+
+ if (!m_linesWithURL[i].starts_with("http")) { // playlist: http://station.com/aaa/bbb/xxx.m3u8
+ // chunklist: http://station.com/aaa/bbb/ddd.aac
+ // result: http://station.com/aaa/bbb/ddd.aac
+ tmp = m_m3u8_host;
+ if (m_linesWithURL[i][0] != '/') { // playlist: http://station.com/aaa/bbb/xxx.m3u8 // tmp
+ // chunklist: ddd.aac // m_linesWithURL[i]
+ // result: http://station.com/aaa/bbb/ddd.aac // m_linesWithURL[i]
+ int idx = tmp.last_index_of('/');
+ tmp[idx + 1] = '\0';
+ tmp.append(m_linesWithURL[i].get());
+ m_linesWithURL[i].clone_from(tmp);
+ continue;
+ } else { // playlist: http://station.com/aaa/bbb/xxx.m3u8
+ // chunklist: /aaa/bbb/ddd.aac
+ // result: http://station.com/aaa/bbb/ddd.aac
+ int idx = tmp.index_of('/', 8);
+ tmp[idx] = '\0';
+ tmp.append(m_linesWithURL[i].get());
+ m_linesWithURL[i].clone_from(tmp);
+ continue;
+ }
+ } else { /* nothing todo*/
+ continue;
+ }
+ }
+ // for(uint16_t i = 0; i < m_linesWithURL.size(); i++) m_linesWithURL[i].println();
+ return 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+int16_t Audio::prepare_first_m3u8_url(ps_ptr& playlistBuff) {
+ // in m_lineswithURL, searches for the first new URL through the recent URL.
+ // URLs that have already been used are removed from the DEQUE
+ // If all URLs are new, there is nothing to do and 0 is returned.
+ // no new URL is found -1 returned
+ int idx = -1;
+ for (int i = 0; i < m_linesWithURL.size(); i++) {
+ if (playlistBuff.equals(m_linesWithURL[i].get())) idx = i;
+ }
+ if (idx == -1) {
+ AUDIO_LOG_DEBUG("nothing found, all entries are new");
+ return 0;
+ }
+ if (idx == m_linesWithURL.size()) {
+ AUDIO_LOG_DEBUG("only last entry found, nothing is new");
+ return -1;
+ }
+ for (int j = 0; j < idx + 1; j++) { m_linesWithURL.pop_front(); }
+ AUDIO_LOG_DEBUG("{} entries are known and removed", idx + 1);
+ return idx + 1;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+ps_ptr Audio::parsePlaylist_M3U8() {
+ // example: audio chunks
+ // #EXTM3U
+ // #EXT-X-TARGETDURATION:10
+ // #EXT-X-MEDIA-SEQUENCE:163374040
+ // #EXT-X-DISCONTINUITY
+ // #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\""
+ // http://n3fa-e2.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/main/163374038.aac
+ // #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\""
+ // http://n3fa-e2.revma.ihrhls.com/zc7729/63_sdtszizjcjbz02/main/163374039.aac
+
+ // #EXTM3U
+ // #EXT-X-VERSION:3
+ // #EXT-X-MEDIA-SEQUENCE:0
+ // #EXT-X-TARGETDURATION:4
+ // #EXTINF:3.997,90s90s - Rock
+ // #EXT-X-PROGRAM-DATE-TIME:2025-08-17T14:08:21.088877044Z
+ // https://hz71.streamabc.net/hls/d2gu4l4peavc72tgotd0/regc-90s90srock1436287-mp3-192-2191420/0.mp3
+ // #EXTINF:3.997,90s90s - Rock
+ // #EXT-X-PROGRAM-DATE-TIME:2025-08-17T14:08:24.088877044Z
+ // https://hz71.streamabc.net/hls/d2gu4l4peavc72tgotd0/regc-90s90srock1436287-mp3-192-2191420/1.mp3
+ // #EXTINF:3.997,90s90s - Rock
+ // #EXT-X-PROGRAM-DATE-TIME:2025-08-17T14:08:28.088877044Z
+ // https://hz71.streamabc.net/hls/d2gu4l4peavc72tgotd0/regc-90s90srock1436287-mp3-192-2191420/2.mp3
+ // #EXTINF:3.997,90s90s - Rock
+ // #EXT-X-PROGRAM-DATE-TIME:2025-08-17T14:08:32.088877044Z
+ // https://hz71.streamabc.net/hls/d2gu4l4peavc72tgotd0/regc-90s90srock1436287-mp3-192-2191420/3.mp3
+
+ if (!m_lastHost.valid()) {
+ AUDIO_LOG_ERROR("m_lastHost is NULL");
+ return {};
+ } // guard
+
+ uint32_t lines = m_playlistContent.size();
+ bool f_haveRedirection = false;
+
+ if (lines) {
+ bool addNextLine = false;
+ deque_clear_and_shrink(m_linesWithURL);
+ vector_clear_and_shrink(m_linesWithEXTINF);
+ for (uint32_t i = 0; i < lines; i++) {
+ // AUDIO_LOG_INFO("pl{} = {}", i, m_playlistContent[i].get());
+ if (m_playlistContent[i].starts_with("#EXT-X-STREAM-INF:")) { f_haveRedirection = true; /*AUDIO_LOG_ERROR("we have a redirection");*/ }
+ if (addNextLine) {
+ if (m_playlistContent[i].starts_with("#EXT-X-PROGRAM-DATE-TIME:")) continue; // skip this line
+ addNextLine = false;
+ // size_t len = strlen(linesWithSeqNr[idx].get()) + strlen(m_playlistContent[i].get()) + 1;
+ m_linesWithURL.emplace_back(m_playlistContent[i]);
+ }
+ if (m_playlistContent[i].starts_with("#EXTINF:")) {
+ m_linesWithEXTINF.emplace_back(m_playlistContent[i]);
+ addNextLine = true;
+ }
+ }
+ if (!f_haveRedirection) {
+ accomplish_m3u8_url();
+ prepare_first_m3u8_url(m_playlistBuff);
+ vector_clear_and_shrink(m_playlistContent);
+ }
+ }
+
+ for (int i = 0; i < m_linesWithURL.size(); i++) { /*AUDIO_LOG_INFO("{}", m_linesWithURL[i].get())*/
+ ;
+ }
+ for (int i = 0; i < m_linesWithEXTINF.size(); i++) { /*AUDIO_LOG_INFO("{}", m_linesWithEXTINF[i].get());*/
+ showstreamtitle(m_linesWithEXTINF[i].get());
+ }
+
+ if (f_haveRedirection) {
+ m_m3u8_host = m3u8redirection(&m_m3u8Codec);
+ vector_clear_and_shrink(m_playlistContent);
+ return {};
+ }
+
+ if (m_codec == CODEC_NONE) {
+ m_codec = CODEC_AAC;
+ if (m_m3u8Codec == CODEC_MP3) m_codec = CODEC_MP3;
+ } // if we have no redirection
+ //----------------------------------------------------------------------------------------------------------------------------------------------------
+
+ if (m_linesWithURL.size() > 0) {
+ ps_ptr playlistBuff;
+ if (m_linesWithURL[0].valid()) {
+ playlistBuff = m_linesWithURL[0];
+ m_linesWithURL.pop_front();
+ m_linesWithURL.shrink_to_fit();
+ }
+ AUDIO_LOG_DEBUG("now playing {}", playlistBuff.get());
+ if (playlistBuff.ends_with("ts")) m_f_ts = true;
+ if (playlistBuff.contains(".ts?") > 0) m_f_ts = true;
+ m_playlistBuff = playlistBuff;
+ return playlistBuff;
+ }
+ return {};
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+
+ps_ptr Audio::m3u8redirection(uint8_t* codec) {
+ // example: redirection
+ // #EXTM3U
+ // #EXT-X-STREAM-INF:BANDWIDTH=117500,AVERAGE-BANDWIDTH=117000,CODECS="mp4a.40.2"
+ // 112/playlist.m3u8?hlssid=7562d0e101b84aeea0fa35f8b963a174
+ // #EXT-X-STREAM-INF:BANDWIDTH=69500,AVERAGE-BANDWIDTH=69000,CODECS="mp4a.40.5"
+ // 64/playlist.m3u8?hlssid=7562d0e101b84aeea0fa35f8b963a174
+ // #EXT-X-STREAM-INF:BANDWIDTH=37500,AVERAGE-BANDWIDTH=37000,CODECS="mp4a.40.29"
+ // 32/playlist.m3u8?hlssid=7562d0e101b84aeea0fa35f8b963a174
+
+ if (!m_lastHost.valid()) {
+ AUDIO_LOG_ERROR("m_lastHost is empty");
+ return {};
+ } // guard
+ const char codecString[9][11] = {
+ "mp4a.40.34", // mp3 stream
+ "mp4a.40.01", // AAC Main
+ "mp4a.40.2", // MPEG-4 AAC LC
+ "mp4a.40.02", // MPEG-4 AAC LC, leading 0 for Aud-OTI compatibility
+ "mp4a.40.29", // MPEG-4 HE-AAC v2 (AAC LC + SBR + PS)
+ "mp4a.40.42", // xHE-AAC
+ "mp4a.40.5", // MPEG-4 HE-AAC v1 (AAC LC + SBR)
+ "mp4a.40.05", // MPEG-4 HE-AAC v1 (AAC LC + SBR), leading 0 for Aud-OTI compatibility
+ "mp4a.67", // MPEG-2 AAC LC
+ };
+
+ uint16_t choosenLine = 0;
+ uint16_t plcSize = m_playlistContent.size();
+ int8_t cS = 100;
+
+ for (uint16_t i = 0; i < plcSize; i++) { // looking for lowest codeString
+ if (m_playlistContent[i].contains("CODECS=\"mp4a")) {
+ for (uint8_t j = 0; j < 9; j++) {
+ if (m_playlistContent[i].contains(codecString[j])) {
+ if (j < cS) {
+ cS = j;
+ choosenLine = i;
+ }
+ }
+ }
+ }
+ }
+ if (cS == 0)
+ *codec = CODEC_MP3;
+ else if (cS < 100)
+ *codec = CODEC_AAC;
+
+ if (cS == 100) { // "mp4a.xx.xx" not found
+ *codec = CODEC_AAC; // assume AAC
+ for (uint16_t i = 0; i < plcSize; i++) { // we have no codeString, looking for "http"
+ if (m_playlistContent[i].contains("#EXT-X-STREAM-INF")) { choosenLine = i; }
+ }
+ }
+
+ choosenLine++; // next line is the redirection url
+
+ ps_ptr result;
+ ps_ptr line;
+ line.clone_from(m_playlistContent[choosenLine]);
+
+ if (line.starts_with("../")) {
+ // ../../2093120-b/RISMI/stream01/streamPlaylist.m3u8
+ while (line.starts_with("../")) { line.remove_prefix("../"); }
+ result.clone_from(m_currentHost);
+ int idx = result.last_index_of('/');
+ if (idx > 0 && (size_t)idx != result.strlen() - 1) result.truncate_at((size_t)idx + 1);
+ result.append(line.get());
+ } else if (!line.starts_with_icase("http")) {
+
+ // http://arcast.com.ar:1935/radio/radionar.stream/playlist.m3u8 m_lastHost
+ // chunklist_w789468022.m3u8 line
+ // http://arcast.com.ar:1935/radio/radionar.stream/chunklist_w789468022.m3u8 --> result
+
+ result.clone_from(m_currentHost);
+ int pos = m_currentHost.last_index_of('/');
+ if (pos > 0) {
+ result.truncate_at((size_t)pos + 1);
+ result.append(line.get());
+ }
+ } else {
+ result.clone_from(line);
+ }
+
+ return result; // it's a redirection, a new m3u8 playlist
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::processLocalFile() {
+ if (!(m_audiofile && m_f_running && m_dataMode == AUDIO_LOCALFILE)) return; // guard
+
+ m_prlf.availableBytes = 0;
+ m_prlf.bytesAddedToBuffer = 0;
+
+ if (m_f_firstCall) { // runs only one time per connection, prepare for start
+ m_f_firstCall = false;
+ m_f_stream = false;
+ m_prlf.audioHeaderFound = false;
+ m_prlf.newFilePos = 0;
+ m_prlf.ctime = millis();
+ m_audioFilePosition = 0;
+ m_audioDataSize = m_audioFileSize;
+ m_audioDataStart = 0;
+ m_f_allDataReceived = false;
+ m_prlf.timeout = 8000; // ms
+ }
+
+ if (m_resumeFilePos >= 0) { // we have a resume file position
+ m_prlf.newFilePos = newInBuffStart(m_resumeFilePos);
+ if (m_prlf.newFilePos < 0) AUDIO_LOG_WARN("skip to new position was not successful");
+ m_haveNewFilePos = m_prlf.newFilePos;
+ m_audioDataReadPtr = m_prlf.newFilePos;
+ m_resumeFilePos = -1;
+ m_f_allDataReceived = false;
+ return;
+ }
+
+ m_prlf.availableBytes = min(InBuff.writeSpace(), (size_t)(m_audioFileSize - m_audioFilePosition));
+ m_prlf.bytesAddedToBuffer = audioFileRead(InBuff.getWritePtr(), min(m_prlf.availableBytes, (uint32_t)UINT16_MAX));
+ if (m_prlf.bytesAddedToBuffer > 0) { InBuff.bytesWritten(m_prlf.bytesAddedToBuffer); }
+ if (m_audioDataSize && m_audioFilePosition >= m_audioDataSize) {
+ if (!m_f_allDataReceived) m_f_allDataReceived = true;
+ }
+ if (!m_audioDataSize && m_audioFilePosition == m_audioFileSize) {
+ if (!m_f_allDataReceived) m_f_allDataReceived = true;
+ }
+ // AUDIO_LOG_DEBUG("m_audioFilePosition {} >= m_audioDataSize {}, m_f_allDataReceived {}", m_audioFilePosition, m_audioDataSize, m_f_allDataReceived);
+
+ if (!m_decoder && InBuff.bufferFilled() > 127) {
+ if (!initializeDecoder()) return;
+ m_prlf.maxFrameSize = InBuff.getMaxBlockSize();
+ }
+
+ if (!m_f_stream) {
+ if (m_controlCounter != 100) {
+ if (m_f_ogg) { m_controlCounter = 100; }
+ if ((millis() - m_prlf.ctime) > m_prlf.timeout) {
+ AUDIO_LOG_ERROR("audioHeader reading timeout");
+ m_f_running = false;
+ goto exit;
+ }
+ if (InBuff.bufferFilled() > m_prlf.maxFrameSize || (InBuff.bufferFilled() == m_audioFileSize) || m_f_allDataReceived) { // at least one complete frame or the file is smaller
+ InBuff.bytesWasRead(readAudioHeader(InBuff.readSpace()));
+ }
+ if (m_controlCounter == 100) {
+ if (m_audioDataStart > 0) { m_prlf.audioHeaderFound = true; }
+ if (!m_audioDataSize) m_audioDataSize = m_audioFileSize;
+ }
+ return;
+ } else {
+ m_f_stream = true;
+ info(*this, evt_info, "stream ready");
+ }
+ }
+
+ if (m_fileStartTime > 0 && m_nominal_bitrate) {
+ if (getBitRate() > 0)
+ setAudioPlayTime(m_fileStartTime);
+ else
+ info(*this, evt_info, "can't set audio play time directly");
+ m_fileStartTime = -1;
+ }
+
+ // end of file reached? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_f_eof) { // m_f_eof and m_f_ID3v1TagFound will be set in playAudioData()
+ if (m_f_ID3v1TagFound) readID3V1Tag();
+ exit:
+ ps_ptr afn; // audio file name
+ if (m_audiofile) afn.assign(m_audiofile.name()); // store temporary the name
+ m_audioCurrentTime = 0;
+ m_audioFileDuration = 0;
+ m_resumeFilePos = -1;
+ m_haveNewFilePos = 0;
+ m_codec = CODEC_NONE;
+ stopSong();
+
+ if (afn.valid()) { info(*this, evt_eof, "{}", afn.c_get()); }
+ return;
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+void Audio::processWebStream() {
+ if (m_dataMode != AUDIO_DATA) return; // guard
+ uint16_t readedBytes = 0;
+
+ m_pwst.availableBytes = 0; // available from stream
+ m_pwst.f_clientIsConnected = m_client->connected();
+ m_pwst.writeSpace = UINT16_MAX;
+
+ // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_f_firstCall) { // runs only ont time per connection, prepare for start
+ m_f_firstCall = false;
+ m_f_stream = false;
+ m_pwst.chunkSize = 0;
+ m_metacount = m_metaint;
+ m_f_allDataReceived = false;
+ readMetadata(0, &readedBytes, true);
+ getChunkSize(0, true);
+ m_audioFilePosition = 0;
+ }
+
+ m_pwst.availableBytes = m_client->available(); // available from stream
+ // chunked data tramsfer
+ if (m_f_chunked && m_pwst.availableBytes) {
+ if (m_pwst.chunkSize == 0) {
+ int chunkLen = getChunkSize(&m_pwst.readedBytes);
+ if (chunkLen == -1) { // need more data
+ vTaskDelay(10);
+ return;
+ }
+ if (chunkLen == -100) { // error
+ stopSong();
+ return;
+ }
+ if (chunkLen == 0) { m_f_allDataReceived = true; }
+ m_pwst.chunkSize = chunkLen;
+ m_pwst.readedBytes = 0; // readedBytes is not a part of chunkSize
+ }
+ m_pwst.writeSpace = min(m_pwst.writeSpace, m_pwst.chunkSize);
+ }
+
+ // we have metadata - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if ((m_metaint) && (m_metacount == 0)) {
+ if (!m_pwst.availableBytes) return;
+ readedBytes = 0;
+ bool res = false;
+ if (m_f_chunked) {
+ res = readMetadata(min(m_pwst.availableBytes, m_pwst.chunkSize), &readedBytes);
+ } else {
+ res = readMetadata(m_pwst.availableBytes, &readedBytes);
+ }
+ m_pwst.readedBytes += readedBytes;
+ if (m_f_chunked) m_pwst.chunkSize -= readedBytes; // reduce chunkSize by metadata length
+ if (res == false) return;
+ m_metacount = m_metaint;
+ return;
+ }
+ if (m_metaint) m_pwst.writeSpace = min(m_pwst.writeSpace, m_metacount);
+
+ // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_pwst.availableBytes) {
+ m_pwst.writeSpace = min(m_pwst.writeSpace, (uint32_t)InBuff.writeSpace());
+ int32_t bytesAddedToBuffer = audioFileRead(InBuff.getWritePtr(), min(m_pwst.writeSpace, (uint32_t)UINT16_MAX));
+
+ if (bytesAddedToBuffer > 0) {
+ m_pwst.writeSpace -= bytesAddedToBuffer;
+ if (m_metaint) m_metacount -= bytesAddedToBuffer;
+ if (m_f_chunked) m_pwst.chunkSize -= bytesAddedToBuffer;
+ InBuff.bytesWritten(bytesAddedToBuffer);
+ }
+ }
+
+ // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_f_stream) {
+ if (!m_f_allDataReceived) streamDetection(m_pwst.availableBytes);
+ if (!m_pwst.f_clientIsConnected) {
+ if (m_f_tts && !m_f_allDataReceived) m_f_allDataReceived = true;
+ } // connection closed (OpenAi)
+ }
+
+ if (!m_decoder && InBuff.bufferFilled() > 127) {
+ if (initializeDecoder())
+ m_pwst.maxFrameSize = InBuff.getMaxBlockSize();
+ else
+ return;
+ }
+
+ // start audio decoding - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (((InBuff.bufferFilled() > m_pwst.maxFrameSize * 2) || (m_f_allDataReceived)) && !m_f_stream) { // waiting for buffer filled
+ info(*this, evt_info, "stream ready");
+ m_f_stream = true; // ready to play the audio data
+ }
+
+ if (m_f_eof) {
+ info(*this, evt_eof, "{}", m_lastHost.c_get());
+ stopSong();
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::processWebFile() {
+
+ if (!m_lastHost.valid()) {
+ AUDIO_LOG_ERROR("m_lastHost is empty");
+ return;
+ } // guard
+ uint32_t availableBytes = 0;
+ int32_t bytesAddedToBuffer = 0;
+ // m_pwf.f_clientIsConnected = m_client->connected(); // we are not connected
+
+ if (m_f_firstCall) { // runs only ont time per connection, prepare for start
+ m_f_firstCall = false;
+ m_f_stream = false;
+ m_controlCounter = 0;
+ m_pwf.audioHeaderFound = false;
+ m_pwf.newFilePos = 0;
+ m_pwf.ctime = millis();
+ m_audioFilePosition = 0;
+ m_audioDataSize = m_audioFileSize;
+ m_audioDataStart = 0;
+ m_f_allDataReceived = false;
+ m_pwf.timeout = 8000; // ms
+ if (!initializeDecoder()) return;
+ m_pwf.maxFrameSize = InBuff.getMaxBlockSize(); // every frame is not bigger
+ }
+
+ if (m_resumeFilePos >= 0) { // we have a resume file position
+ m_pwf.newFilePos = newInBuffStart(m_resumeFilePos);
+ if (m_pwf.newFilePos < 0) AUDIO_LOG_WARN("skip to new position was not successful");
+ m_haveNewFilePos = m_pwf.newFilePos;
+ m_audioDataReadPtr = m_pwf.newFilePos;
+ m_resumeFilePos = -1;
+ m_f_allDataReceived = false;
+ return;
+ }
+
+ m_pwf.availableBytes = min(m_client->available(), (int)InBuff.writeSpace());
+ m_pwf.bytesAddedToBuffer = audioFileRead(InBuff.getWritePtr(), min(m_pwf.availableBytes, (uint32_t)UINT16_MAX));
+ if (m_pwf.bytesAddedToBuffer > 0) { InBuff.bytesWritten(m_pwf.bytesAddedToBuffer); }
+ if (m_audioDataSize && m_audioFilePosition >= m_audioDataSize) {
+ if (!m_f_allDataReceived) m_f_allDataReceived = true;
+ }
+ if (!m_audioDataSize && m_audioFilePosition == m_audioFileSize) {
+ if (!m_f_allDataReceived) m_f_allDataReceived = true;
+ }
+ // AUDIO_LOG_ERROR("m_audioFilePosition {} >= m_audioDataSize {}, m_f_allDataReceived {}", m_audioFilePosition, m_audioDataSize, m_f_allDataReceived);
+ if (!m_decoder && InBuff.bufferFilled() > 127) {
+ if (!initializeDecoder()) return;
+ }
+
+ if (!m_f_stream) {
+ if (m_controlCounter != 100) {
+ if (m_f_ogg) { m_controlCounter = 100; }
+ if ((millis() - m_pwf.ctime) > m_pwf.timeout) {
+ AUDIO_LOG_ERROR("audioHeader reading timeout");
+ m_f_running = false;
+ goto exit;
+ }
+ if (InBuff.bufferFilled() > m_pwf.maxFrameSize || (InBuff.bufferFilled() == m_audioFileSize) || m_f_allDataReceived) { // at least one complete frame or the file is smaller
+ InBuff.bytesWasRead(readAudioHeader(InBuff.readSpace()));
+ }
+ if (m_controlCounter == 100) {
+ if (m_audioDataStart > 0) { m_pwf.audioHeaderFound = true; }
+ if (!m_audioDataSize) m_audioDataSize = m_audioFileSize;
+ }
+ return;
+ } else {
+ if (m_resumeFilePos == -1) {
+ if (InBuff.bufferFilled() > 2 * InBuff.getMaxBlockSize()) {
+ m_f_stream = true;
+ info(*this, evt_info, "stream ready");
+ }
+ }
+ }
+ }
+
+ if (m_fileStartTime > 0 && m_nominal_bitrate) {
+ if (getBitRate() > 0)
+ setAudioPlayTime(m_fileStartTime);
+ else
+ info(*this, evt_info, "can't set audio play time directly");
+ m_fileStartTime = -1;
+ }
+
+ // end of file reached? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_f_eof) { // m_f_eof and m_f_ID3v1TagFound will be set in playAudioData()
+ if (m_f_ID3v1TagFound) readID3V1Tag();
+ exit:
+ stopSong();
+ info(*this, evt_eof, "{}", m_lastHost.c_get());
+
+ m_audioCurrentTime = 0;
+ m_audioFileDuration = 0;
+ m_resumeFilePos = -1;
+ m_haveNewFilePos = 0;
+ m_codec = CODEC_NONE;
+ return;
+ }
+}
+// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::processWebStreamTS() {
+
+ // first call, set some values to default ———————————————————————————————————
+ if (m_f_firstCall) { // runs only one time per connection, prepare for start
+ m_f_firstCall = false;
+ m_f_m3u8data = true;
+ m_audioFilePosition = 0;
+ m_pwsst.f_firstPacket = true;
+ m_pwsst.f_chunkFinished = false;
+ m_pwsst.f_nextRound = false;
+ m_pwsst.byteCounter = 0;
+ m_t0 = millis();
+ if (!m_pwsst.ts_packet.valid()) m_pwsst.ts_packet.alloc_array(m_pwsst.ts_packetsize, "m_pwsst.ts_packet"); // first init
+ if (!m_decoder) { // first init
+ getChunkSize(0, true);
+ m_pwsst.chunkSize = 0;
+ ts_parsePacket(0, 0, 0);
+ m_pwsst.ts_packetPtr = 0;
+ if (!initializeDecoder()) return;
+ }
+ } // —————————————————————————————————————————————————————————————————————————
+
+ m_pwsst.ts_packetStart = 0;
+ m_pwsst.ts_packetLength = 0;
+ uint32_t availableBytes = m_client->available(); // available bytes in stream
+
+ if (availableBytes) {
+ /* If the m3u8 stream uses 'chunked data transfer' no content length is supplied. Then the chunk size determines the audio data to be processed.
+ However, the chunk size in some streams is limited to 32768 bytes, although the chunk can be larger. Then the chunk size is
+ calculated again. The data used to calculate (here readedBytes) the chunk size is not part of it.
+ */
+ uint16_t readedBytes = 0;
+ uint32_t minAvBytes = 0;
+ if (m_pwsst.f_chunkFinished) goto chunkFinished;
+ if (m_f_chunked && m_pwsst.chunkSize == m_pwsst.byteCounter) {
+ int chunkLen = getChunkSize(&readedBytes);
+ if (chunkLen == -1) { // need more data
+ vTaskDelay(10);
+ return;
+ }
+ if (chunkLen == -100) { // error
+ stopSong();
+ return;
+ }
+ m_pwsst.chunkSize += chunkLen;
+ AUDIO_LOG_DEBUG("chunkLen {}, rb {}", chunkLen, readedBytes);
+ if (chunkLen == 0) {
+ m_pwsst.f_chunkFinished = true;
+ m_pwsst.chunkSize = 0;
+ m_pwsst.byteCounter = 0;
+ goto chunkFinished;
+ }
+ }
+ if (m_pwsst.chunkSize)
+ minAvBytes = min3(availableBytes, m_pwsst.ts_packetsize - m_pwsst.ts_packetPtr, m_pwsst.chunkSize - m_pwsst.byteCounter);
+ else
+ minAvBytes = min(availableBytes, (uint32_t)(m_pwsst.ts_packetsize - m_pwsst.ts_packetPtr));
+
+ int res = audioFileRead(m_pwsst.ts_packet.get() + m_pwsst.ts_packetPtr, minAvBytes);
+ if (res > 0) {
+ m_pwsst.ts_packetPtr += res;
+ m_pwsst.byteCounter += res;
+ if (m_pwsst.ts_packetPtr < m_pwsst.ts_packetsize) return; // not enough data yet, the process must be repeated if the packet size (188 bytes) is not reached
+ m_pwsst.ts_packetPtr = 0;
+ if (m_pwsst.f_firstPacket) { // search for ID3 Header in the first packet
+ m_pwsst.f_firstPacket = false;
+ uint8_t ID3_HeaderSize = process_m3u8_ID3_Header(m_pwsst.ts_packet.get());
+ if (ID3_HeaderSize > m_pwsst.ts_packetsize) {
+ AUDIO_LOG_ERROR("ID3 Header is too big");
+ stopSong();
+ return;
+ }
+ if (ID3_HeaderSize) {
+ memcpy(m_pwsst.ts_packet.get(), &m_pwsst.ts_packet.get()[ID3_HeaderSize], m_pwsst.ts_packetsize - ID3_HeaderSize);
+ m_pwsst.ts_packetPtr = m_pwsst.ts_packetsize - ID3_HeaderSize;
+ return;
+ }
+ }
+ if (!ts_parsePacket(&m_pwsst.ts_packet.get()[0], &m_pwsst.ts_packetStart, &m_pwsst.ts_packetLength)) {
+ stopSong();
+ AUDIO_LOG_ERROR("song stopped");
+ return;
+ };
+
+ if (m_pwsst.ts_packetLength) {
+ size_t ws = InBuff.writeSpace();
+ if (ws >= m_pwsst.ts_packetLength) {
+ memcpy(InBuff.getWritePtr(), m_pwsst.ts_packet.get() + m_pwsst.ts_packetStart, m_pwsst.ts_packetLength);
+ InBuff.bytesWritten(m_pwsst.ts_packetLength);
+ m_pwsst.f_nextRound = true;
+ } else {
+ int t = 0;
+ memcpy(InBuff.getWritePtr(), m_pwsst.ts_packet.get() + m_pwsst.ts_packetStart, ws); // write everything that fits into the buffer
+ InBuff.bytesWritten(ws);
+ while (true) {
+ if (InBuff.writeSpace() >= m_pwsst.ts_packetLength - ws) {
+ memcpy(InBuff.getWritePtr(), &m_pwsst.ts_packet.get()[ws + m_pwsst.ts_packetStart], m_pwsst.ts_packetLength - ws); // write the rest
+ InBuff.bytesWritten(m_pwsst.ts_packetLength - ws);
+ break;
+ }
+ t++;
+ vTaskDelay(10); // wait until the buffer is free
+ if (t == 10) {
+ AUDIO_LOG_ERROR("InBuff is full");
+ break;
+ }
+ }
+ }
+ }
+ if (m_audioFileSize && m_pwsst.byteCounter > m_audioFileSize) {
+ AUDIO_LOG_ERROR("byteCounter overflow, byteCounter: {}, contentlength: {}", m_pwsst.byteCounter, m_audioFileSize);
+ return;
+ }
+ if (m_pwsst.chunkSize && m_pwsst.byteCounter > m_pwsst.chunkSize) {
+ AUDIO_LOG_ERROR("byteCounter overflow, byteCounter: {}, chunkSize: {}", m_pwsst.byteCounter, m_pwsst.chunkSize);
+ return;
+ }
+ }
+ }
+ if (m_audioFileSize && m_pwsst.byteCounter == m_audioFileSize) {
+ if (InBuff.bufferFilled() < settings.BUFFER_TRESHOLD_HLS) {
+ m_f_continue = true;
+ m_pwsst.byteCounter = 0;
+ m_pwsst.ts_packetPtr = 0;
+ }
+ m_pwsst.f_nextRound = false;
+ goto exit;
+ }
+
+chunkFinished:
+ if (m_pwsst.f_chunkFinished) {
+ if (InBuff.bufferFilled() < settings.BUFFER_TRESHOLD_HLS) {
+ m_pwsst.f_chunkFinished = false;
+ m_f_continue = true;
+ m_pwsst.byteCounter = 0;
+ m_pwsst.chunkSize = 0;
+ m_pwsst.ts_packetPtr = 0;
+ }
+ goto exit;
+ }
+
+ // if the buffer is often almost empty issue a warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_f_stream) { streamDetection(availableBytes); }
+
+ // buffer fill routine - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ {
+ if (InBuff.bufferFilled() > settings.BUFFER_TRESHOLD_HLS && !m_f_stream) { // waiting for buffer filled
+ m_f_stream = true; // ready to play the audio data
+ uint16_t filltime = millis() - m_t0;
+ info(*this, evt_info, "stream ready");
+ info(*this, evt_info, "buffer filled in {} ms", filltime);
+ }
+ }
+
+exit:
+ return;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::processWebStreamHLS() {
+
+ // first call, set some values to default - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (m_f_firstCall) { // runs only ont time per connection, prepare for start
+ m_f_firstCall = false;
+ m_f_m3u8data = true;
+ m_t0 = millis();
+ m_controlCounter = 0;
+ m_audioFilePosition = 0;
+ m_pwsHLS.ID3BuffSize = 4096;
+ m_pwsHLS.availableBytes = 0;
+ m_pwsHLS.firstBytes = true;
+ m_pwsHLS.f_chunkFinished = false;
+ m_pwsHLS.byteCounter = 0;
+ m_pwsHLS.chunkSize = 0;
+ m_pwsHLS.ID3WritePtr = 0;
+ m_pwsHLS.ID3ReadPtr = 0;
+ m_pwsHLS.ID3Buff.alloc(m_pwsHLS.ID3BuffSize, "m_pwsHLS.ID3Buff");
+ getChunkSize(0, true);
+ if (!m_decoder && !initializeDecoder()) return;
+ m_pwsHLS.maxFrameSize = InBuff.getMaxBlockSize(); // every mp3/aac frame is not bigger
+ }
+
+ if (m_dataMode != AUDIO_DATA) return; // guard
+
+ m_pwsHLS.availableBytes = m_client->available();
+ if (m_pwsHLS.availableBytes) { // an ID3 header could come here
+ uint16_t readedBytes = 0;
+
+ if (m_f_chunked && !m_pwsHLS.chunkSize) {
+ m_pwsHLS.chunkSize = getChunkSize(&readedBytes);
+ if (m_pwsHLS.chunkSize == -1) { // need more data
+ vTaskDelay(10);
+ return;
+ }
+ if (m_pwsHLS.chunkSize == -100) { // error
+ stopSong();
+ return;
+ }
+ m_pwsHLS.byteCounter += readedBytes;
+ }
+
+ if (m_pwsHLS.firstBytes) {
+ if (m_pwsHLS.ID3WritePtr < m_pwsHLS.ID3BuffSize) {
+ m_pwsHLS.ID3WritePtr += audioFileRead(&m_pwsHLS.ID3Buff[m_pwsHLS.ID3WritePtr], m_pwsHLS.ID3BuffSize - m_pwsHLS.ID3WritePtr);
+ return;
+ }
+ if (m_controlCounter < 100) {
+ int res = read_ID3_Header(&m_pwsHLS.ID3Buff[m_pwsHLS.ID3ReadPtr], m_pwsHLS.ID3BuffSize - m_pwsHLS.ID3ReadPtr);
+ if (res >= 0) m_pwsHLS.ID3ReadPtr += res;
+ if (m_pwsHLS.ID3ReadPtr > m_pwsHLS.ID3BuffSize) {
+ AUDIO_LOG_ERROR("buffer overflow");
+ stopSong();
+ return;
+ }
+ return;
+ }
+ if (m_controlCounter != 100) return;
+
+ size_t ws = InBuff.writeSpace();
+ if (ws >= m_pwsHLS.ID3BuffSize - m_pwsHLS.ID3ReadPtr) {
+ memcpy(InBuff.getWritePtr(), &m_pwsHLS.ID3Buff[m_pwsHLS.ID3ReadPtr], m_pwsHLS.ID3BuffSize - m_pwsHLS.ID3ReadPtr);
+ InBuff.bytesWritten(m_pwsHLS.ID3BuffSize - m_pwsHLS.ID3ReadPtr);
+ } else {
+ int t = 0;
+ memcpy(InBuff.getWritePtr(), &m_pwsHLS.ID3Buff[m_pwsHLS.ID3ReadPtr], ws);
+ InBuff.bytesWritten(ws);
+ while (true) {
+ if (InBuff.writeSpace() >= m_pwsHLS.ID3BuffSize - (m_pwsHLS.ID3ReadPtr + ws)) {
+ memcpy(InBuff.getWritePtr(), &m_pwsHLS.ID3Buff[ws + m_pwsHLS.ID3ReadPtr], m_pwsHLS.ID3BuffSize - (m_pwsHLS.ID3ReadPtr + ws)); // write everything that fits into the buffer
+ InBuff.bytesWritten(m_pwsHLS.ID3BuffSize - (m_pwsHLS.ID3ReadPtr + ws));
+ break;
+ }
+ t++;
+ vTaskDelay(10); // wait until the buffer is free
+ if (t == 10) {
+ AUDIO_LOG_ERROR("InBuff is full");
+ break;
+ }
+ }
+ }
+ m_pwsHLS.ID3Buff.reset();
+ m_pwsHLS.byteCounter += m_pwsHLS.ID3BuffSize;
+ m_pwsHLS.firstBytes = false;
+ }
+
+ size_t bytesWasWritten = 0;
+ if (InBuff.writeSpace() >= m_pwsHLS.availableBytes) {
+ // if(availableBytes > 1024) availableBytes = 1024; // 1K throttle
+ bytesWasWritten = audioFileRead(InBuff.getWritePtr(), m_pwsHLS.availableBytes);
+ } else {
+ bytesWasWritten = audioFileRead(InBuff.getWritePtr(), InBuff.writeSpace());
+ }
+ InBuff.bytesWritten(bytesWasWritten);
+
+ m_pwsHLS.byteCounter += bytesWasWritten;
+
+ if (m_pwsHLS.byteCounter == m_audioFileSize || m_pwsHLS.byteCounter == m_pwsHLS.chunkSize) {
+ m_pwsHLS.f_chunkFinished = true;
+ m_pwsHLS.byteCounter = 0;
+ }
+ }
+
+ if (m_pwsHLS.f_chunkFinished) {
+ if (InBuff.bufferFilled() < settings.BUFFER_TRESHOLD_HLS) {
+ m_pwsHLS.f_chunkFinished = false;
+ m_f_continue = true;
+ }
+ }
+
+ // if the buffer is often almost empty issue a warning or try a new connection - - - - - - - - - - - - - - - - - - -
+ if (m_f_stream) { streamDetection(m_pwsHLS.availableBytes); }
+
+ if (InBuff.bufferFilled() > settings.BUFFER_TRESHOLD_HLS && !m_f_stream) { // waiting for buffer filled
+ m_f_stream = true; // ready to play the audio data
+ // uint16_t filltime = millis() - m_t0;
+ info(*this, evt_info, "stream ready");
+ // info(*this, evt_info, "buffer filled in {} ms", filltime);
+ }
+ return;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::playAudioData() {
+
+ if (m_f_eof || m_f_lockInBuffer || !m_f_stream) {
+ vTaskDelay(1);
+ return;
+ } // guard, stream not ready or eof reached or InBuff is locked or not running
+ if (m_validSamples) {
+ playChunk();
+ return;
+ } // guard, play samples first
+ //--------------------------------------------------------------------------------
+ m_pad.count = 0;
+ m_pad.bytesToDecode = InBuff.readSpace();
+ m_pad.bytesDecoded = 0;
+
+ if (m_f_firstPlayCall) {
+ m_audioDataReadPtr = 0;
+ m_f_firstPlayCall = false;
+ m_pad.count = 0;
+ m_pad.oldAudioDataSize = 0;
+ m_bytesNotConsumed = 0;
+ m_pad.lastFrames = false;
+ m_f_eof = false;
+ }
+ //--------------------------------------------------------------------------------
+
+ bool isFile = false;
+ bool isStream = false;
+
+ if (m_dataMode == AUDIO_LOCALFILE) isFile = true;
+ if (m_streamType == ST_WEBFILE && m_playlistFormat != FORMAT_M3U8) isFile = true; // local file or webfile but not m3u8 file
+ if (m_streamType == ST_WEBSTREAM || m_playlistFormat == FORMAT_M3U8) isStream = true;
+ if (!isFile && !isStream) return;
+
+ xSemaphoreTake(mutex_audioTaskIsDecoding, 1 * configTICK_RATE_HZ);
+ {
+ m_pad.bytesDecoded = 0;
+ if (isFile) {
+ if (!m_audioDataSize) goto exit; // no data to decode if filesize is 0
+ if (m_audioDataStart + m_audioDataSize - m_audioDataReadPtr == 128) {
+ m_f_ID3v1TagFound = true;
+ m_f_eof = true;
+ goto exit;
+ }
+ if (m_audioDataSize <= m_audioDataReadPtr) {
+ m_f_eof = true;
+ goto exit;
+ }
+
+ if (m_audioDataStart + m_audioDataSize >= m_audioFilePosition) m_f_allDataReceived = true;
+ if (m_audioDataSize - m_audioDataReadPtr <= InBuff.getMaxBlockSize()) m_pad.lastFrames = true;
+
+ if (m_pad.lastFrames) {
+ m_pad.bytesToDecode = min(InBuff.readSpace(), (size_t)(m_audioDataStart + m_audioDataSize - m_audioDataReadPtr));
+ m_pad.bytesDecoded = sendBytes(InBuff.getReadPtr(), m_pad.bytesToDecode);
+ } else {
+ m_pad.bytesToDecode = InBuff.readSpace();
+ if (m_pad.bytesToDecode >= InBuff.getMaxBlockSize()) { m_pad.bytesDecoded = sendBytes(InBuff.getReadPtr(), m_pad.bytesToDecode); }
+ }
+ }
+
+ if (isStream) {
+ if (m_f_allDataReceived) { // Google TTS, OpenAI
+ m_pad.lastFrames = true;
+ if (m_f_tts && !InBuff.bufferFilled()) {
+ m_f_eof = true;
+ goto exit;
+ }
+ }
+ if (m_pad.lastFrames) {
+ if (m_f_chunked) {
+ m_pad.bytesToDecode = InBuff.readSpace();
+ } else {
+ m_pad.bytesToDecode = min(InBuff.readSpace(), (size_t)(m_audioDataStart + m_audioDataSize - m_audioDataReadPtr));
+ }
+ m_pad.bytesDecoded = sendBytes(InBuff.getReadPtr(), m_pad.bytesToDecode);
+ } else {
+ m_pad.bytesToDecode = InBuff.readSpace();
+ if (m_pad.bytesToDecode >= InBuff.getMaxBlockSize()) { m_pad.bytesDecoded = sendBytes(InBuff.getReadPtr(), m_pad.bytesToDecode); }
+ }
+ }
+
+ if (m_pad.bytesDecoded > 0) {
+ InBuff.bytesWasRead(m_pad.bytesDecoded);
+ m_audioDataReadPtr += m_pad.bytesDecoded;
+ }
+ }
+exit:
+ xSemaphoreGive(mutex_audioTaskIsDecoding);
+
+ AUDIO_LOG_DEBUG("m_audioDataReadPtr {}, m_audioDataSize {}", m_audioDataReadPtr, m_audioDataSize);
+ return;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::parseHttpResponseHeader() { // this is the response to a GET / request
+
+ if (m_dataMode != HTTP_RESPONSE_HEADER) return false;
+ m_icy_items.reset();
+ m_metaint = 0; // no metaint yet
+
+ m_phreh.reset();
+ m_phreh.ctime = millis();
+ m_phreh.timeout = 5000; // ms
+ m_httpRespHdrBuff.clear();
+ uint16_t pos = 0;
+
+ while (true) { // read the header first and store it in m_httpRespHdrBuff
+ if (m_client->available()) { m_httpRespHdrBuff[pos++] = audioFileRead(); }
+ if (m_httpRespHdrBuff.ends_with("\r\n\r\n")) break;
+ if (m_httpRespHdrBuff.ends_with("\n\n")) break;
+ if (pos == m_httpRespHdrBuff.size()) {
+ AUDIO_LOG_WARN("responseHeaderline overflow");
+ m_httpRespHdrBuff[pos - 1] = '\0';
+ break;
+ }
+ if ((millis() - m_phreh.ctime) > m_phreh.timeout) {
+ AUDIO_LOG_ERROR("timeout");
+ m_phreh.f_time = false;
+ // stopSong();
+ return false;
+ }
+ }
+
+ ps_ptr rhl;
+ bool ct_seen = false;
+
+ // m_httpRespHdrBuff.println();
+
+ pos = 0;
+ while (true) { // read the header line for line
+ int idx = m_httpRespHdrBuff.index_of('\n', pos);
+ if (idx > pos) {
+ rhl = m_httpRespHdrBuff.substr(pos, idx - pos);
+ rhl.set_name("rhl");
+ rhl.remove_chars("\r");
+ pos = idx + 1;
+ if (rhl.strlen() == 0) continue;
+ } else { // done?
+ if (ct_seen)
+ goto lastToDo;
+ else { goto exit; }
+ }
+
+ // rhl.println();
+
+ if (rhl.starts_with_icase("icy-")) { // —— ICY BLOCK ——————————————————————————————————————————————————————————
+ m_phreh.f_icy_data = true;
+
+ if (rhl.starts_with_icase("icy-genre:")) {
+ m_icy_items.icy_genre.copy_from(rhl.get() + 10); // Ambient, Rock, etc
+ m_icy_items.icy_genre.trim();
+ latinToUTF8(m_icy_items.icy_genre);
+ }
+
+ if (rhl.starts_with_icase("icy-logo:")) {
+ m_icy_items.icy_logo.copy_from(rhl.get() + 9); // https://www.0nradio.com/logos/0n-70s_600x600.jpg
+ m_icy_items.icy_logo.trim();
+ latinToUTF8(m_icy_items.icy_logo);
+ }
+
+ if (rhl.starts_with_icase("icy-name:")) {
+ m_icy_items.icy_name.copy_from(rhl.get() + 9);
+ m_icy_items.icy_name.trim();
+ latinToUTF8(m_icy_items.icy_name);
+ }
+
+ if (rhl.starts_with_icase("icy-url:")) {
+ m_icy_items.icy_url.copy_from(rhl.get() + 8);
+ m_icy_items.icy_url.trim();
+ }
+
+ if (rhl.starts_with_icase("icy-description:")) {
+ m_icy_items.icy_description.copy_from(rhl.get() + 16);
+ m_icy_items.icy_description.trim();
+ latinToUTF8(m_icy_items.icy_description);
+ }
+
+ if (rhl.starts_with_icase("icy-metaint:")) {
+ m_icy_items.icy_metaint.copy_from(rhl.get() + 12);
+ m_icy_items.icy_metaint.trim();
+ }
+
+ if (rhl.starts_with_icase("icy-br:")) {
+ m_icy_items.icy_br.copy_from(rhl.get() + 7);
+ m_icy_items.icy_br.trim();
+ }
+ } // —— END ICY BLOCK —————————————————————————————————————————————————————————————————————————————————————————
+
+ if (rhl.starts_with_icase("HTTP/")) { // HTTP status error code
+ char statusCode[5];
+ statusCode[0] = rhl[9];
+ statusCode[1] = rhl[10];
+ statusCode[2] = rhl[11];
+ statusCode[3] = '\0';
+ int sc = atoi(statusCode);
+ if (sc == 403 && !m_f_alt_user_agent) { // HTTP/1.1 403 Forbidden
+ m_f_alt_user_agent = true;
+ AUDIO_LOG_WARN("403 Forbidden, test alternative user agent");
+ connecttohost(m_lastHost.c_get());
+ return true;
+ }
+ m_f_alt_user_agent = false;
+ if (sc > 310) { // e.g. HTTP/1.1 301 Moved Permanently
+ info(*this, evt_streamtitle, "{}", rhl.get());
+ goto exit;
+ }
+ } else if (rhl.starts_with_icase("content-type:")) { // content-type: text/html; charset=UTF-8
+ int idx = rhl.index_of(';', 13);
+ if (idx > 0) rhl[idx] = '\0';
+ if (parseContentType(rhl.get() + 13))
+ ct_seen = true;
+ else {
+ AUDIO_LOG_WARN("unknown contentType {}", rhl.get() + 13);
+ goto exit;
+ }
+ }
+
+ else if (rhl.starts_with_icase("location:")) {
+ int pos = rhl.index_of_icase("http", 0);
+ if (pos == -1) {
+ int doubleSlash = rhl.index_of("//"); // e.g. location: //frontend.streamonkey.net/... host: http://webstream.radiof.de/
+ if (doubleSlash >= 9) rhl.insert("http:", doubleSlash); // ==> http://frontend.streamonkey.net/fhn-radiof945/stream/mp3?aggregator=fh-tinyurl
+ pos = rhl.index_of_icase("http", 0);
+ }
+ if (pos >= 0) {
+ const char* c_host = (rhl.get() + pos);
+ if (!m_currentHost.equals(c_host)) { // prevent a loop
+ int pos_slash = indexOf(c_host, "/", 9);
+ if (pos_slash > 9) {
+ if (!strncmp(c_host, m_currentHost.get(), pos_slash)) {
+ info(*this, evt_info, "redirect to new extension at existing host \"{}\"", c_host);
+ if (m_playlistFormat == FORMAT_M3U8) {
+ // m_lastHost.assign(c_host);
+ m_f_m3u8data = true;
+ }
+ httpPrint(c_host);
+ while (m_client->available()) audioFileRead(); // empty client buffer
+ return true;
+ }
+ }
+ info(*this, evt_info, "redirect to new host \"{}\"", c_host);
+ httpPrint(c_host);
+ return true;
+ }
+ } else {
+ AUDIO_LOG_WARN("unknown redirection: {}", rhl.c_get());
+ }
+ }
+
+ else if (rhl.starts_with_icase("content-encoding:")) {
+ info(*this, evt_info, "{}", rhl.get());
+ if (rhl.contains("gzip")) {
+ AUDIO_LOG_ERROR("can't extract gzip");
+ goto exit;
+ }
+ }
+
+ else if (rhl.starts_with_icase("content-disposition:")) { // e.g we have this headerline: content-disposition: attachment; filename=stream.asx
+ int idx = rhl.index_of_icase("filename=");
+ if (idx >= 0) {
+ ps_ptr fn;
+ fn.assign(rhl.get() + idx + 9); // Position directly after "filename="
+ fn.replace("\"", ""); // remove '\"' around filename if present
+ info(*this, evt_info, "Filename is {}", fn.get());
+ }
+ } else if (rhl.starts_with_icase("connection:")) {
+ if (rhl.contains_with_icase("close")) { m_f_connectionClose = true; /* AUDIO_LOG_ERROR("connection will be closed"); */ } // ends after ogg last Page is set
+ }
+
+ else if (rhl.starts_with_icase("content-length:")) {
+ const char* c_cl = (rhl.get() + 15);
+ int32_t i_cl = atoi(c_cl);
+ m_audioFileSize = i_cl;
+ // info(*this, evt_info, "content-length: {}", m_audioFileSize);
+ }
+
+ else if (rhl.starts_with_icase("transfer-encoding:")) {
+ if (rhl.ends_with_icase("chunked")) { // Station provides chunked transfer
+ m_f_chunked = true;
+ info(*this, evt_info, "chunked data transfer");
+ m_chunkcount = 0; // Expect chunkcount in DATA
+ }
+ }
+
+ else if (rhl.starts_with_icase("accept-ranges:")) {
+ if (rhl.ends_with_icase("bytes")) m_f_acceptRanges = true;
+ // AUDIO_LOG_INFO("{}", rhl.c_get());
+ }
+
+ else if (rhl.starts_with_icase("content-range:")) {
+ AUDIO_LOG_INFO("{}", rhl.c_get());
+ }
+
+ else if (rhl.starts_with_icase("www-authenticate:")) {
+ AUDIO_LOG_WARN("authentification failed, wrong credentials?");
+ goto exit;
+ } else {
+ ;
+ }
+ } // outer while
+
+exit: // termination condition
+ m_f_alt_user_agent = false;
+ m_dataMode = AUDIO_NONE;
+ stopSong();
+ return false;
+
+lastToDo:
+ m_f_alt_user_agent = false;
+ m_streamType = ST_WEBSTREAM;
+ if (m_audioFileSize > 0) m_streamType = ST_WEBFILE; // content length found
+ if (m_phreh.f_icy_data) m_streamType = ST_WEBSTREAM;
+ if (m_f_tts) m_streamType = ST_WEBSTREAM; // this is from AI or GoogleTTS(AI response)
+
+ if (ct_seen && m_playlistFormat == FORMAT_M3U8 && !m_m3u8_host.valid()) { m_m3u8_host = m_lastHost; }
+
+ if (m_codec != CODEC_NONE) {
+ m_dataMode = AUDIO_DATA; // Expecting data now
+ } else if (m_playlistFormat != FORMAT_NONE) {
+ m_dataMode = AUDIO_PLAYLISTINIT; // playlist expected
+ // AUDIO_LOG_INFO("now parse playlist");
+ } else {
+ AUDIO_LOG_INFO("unknown content found at: {}", m_currentHost.c_get());
+ goto exit;
+ }
+
+ if (m_phreh.f_icy_data) {
+ if (m_icy_items.icy_description.valid()) info(*this, evt_icydescription, "{}", m_icy_items.icy_description);
+ if (m_icy_items.icy_genre.valid()) info(*this, evt_genre, "{}", m_icy_items.icy_genre);
+ if (m_icy_items.icy_logo.valid()) info(*this, evt_icylogo, "{}", m_icy_items.icy_logo);
+ if (m_icy_items.icy_name.valid()) info(*this, evt_name, "{}", m_icy_items.icy_name);
+ if (m_icy_items.icy_url.valid()) info(*this, evt_icyurl, "{}", m_icy_items.icy_url);
+ if (m_icy_items.icy_metaint.valid()) m_metaint = m_icy_items.icy_metaint.to_uint32();
+ if (m_icy_items.icy_br.valid()) m_nominal_bitrate = m_icy_items.icy_br.to_uint32() * 1000;
+ }
+
+ AUDIO_LOG_DEBUG("playlistFormat {}, dataMode {}, streamType: {}", plsFmtStr[m_playlistFormat], dataModeStr[m_dataMode], streamTypeStr[m_streamType]);
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::parseHttpRangeHeader() { // this is the response to a Range request
+
+ ps_ptr rhl;
+ rhl.alloc(1024, "rhl"); // responseHeaderline
+ bool ct_seen = false;
+
+ if (m_dataMode != HTTP_RANGE_HEADER) {
+ AUDIO_LOG_ERROR("wrong datamode {}", m_dataMode);
+ goto exit;
+ }
+
+ m_phrah.ctime = millis();
+ m_phrah.timeout = 4500; // ms
+
+ if (m_client->available() == 0) {
+ if (!m_phrah.f_time) {
+ m_phrah.stime = millis();
+ m_phrah.f_time = true;
+ }
+ if ((millis() - m_phrah.stime) > m_phrah.timeout) {
+ AUDIO_LOG_ERROR("timeout");
+ m_phrah.f_time = false;
+ return false;
+ }
+ }
+ m_phrah.f_time = false;
+
+ rhl.clear();
+
+ while (true) { // outer while
+ uint16_t pos = 0;
+ if ((millis() - m_phrah.ctime) > m_phrah.timeout) {
+ AUDIO_LOG_ERROR("timeout");
+ m_f_timeout = true;
+ goto exit;
+ }
+ while (m_client->available()) {
+ uint8_t b = audioFileRead();
+ if (b == '\n') {
+ if (!pos) { // empty line received, is the last line of this responseHeader
+ goto lastToDo;
+ }
+ break;
+ }
+ if (b == '\r') rhl[pos] = 0;
+ if (b < 0x20) continue;
+ rhl[pos] = b;
+ pos++;
+ if (pos == 1023) {
+ pos = 1022;
+ continue;
+ }
+ if (pos == 1022) {
+ rhl[pos] = '\0';
+ AUDIO_LOG_WARN("responseHeaderline overflow");
+ }
+ } // inner while
+ if (!pos) {
+ vTaskDelay(5);
+ continue;
+ }
+ // AUDIO_LOG_WARN("rh {}", rhl.c_get());
+ if (rhl.starts_with_icase("HTTP/")) { // HTTP status error code
+ char statusCode[5];
+ statusCode[0] = rhl[9];
+ statusCode[1] = rhl[10];
+ statusCode[2] = rhl[11];
+ statusCode[3] = '\0';
+ int sc = atoi(statusCode);
+ if (sc > 310) { // e.g. HTTP/1.1 301 Moved Permanently
+ info(*this, evt_name, "{}", rhl.get());
+ goto exit;
+ }
+ }
+ if (rhl.starts_with_icase("Server:")) { info(*this, evt_info, "{}", rhl.c_get()); }
+ if (rhl.starts_with_icase("Content-Length:")) {
+ // info(*this, evt_info, "{}", rhl.c_get());
+ }
+ if (rhl.starts_with_icase("Content-Range:")) { info(*this, evt_info, "{}", rhl.c_get()); }
+ if (rhl.starts_with_icase("Content-Type:")) {
+ // info(*this, evt_info, "{}", rhl.c_get());
+ }
+ }
+exit:
+ return false;
+lastToDo:
+ m_dataMode = AUDIO_DATA;
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::initializeDecoder() {
+ if (m_decoder) {
+ AUDIO_LOG_WARN("stopSong first");
+ return true;
+ }
+ m_codec = determineCodec(m_codec); // OGG cn be VORBIS, FLAC or OPUS, contentType must not always be correct
+
+ const char* type = nullptr;
+ switch (m_codec) {
+ case CODEC_MP3:
+ type = "MP3";
+ InBuff.setMaxBlocksize(m_frameSizeMP3);
+ break;
+ case CODEC_AAC:
+ type = "AAC";
+ InBuff.setMaxBlocksize(m_frameSizeAAC);
+ break;
+ case CODEC_M4A:
+ type = "AAC";
+ InBuff.setMaxBlocksize(m_frameSizeAAC);
+ break;
+ case CODEC_FLAC:
+ type = "FLAC";
+ InBuff.setMaxBlocksize(m_frameSizeFLAC);
+ break;
+ case CODEC_OPUS:
+ type = "OPUS";
+ InBuff.setMaxBlocksize(m_frameSizeOPUS);
+ break;
+ case CODEC_VORBIS:
+ type = "VORBIS";
+ InBuff.setMaxBlocksize(m_frameSizeVORBIS);
+ break;
+ case CODEC_WAV:
+ type = "WAV";
+ InBuff.setMaxBlocksize(m_frameSizeWav);
+ break;
+ default:
+ AUDIO_LOG_ERROR("unknown decoder");
+ stopSong();
+ return false;
+ break;
+ }
+
+ if (type) {
+ m_decoder = createDecoder(type);
+ if (!m_decoder || !m_decoder->init()) {
+ AUDIO_LOG_ERROR("The {Decoder could not be initialized", type);
+ stopSong();
+ return false;
+ }
+ }
+ info(*this, evt_info, "{}Decoder has been initialized", m_decoder->whoIsIt());
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::parseContentType(ps_ptr ct) {
+ enum : int { CT_NONE, CT_MP3, CT_AAC, CT_M4A, CT_WAV, CT_FLAC, CT_PLS, CT_M3U, CT_ASX, CT_M3U8, CT_TXT, CT_AACP, CT_OPUS, CT_OGG, CT_VORBIS };
+
+ // MIME types and their CT_Val values
+ static const struct {
+ const char* mime_type;
+ int ct_val;
+ } mime_map[] = {
+ {"audio/mpeg", CT_MP3},
+ {"audio/mpeg3", CT_MP3},
+ {"audio/x-mpeg", CT_MP3},
+ {"audio/x-mpeg-3", CT_MP3},
+ {"audio/mp3", CT_MP3},
+ {"audio/aac", CT_AAC},
+ {"audio/x-aac", CT_AAC},
+ {"audio/aacp", CT_AAC},
+ {"video/mp2t", CT_AAC},
+ {"audio/mp2t", CT_AAC},
+ {"video/m2ts", CT_AAC},
+ {"audio/mp4", CT_M4A},
+ {"audio/m4a", CT_M4A},
+ {"audio/x-m4a", CT_M4A},
+ {"audio/wav", CT_WAV},
+ {"audio/x-wav", CT_WAV},
+ {"audio/flac", CT_FLAC},
+ {"audio/x-flac", CT_FLAC},
+ {"audio/scpls", CT_PLS},
+ {"audio/x-scpls", CT_PLS},
+ {"application/pls+xml", CT_PLS},
+ {"audio/mpegurl", CT_M3U},
+ {"audio/x-mpegurl", CT_M3U},
+ {"audio/ms-asf", CT_ASX},
+ {"video/x-ms-asf", CT_ASX},
+ {"audio/x-ms-asx", CT_ASX},
+ {"application/ogg", CT_OGG},
+ {"audio/ogg", CT_OGG},
+ {"application/vnd.apple.mpegurl", CT_M3U8},
+ {"application/hls+xml", CT_M3U8},
+ {"application/x-mpegurl", CT_M3U8},
+ {"application/octet-stream", CT_TXT},
+ {"text/html", CT_TXT},
+ {"text/plain", CT_TXT},
+ {"application/json", CT_TXT},
+ {NULL, CT_NONE} // Final marker
+ };
+
+ ct.trim();
+
+ m_codec = CODEC_NONE;
+ int ct_val = CT_NONE;
+
+ // Search in the Lookup table
+ for (int i = 0; mime_map[i].mime_type != NULL; i++) {
+ ct.toLowerCase();
+ if (ct.equals(mime_map[i].mime_type)) {
+ ct_val = mime_map[i].ct_val;
+ break;
+ }
+ }
+
+ // Special cases for video/mp2t and audio/mp2t
+ if (ct_val == CT_AAC && (ct == "video/mp2t" || ct == "audio/mp2t") && m_m3u8Codec == CODEC_MP3) { ct_val = CT_MP3; }
+ // Special case for audio/mpegurl
+ if (ct_val == CT_M3U && ct == "audio/mpegurl" && m_expectedPlsFmt == FORMAT_M3U8) { ct_val = CT_M3U8; }
+ // Special case for application/json
+ if (ct_val == CT_TXT && ct == "application/json" && m_expectedPlsFmt == FORMAT_M3U8) { ct_val = CT_M3U8; }
+
+ // exam for invalid content types
+ if (ct_val == CT_NONE) {
+ AUDIO_LOG_WARN("ContentType {} not supported", ct);
+ return false;
+ }
+
+ // allocation of m_codec and m_playlist format
+ switch (ct_val) {
+ case CT_MP3: m_codec = CODEC_MP3; break;
+ case CT_AAC: m_codec = CODEC_AAC; break;
+ case CT_M4A: m_codec = CODEC_M4A; break;
+ case CT_FLAC: m_codec = CODEC_FLAC; break;
+ case CT_OPUS:
+ m_codec = CODEC_OPUS;
+ m_f_ogg = true;
+ break;
+ case CT_VORBIS:
+ m_codec = CODEC_VORBIS;
+ m_f_ogg = true;
+ break;
+ case CT_WAV: m_codec = CODEC_WAV; break;
+ case CT_OGG:
+ m_codec = CODEC_OGG;
+ m_f_ogg = true;
+ break;
+ case CT_PLS: m_playlistFormat = FORMAT_PLS; break;
+ case CT_M3U: m_playlistFormat = FORMAT_M3U; break;
+ case CT_ASX: m_playlistFormat = FORMAT_ASX; break;
+ case CT_M3U8: m_playlistFormat = FORMAT_M3U8; break;
+ case CT_TXT:
+ if (m_expectedCodec == CODEC_AAC) m_codec = CODEC_AAC;
+ if (m_expectedCodec == CODEC_MP3) m_codec = CODEC_MP3;
+ if (m_expectedPlsFmt == FORMAT_ASX) m_playlistFormat = FORMAT_ASX;
+ if (m_expectedPlsFmt == FORMAT_M3U) m_playlistFormat = FORMAT_M3U;
+ if (m_expectedPlsFmt == FORMAT_M3U8) m_playlistFormat = FORMAT_M3U8;
+ if (m_expectedPlsFmt == FORMAT_PLS) m_playlistFormat = FORMAT_PLS;
+ break;
+ default: AUDIO_LOG_ERROR("{}, unsupported audio format", ct); return false;
+ }
+
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::showstreamtitle(char* st) {
+ if (!st) return;
+ // example for ml:
+ // StreamTitle='Oliver Frank - Mega Hitmix';StreamUrl='www.radio-welle-woerthersee.at';
+ // or adw_ad='true';durationMilliseconds='10135';adId='34254';insertionType='preroll';
+ // html: 'Bielszy odcień bluesa 682 cz.1' --> 'Bielszy odcień bluesa 682 cz.1'
+
+ ps_ptr ml;
+ ps_ptr title;
+ ps_ptr artist;
+ ps_ptr streamTitle;
+ ps_ptr sUrl;
+ ml.htmlToUTF8(st); // convert to UTF-8
+
+ int16_t idx1 = 0, idx2, idx4, idx5, idx6, idx7, titleLen = 0, artistLen = 0, titleStart = 0, artistStart = 0;
+
+ // if(idx1 < 0) idx1 = indexOf(ml, "Title:", 0); // Title found (e.g. https://stream-hls.bauermedia.pt/comercial.aac/playlist.m3u8)
+ // if(idx1 < 0) idx1 = indexOf(ml, "title:", 0); // Title found (e.g. #EXTINF:10,title="The Dan Patrick Show (M-F 9a-12p ET)",artist="zc1401"
+
+ if (ml.index_of("StreamTitle='37364
+ 00000037364.jpgBoyfriend
+ SingleDOVE CAMERON
+ CD4161BOYFRIEND
+ 2126303
+ Dove Cameroncidadefm.jpg
+ Cidade12012660981
+ Boyfriend
Cidade
+ 2022-11-15T22:00:00+00:002022-11-16T06:59:59+00:00
+ Cidade22h às 07h0
+ ';StreamUrl='';
+ */
+ idx4 = ml.index_of("");
+ idx5 = ml.index_of("");
+ idx6 = ml.index_of("");
+ idx7 = ml.index_of("");
+ if (idx4 == -1 || idx5 == -1) return;
+ titleStart = idx4 + 21; //
+ titleLen = idx5 - titleStart;
+
+ if (idx6 != -1 && idx7 != -1) {
+ artistStart = idx6 + 21; //
+ artistLen = idx7 - artistStart;
+ }
+ if (titleLen) title.assign(ml.get() + titleStart, titleLen);
+ if (artistLen) artist.assign(ml.get() + artistStart, artistLen);
+
+ if (title.valid() && artist.valid()) {
+ streamTitle.assign(title.get());
+ streamTitle.append(" - ");
+ streamTitle.append(artist.get());
+ } else if (title.valid()) {
+ streamTitle.assign(title.get());
+ } else if (artist.valid()) {
+ streamTitle.assign(artist.get());
+ }
+ }
+
+ else if (ml.index_of("StreamTitle='") == 0) {
+ int start = 13;
+ int end = ml.index_of("';", start);
+ if (end < 0) end = ml.index_of("'", start); // fallback — when the station is transmitting incorrectly
+ if (end > start) {
+ int len = end - start;
+ streamTitle.assign(ml.get() + start, len);
+ }
+ }
+
+ else if (ml.starts_with("Grouping:")) {
+ streamTitle.assign(ml.get() + 9);
+ }
+
+ else if (ml.starts_with("#EXTINF")) {
+ // extraxt StreamTitle from m3u #EXTINF line to icy-format
+ // orig: #EXTINF:10,title="text="TitleName",artist="ArtistName"
+ // conv: StreamTitle=TitleName - ArtistName
+ // orig: #EXTINF:10,title="text=\"Spot Block End\" amgTrackId=\"9876543\"",artist=" ",url="length=\"00:00:00\""
+ // conv: StreamTitle=text=\"Spot Block End\" amgTrackId=\"9876543\" -
+
+ idx1 = ml.index_of("title=\"");
+ idx2 = ml.index_of("artist=\"");
+ if (idx1 > 0) {
+ int titleStart = idx1 + 7;
+ int idx3 = ml.index_of("\"", titleStart);
+ if (idx3 > titleStart) {
+ int titleLength = idx3 - titleStart;
+ title.assign(ml.get() + titleStart, titleLength);
+ if (title.starts_with("text=\\")) { // #EXTINF:10,title="text=\"Spot Block End\"",artist=" ",
+ titleStart += 7;
+ idx3 = ml.index_of("\\", titleStart);
+ if (idx3 > titleStart) {
+ int titleLength = idx3 - titleStart;
+ title.assign(ml.get() + titleStart, titleLength);
+ }
+ }
+ }
+ }
+ if (idx2 > 0) {
+ int artistStart = idx2 + 8;
+ int idx3 = ml.index_of("\"", artistStart);
+ if (idx3 > artistStart) {
+ int artistLength = idx3 - artistStart;
+ artist.assign(ml.get() + artistStart, artistLength);
+ if (strcmp(artist.get(), " ") == 0) artist.reset();
+ }
+ }
+ if (title.valid() && artist.valid()) {
+ streamTitle.assign(title.get());
+ streamTitle.append(" - ");
+ streamTitle.append(artist.get());
+ } else if (title.valid()) {
+ streamTitle.assign(title.get());
+ } else if (artist.valid()) {
+ streamTitle.assign(artist.get());
+ }
+ } else {
+ ;
+ }
+
+ if (ml.index_of("StreamUrl=", 0) > 0) {
+ idx1 = ml.index_of("StreamUrl=", 0);
+ idx2 = ml.index_of(";", idx1);
+ if (idx1 >= 0 && idx2 > idx1) { // StreamURL found
+ uint16_t len = idx2 - idx1;
+ sUrl.assign(ml.get() + idx1, len);
+ if (!sUrl.equals(m_streamURL.c_get())) {
+ info(*this, evt_info, "Stream URL: {}", sUrl.c_get()); // e.g. StreamUrl='http://myUrl.com'
+ m_streamURL = sUrl;
+ }
+ }
+ }
+
+ if (ml.index_of("adw_ad=", 0) > 0) {
+ idx1 = ml.index_of("adw_ad=", 0);
+ if (idx1 >= 0) { // Advertisement found
+ idx1 = ml.index_of("durationMilliseconds=", 0);
+ idx2 = ml.index_of(";", idx1);
+ if (idx1 >= 0 && idx2 > idx1) {
+ uint16_t len = idx2 - idx1;
+ ps_ptr sAdv;
+ sAdv.assign(ml.get() + idx1, len + 1);
+ // info(*this, evt_info, "Adveritsement: {}", sAdv.get());
+ uint8_t pos = 21; // remove "StreamTitle="
+ if (sAdv[pos] == '\'') pos++; // remove leading \'
+ if (sAdv[strlen(sAdv.get()) - 1] == '\'') sAdv[strlen(sAdv.get()) - 1] = '\0'; // remove trailing \'
+ info(*this, evt_info, "Advertisement: {}", sAdv.get() + pos);
+ }
+ }
+ return;
+ }
+ if (!streamTitle.valid()) return;
+
+ if (!m_streamTitle.equals(streamTitle)) {
+ m_streamTitle.clone_from(streamTitle);
+ } else {
+ return; // is equal
+ }
+
+ if (m_streamTitle.starts_with("{\"")) { // maybe a json string
+ // "{\"status\":0,\"error\":{\"info\":\"\\u042d\\u0442\\u043e \\u0440\\u0435\\u043a\\u043b\\u0430\\u043c\\u0430 \\u0438\\u043b\\u0438
+ // \\u0434\\u0436\\u0438\\u043d\\u0433\\u043b\",\"code\":201},\"result\":\"\\u042d\\u0442\\u043e \\u0440\\u0435\\u043a\\u043b\\u0430\\u043c\\u0430 \\u0438\\u043b\\u0438
+ // \\u0434\\u0436\\u0438\\u043d\\u0433\\u043b\"}\n0";
+ ps_ptr jsonIn;
+ jsonIn.clone_from(m_streamTitle);
+ jsonIn.truncate_at('\n'); // can be '{"status":1,"message":"Ok","result":"Ok","errorCode":0}\n0'
+ if (!jsonIn.isJson()) return;
+ int idx = jsonIn.index_of_icase("\"result\"");
+ if (idx < 0) return;
+ jsonIn.remove_before(idx + 10); // remove "result":
+ jsonIn.truncate_at('"'); // remove after '"' Ok","result":"Ok","errorCode":0} --> OK
+ // AUDIO_LOG_INFO("jsonIn: {}", jsonIn.c_get());
+ m_streamTitle.unicodeToUTF8(jsonIn.c_get());
+ }
+
+ info(*this, evt_streamtitle, "{}", m_streamTitle.c_get());
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::showCodecParams() {
+
+ info(*this, evt_info, "Channels: {}", getChannels());
+ info(*this, evt_info, "SampleRate (Hz): {}", getSampleRate());
+ info(*this, evt_info, "BitsPerSample: {}", getBitsPerSample());
+ // if(getBitRate()) { info(*this, evt_info, "BitRate (b/s): {}", getBitRate()); }
+ // else { info(*this, evt_info, "BitRate (b/s): N/A"); }
+
+ if (m_codec == CODEC_AAC) {
+ info(*this, evt_info, "{}", m_decoder->arg2()); // AAC Format
+ uint8_t answ = m_decoder->val2(); // SBR
+ if (answ > 0 && answ < 4) {
+ const char sbr[4][50] = {"without SBR", "upsampled SBR", "downsampled SBR", "no SBR used, but file is upsampled by a factor 2"};
+ info(*this, evt_info, "Spectral band replication: {}", sbr[answ]);
+ }
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::findNextSync(uint8_t* data, size_t len) {
+ // Mp3 and aac audio data are divided into frames. At the beginning of each frame there is a sync word.
+ // The sync word is 0xFFF. This is followed by information about the structure of the frame.
+ // Wav files have no frames
+ // Return: 0 the synchronous word was found at position 0
+ // > 0 is the offset to the next sync word
+ // -1 the sync word was not found within the block with the length len
+
+ m_fnsy.nextSync = 0;
+
+ if (m_codec == CODEC_WAV) {
+ m_f_playing = true;
+ m_fnsy.nextSync = 0;
+ }
+ if (m_codec == CODEC_MP3) {
+ m_fnsy.nextSync = m_decoder->findSyncWord(data, (int32_t)len);
+ if (m_fnsy.nextSync == -1) return len; // syncword not found, search next block
+ m_decoder->clear();
+ }
+ if (m_codec == CODEC_AAC) { m_fnsy.nextSync = m_decoder->findSyncWord(data, (int32_t)len); }
+ if (m_codec == CODEC_M4A) {
+ if (!m_M4A_chConfig) m_M4A_chConfig = 2; // guard
+ if (!m_M4A_sampleRate) m_M4A_sampleRate = 44100;
+ if (!m_M4A_objectType) m_M4A_objectType = 2;
+ m_decoder->setRawBlockParams(m_M4A_chConfig, m_M4A_sampleRate, 0, m_M4A_objectType, 0);
+ m_f_playing = true;
+ m_fnsy.nextSync = 0;
+ }
+ if (m_codec == CODEC_FLAC) {
+ m_fnsy.nextSync = m_decoder->findSyncWord(data, (int32_t)len);
+ if (m_fnsy.nextSync == -1) return len; // OggS not found, search next block
+ }
+ if (m_codec == CODEC_OPUS) {
+ m_fnsy.nextSync = m_decoder->findSyncWord(data, (int32_t)len);
+ if (m_fnsy.nextSync == -1) return len; // OggS not found, search next block
+ }
+ if (m_codec == CODEC_VORBIS) {
+ m_fnsy.nextSync = m_decoder->findSyncWord(data, len);
+ if (m_fnsy.nextSync == -1) return len; // OggS not found, search next block
+ }
+ if (m_fnsy.nextSync == -1) {
+ if (m_fnsy.swnf == 0)
+ info(*this, evt_info, "syncword not found");
+ else {
+ m_fnsy.swnf++; // syncword not found counter, can be multimediadata
+ }
+ }
+ if (m_fnsy.nextSync == 0) {
+ if (m_fnsy.swnf) {
+ info(*this, evt_info, "syncword not found {} times", m_fnsy.swnf);
+ m_fnsy.swnf = 0;
+ } else {
+ info(*this, evt_info, "syncword found at pos 0");
+ m_f_decode_ready = true;
+ }
+ }
+ if (m_fnsy.nextSync > 0) { info(*this, evt_info, "syncword found at pos {}", m_fnsy.nextSync); }
+ return m_fnsy.nextSync;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setDecoderItems() {
+ setChannels(m_decoder->getChannels());
+ setSampleRate(m_decoder->getSampleRate());
+ setBitsPerSample(m_decoder->getBitsPerSample());
+ if (m_decoder->arg1()) info(*this, evt_info, "{}", m_decoder->arg1());
+ if (m_decoder->getAudioDataStart() > 0) { // only flac-ogg, native flac sets audioDataStart in readFlacHeader()
+ m_audioDataStart = m_decoder->getAudioDataStart();
+ info(*this, evt_info, "AudioDataStart: {}", m_audioDataStart);
+ if (m_audioFileSize && !m_audioDataSize) m_audioDataSize = m_audioFileSize - m_audioDataStart;
+ }
+ if (m_lastGranulePosition && m_audioFileSize && m_i2s_items.sampleRate) {
+ m_audioFileDuration = (uint32_t)(m_lastGranulePosition / m_i2s_items.sampleRate);
+ m_nominal_bitrate = (m_audioFileSize - m_audioDataStart) * 8 / m_audioFileDuration;
+ AUDIO_LOG_DEBUG("m_nominal_bitrate {}, m_lastGranulePosition {}", m_nominal_bitrate, m_lastGranulePosition);
+ info(*this, evt_info, "Duration (s): {}", m_audioFileDuration);
+ }
+
+ if (getBitsPerSample() != 8 && getBitsPerSample() != 16 && getBitsPerSample() != 24 && getBitsPerSample() != 32) {
+ AUDIO_LOG_ERROR("Bits per sample must be 8, 16, 24 or 32 found {}", getBitsPerSample());
+ stopSong();
+ }
+
+ if (getChannels() != 1 && getChannels() != 2) {
+ AUDIO_LOG_ERROR("Num of channels must be 1 or 2, found {}", getChannels());
+ stopSong();
+ }
+ showCodecParams();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::decodeError(int8_t res, uint8_t* data, int32_t bytesDecoded) {
+ // for(int i = 0; i < 10; i++){printf("0x%02X ", data[i]);} printf("\n");
+ if (res == -100) {
+ stopSong();
+ return bytesDecoded;
+ } // serious error, e.g. decoder could not be initialized
+ if (m_codec == CODEC_AAC && res == -21) { // mono <-> stereo change
+ // According to the specification, the channel configuration is transferred in the first ADTS header and no longer changes in the entire
+ // stream. Some streams send short mono blocks in a stereo stream. e.g. http://mp3.ffh.de/ffhchannels/soundtrack.aac
+ // This triggers error -21 because the faad2 decoder cannot switch automatically.
+ m_sbyt.channels = 0;
+ if ((data[0] == 0xFF) || ((data[1] & 0xF0) == 0xF0)) {
+ int channel_config = ((data[2] & 0x01) << 2) | ((data[3] & 0xC0) >> 6);
+ if (channel_config != m_sbyt.channels) {
+ m_sbyt.channels = channel_config;
+ info(*this, evt_info, "AAC channel config changed to {}", m_sbyt.channels);
+ }
+ }
+ m_decoder->reset();
+ m_decoder->init();
+ return 0;
+ }
+ if (m_codec == CODEC_MP3) {
+ if (res == MP3Decoder::MP3_NEED_RESTART) {
+ info(*this, evt_info, "" ANSI_ESC_RED "Network error" ANSI_ESC_RESET "");
+ connecttohost(m_lastHost.get());
+ return 0;
+ }
+ }
+ m_f_playing = false; // seek for new syncword
+ if (bytesDecoded == 0) return 1; // skip one byte and seek for the next sync word
+ return bytesDecoded;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::decodeContinue(int8_t res, uint8_t* data, int32_t bytesDecoded, int32_t* bytesLeft) {
+ // if(m_codec == CODEC_MP3){ if(res == MAD_ERROR_CONTINUE) return bytesDecoded;} // nothing to play, mybe eof
+ if (m_codec == CODEC_AAC) {
+ if (res == AACDecoder::AAC_ID3_HDR) {
+ uint32_t size = ((data[6 + bytesDecoded] & 0x7F) << 21) | ((data[7 + bytesDecoded] & 0x7F) << 14) | ((data[8 + bytesDecoded] & 0x7F) << 7) | (data[9 + bytesDecoded] & 0x7F);
+ size += 10; // skip payload
+ return bytesDecoded + size;
+ if (bytesDecoded > *bytesLeft) AUDIO_LOG_ERROR("AAC input data too small bytesDecoded {} > bytesLeft {}", bytesDecoded, *bytesLeft);
+ }
+ }
+
+ if (m_codec == CODEC_FLAC) {
+ if (res == FlacDecoder::FLAC_PARSE_OGG_DONE) return bytesDecoded;
+ if (res == FlacDecoder::FLAC_DECODE_FRAMES_LOOP) return bytesDecoded;
+ } // nothing to play
+ if (m_codec == CODEC_OPUS) {
+ if (res == OpusDecoder::OPUS_PARSE_OGG_DONE) return bytesDecoded;
+ if (res == OpusDecoder::OPUS_END) return bytesDecoded;
+ } // nothing to play
+ if (m_codec == CODEC_VORBIS) {
+ if (res == VorbisDecoder::VORBIS_PARSE_OGG_DONE) return bytesDecoded;
+ if (res == VorbisDecoder::VORBIS_COMMENT_DONE) return bytesDecoded;
+ if (res == VorbisDecoder::VORBIS_COMMENT_NEED_MORE) return bytesDecoded;
+ } // nothing to play
+ if (m_codec == CODEC_MP3) {
+ if (res == MP3Decoder::MP3_NEXT_FRAME) return bytesDecoded;
+ }
+ return 0;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::sendBytes(uint8_t* data, size_t len) {
+ if (!m_f_running) return 0; // guard
+ if (!m_decoder) return 0; // guard
+
+ int res = 0;
+ int bytesDecoded = 0;
+ const char* st = NULL;
+ std::vector vec;
+ uint16_t samples_out = 0;
+ if (m_validSamples) { goto exit; } // nothing to decode, next round
+
+ m_sbyt.bytesLeft = 0;
+ m_sbyt.nextSync = 0;
+
+ if (!m_f_playing) {
+ m_sbyt.channels = 2; // assume aac stereo
+ m_sbyt.isPS = 0;
+ m_sbyt.f_setDecodeParamsOnce = true;
+ m_sbyt.nextSync = findNextSync(data, len);
+ if (m_sbyt.nextSync < 0) return len; // no syncword found
+ if (m_sbyt.nextSync == 0) { m_f_playing = true; }
+ if (m_sbyt.nextSync > 0) return m_sbyt.nextSync;
+ }
+ // m_f_playing is true at this pos
+
+ m_sbyt.bytesLeft = len;
+
+ if (m_codec == CODEC_NONE && m_playlistFormat == FORMAT_M3U8) return 0; // can happen when the m3u8 playlist is loaded
+ if (!m_f_decode_ready) return 0; // find sync first
+
+ //-----------------------------------------------------------------
+ res = m_decoder->decode(data, &m_sbyt.bytesLeft, m_outBuff.get());
+ bytesDecoded = len - m_sbyt.bytesLeft;
+ //-----------------------------------------------------------------
+
+ // res - possible values are:
+ // 0: okay, no error
+ // >= 100: the decoder needs more data
+ // < 0: there has been an error
+ // -100: serious error, stop song
+
+ if (res < 0) { return decodeError(res, data, bytesDecoded); } // Error, skip the frame...
+ if (res > 99) { return decodeContinue(res, data, bytesDecoded, &m_sbyt.bytesLeft); } // decoder needs more data...
+
+ if ((bytesDecoded == 0) && (m_codec != CODEC_VORBIS && m_codec != CODEC_FLAC)) { // unlikely framesize, exept VORBIS decodes lastSegmentTable
+ info(*this, evt_info, "framesize is 0, start decoding again");
+ m_f_playing = false; // seek for new syncword
+ // we're here because there was a wrong sync word so skip one byte and seek for the next
+ return 1;
+ }
+ // status: bytesDecoded > 0 and res >= 0
+
+ switch (m_codec) {
+ case CODEC_WAV: m_validSamples = m_decoder->getOutputSamples(); break;
+ case CODEC_MP3: m_validSamples = m_decoder->getOutputSamples(); break;
+ case CODEC_AAC:
+ m_validSamples = m_decoder->getOutputSamples() / getChannels();
+ if (!m_sbyt.isPS && m_decoder->val1()) { // only change 0 -> 1
+ m_sbyt.isPS = 1;
+ info(*this, evt_info, "Parametric Stereo");
+ } else
+ m_sbyt.isPS = m_decoder->val1();
+ break;
+ case CODEC_M4A: m_validSamples = m_decoder->getOutputSamples() / getChannels(); break;
+ case CODEC_FLAC:
+ m_validSamples = m_decoder->getOutputSamples();
+ st = m_decoder->getStreamTitle();
+ if (st) { info(*this, evt_streamtitle, "{}", st); }
+ vec = m_decoder->getMetadataBlockPicture();
+ if (vec.size() > 0) { // get blockpic data
+ // AUDIO_LOG_INFO("---------------------------------------------------------------------------");
+ // AUDIO_LOG_INFO("ogg metadata blockpicture found:");
+ // for(int i = 0; i < vec.size(); i += 2) { AUDIO_LOG_INFO("segment {:02}, pos {:07}, len {:05}", i / 2, vec[i], vec[i + 1]); }
+ // AUDIO_LOG_INFO("---------------------------------------------------------------------------");
+ info(*this, evt_image, vec);
+ }
+ break;
+ case CODEC_OPUS:
+ m_validSamples = m_decoder->getOutputSamples();
+ st = m_decoder->getStreamTitle();
+ if (st) { info(*this, evt_streamtitle, st); }
+ vec = m_decoder->getMetadataBlockPicture();
+ if (vec.size() > 0) { // get blockpic data
+ // AUDIO_LOG_INFO("---------------------------------------------------------------------------");
+ // AUDIO_LOG_INFO("ogg metadata blockpicture found:");
+ // for(int i = 0; i < vec.size(); i += 2) { AUDIO_LOG_INFO("segment {:02}, pos {:07}, len {:05}", i / 2, vec[i], vec[i + 1]); }
+ // AUDIO_LOG_INFO("---------------------------------------------------------------------------");
+ info(*this, evt_image, vec);
+ }
+ if (m_decoder->arg1()) {
+ if (m_sbyt.opus_mode != m_decoder->arg1()) {
+ info(*this, evt_info, m_decoder->arg1());
+ m_sbyt.opus_mode = m_decoder->arg1();
+ }
+ }
+ break;
+
+ case CODEC_VORBIS:
+ m_validSamples = m_decoder->getOutputSamples();
+ st = m_decoder->getStreamTitle();
+ if (st) { info(*this, evt_streamtitle, st); }
+ vec = m_decoder->getMetadataBlockPicture();
+ if (vec.size() > 0) { // get blockpic data
+ // AUDIO_LOG_INFO("---------------------------------------------------------------------------");
+ // AUDIO_LOG_INFO("ogg metadata blockpicture found:");
+ // for(int i = 0; i < vec.size(); i += 2) { AUDIO_LOG_INFO("segment {:02}, pos {:07}, len {:05}", i / 2, vec[i], vec[i + 1]); }
+ // AUDIO_LOG_INFO("---------------------------------------------------------------------------");
+ info(*this, evt_image, vec);
+ }
+ break;
+ }
+ if (m_sbyt.f_setDecodeParamsOnce && m_validSamples) {
+ m_sbyt.f_setDecodeParamsOnce = false;
+ setDecoderItems();
+ }
+ samples_out = m_validSamples;
+
+exit:
+ m_curSample = 0;
+ if (m_validSamples) {
+ calculateAudioTime(bytesDecoded, samples_out);
+ playChunk();
+ }
+ return bytesDecoded;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::calculateAudioTime(uint16_t bytesDecoderIn, uint16_t samples_decoder_out) {
+
+ // if(m_dataMode != AUDIO_LOCALFILE && m_streamType != ST_WEBFILE) return; //guard
+
+ float audioCurrentTime = 0.0;
+
+ if (m_cat.firstCall) { // first call
+ m_cat.firstCall = false;
+ m_cat.reset();
+
+ if (m_codec == CODEC_FLAC && m_decoder->getAudioFileDuration()) { // BITSTREAMINFO FLAC/OGG
+ m_audioFileDuration = m_decoder->getAudioFileDuration();
+ m_cat.nominalBitRate = (m_audioDataSize / m_decoder->getAudioFileDuration()) * 8;
+ }
+
+ if (m_nominal_bitrate) {
+ info(*this, evt_bitrate, "{}", m_nominal_bitrate);
+ m_cat.nominalBitRate = m_nominal_bitrate;
+ m_audioFileDuration = round(((float)m_audioDataSize * 8 / m_cat.nominalBitRate));
+ if (m_lastGranulePosition)
+ m_cat.tota_samples = m_lastGranulePosition;
+ else
+ m_cat.tota_samples = m_audioFileDuration * m_i2s_items.sampleRate;
+ }
+ }
+
+ m_cat.sumBytesIn += bytesDecoderIn;
+ m_cat.deltaBytesIn += bytesDecoderIn;
+ m_cat.sum_samples += samples_decoder_out;
+
+ if (m_cat.timeStamp + 50 < millis()) {
+ uint32_t t = millis(); // time tracking
+ uint32_t delta_t = t - m_cat.timeStamp; // ---"---
+ m_cat.timeStamp = t; // ---"---
+
+ if (m_cat.nominalBitRate) {
+ audioCurrentTime = (uint32_t)(m_cat.sum_samples / m_i2s_items.sampleRate);
+ } else {
+ double instBitRate = (m_cat.deltaBytesIn * 8000.0) / delta_t;
+ m_cat.counter++;
+ m_cat.avrBitRate += (instBitRate - m_cat.avrBitRate) / m_cat.counter;
+ if ((abs(m_cat.avrBitRate - m_cat.oldAvrBitrate < 50)) && !m_cat.avrBitrateStable && m_cat.avrBitRate > 1000) {
+ m_cat.brCounter++;
+ if (m_cat.brCounter > 6) {
+ m_cat.avrBitrateStable = m_cat.avrBitRate;
+ info(*this, evt_bitrate, "{}", m_cat.avrBitrateStable); // estimated
+ }
+ }
+ m_avr_bitrate = m_cat.avrBitRate;
+ audioCurrentTime = (float)m_cat.sumBytesIn * 8 / m_cat.avrBitRate;
+ m_audioFileDuration = round(((float)m_audioDataSize * 8 / m_cat.avrBitRate));
+ m_cat.oldAvrBitrate = m_avr_bitrate;
+ }
+ m_cat.deltaBytesIn = 0;
+ m_audioCurrentTime = round(audioCurrentTime);
+ }
+
+ if (m_haveNewFilePos && (m_cat.avrBitRate || m_cat.nominalBitRate)) {
+ uint32_t posWhithinAudioBlock = m_haveNewFilePos - m_audioDataStart;
+ float newTime = 0;
+ if (m_cat.nominalBitRate) {
+ newTime = (float)posWhithinAudioBlock / (m_cat.nominalBitRate / 8);
+ } else {
+ newTime = (float)posWhithinAudioBlock / (m_cat.avrBitRate / 8);
+ m_avr_bitrate = m_cat.avrBitRate;
+ }
+ m_audioCurrentTime = round(newTime);
+ m_cat.sumBytesIn = posWhithinAudioBlock;
+ m_haveNewFilePos = 0;
+ m_cat.syltIdx = 0;
+ if (m_syltLines.size()) {
+ while (m_cat.syltIdx < m_syltLines.size()) {
+ if (m_audioCurrentTime * 1000 < m_syltTimeStamp[m_cat.syltIdx]) break;
+ m_cat.syltIdx++;
+ }
+ if (m_cat.syltIdx) m_cat.syltIdx--;
+ }
+ }
+
+ if (m_syltLines.size()) {
+ // AUDIO_LOG_INFO("{}", audioCurrentTime * 1000); // ms
+ if (m_cat.syltIdx >= m_syltLines.size()) return;
+ if (m_audioCurrentTime * 1000 > m_syltTimeStamp[m_cat.syltIdx]) {
+ info(*this, evt_lyrics, "{}", m_syltLines[m_cat.syltIdx].c_get());
+ m_cat.syltIdx++;
+ }
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::i2s_config() {
+ esp_err_t result = ESP_OK;
+ // -------- I2S configuration -------------------------------------------------------------------------------------------
+ memset(&m_i2s_chan_cfg, 0, sizeof(i2s_chan_config_t));
+#if (ESP_IDF_VERSION_MAJOR < 6)
+ m_i2s_chan_cfg.id = (i2s_port_t)m_i2s_items.i2s_num; // I2S_NUM_AUTO, I2S_NUM_0, I2S_NUM_1
+#else
+ m_i2s_chan_cfg.id = m_i2s_items.i2s_num; // I2S_NUM_AUTO, I2S_NUM_0, I2S_NUM_1
+#endif
+ m_i2s_chan_cfg.role = I2S_ROLE_MASTER; // I2S controller master role, bclk and lrc signal will be set to output
+ m_i2s_chan_cfg.dma_desc_num = settings.DMA_DESC_NUM; // number of DMA buffer
+ m_i2s_chan_cfg.dma_frame_num = settings.DMA_FRAME_NUM; // I2S frame number in one DMA buffer.
+ m_i2s_chan_cfg.auto_clear = true; // i2s will always send zero automatically if no data to send
+ m_i2s_chan_cfg.allow_pd = false;
+ m_i2s_chan_cfg.intr_priority = 2;
+ result = i2s_new_channel(&m_i2s_chan_cfg, &m_i2s_tx_handle, NULL);
+ if (result != ESP_OK) { // ESP_ERR_INVALID_ARG?
+ AUDIO_LOG_ERROR("I2S channel: invalid argument");
+ return false;
+ }
+
+ memset(&m_i2s_std_cfg, 0, sizeof(i2s_std_config_t));
+ m_i2s_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO); // Set to enable bit shift in Philips mode
+ m_i2s_std_cfg.gpio_cfg.bclk = I2S_GPIO_UNUSED; // BCLK, Assignment in setPinout()
+ m_i2s_std_cfg.gpio_cfg.din = I2S_GPIO_UNUSED; // not used
+ m_i2s_std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED; // DOUT, Assignment in setPinout()
+ m_i2s_std_cfg.gpio_cfg.mclk = I2S_GPIO_UNUSED; // MCLK, Assignment in setPinout()
+ m_i2s_std_cfg.gpio_cfg.ws = I2S_GPIO_UNUSED; // LRC, Assignment in setPinout()
+ m_i2s_std_cfg.gpio_cfg.invert_flags.mclk_inv = false;
+ m_i2s_std_cfg.gpio_cfg.invert_flags.bclk_inv = false;
+ m_i2s_std_cfg.gpio_cfg.invert_flags.ws_inv = false;
+ m_i2s_std_cfg.clk_cfg.sample_rate_hz = m_i2s_items.sampleRate;
+ m_i2s_std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_DEFAULT;
+ m_i2s_std_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256;
+ result = i2s_channel_init_std_mode(m_i2s_tx_handle, &m_i2s_std_cfg);
+ if (result != ESP_OK) {
+ if (result == ESP_ERR_INVALID_ARG) AUDIO_LOG_ERROR("invalid i2s configuration or i2s not standard mode");
+ if (result == ESP_ERR_INVALID_STATE) AUDIO_LOG_ERROR("This i2s channel is not initialized or not stopped");
+ return false;
+ }
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setPinout(uint8_t BCLK, uint8_t LRC, uint8_t DOUT, int8_t MCLK) {
+
+ i2s_std_gpio_config_t gpio_cfg = {};
+ bool result = i2s_config();
+
+ gpio_cfg.bclk = (gpio_num_t)BCLK;
+ gpio_cfg.din = (gpio_num_t)I2S_GPIO_UNUSED;
+ gpio_cfg.dout = (gpio_num_t)DOUT;
+ gpio_cfg.mclk = (gpio_num_t)MCLK;
+ gpio_cfg.ws = (gpio_num_t)LRC;
+
+#if (ESP_ARDUINO_VERSION_MAJOR < 3)
+ AUDIO_LOG_ERROR("Arduino Version must be 3.0.0 or higher!");
+ result = false;
+ goto exit;
+#endif
+
+ m_f_psramFound = psramInit();
+ if (!m_f_psramFound) {
+ AUDIO_LOG_ERROR("PSRAM not found");
+ result = false;
+ goto exit;
+ }
+
+ m_outBuff.alloc_array(m_outbuffSize, "m_outBuff");
+ m_resamplesBuff.alloc_array(m_resamplesBuffSize, "m_resamplesBuff");
+ m_vu_items.delay_l.alloc_array(m_i2s_chan_cfg.dma_desc_num * m_i2s_chan_cfg.dma_frame_num, "delay_l");
+ m_vu_items.delay_r.alloc_array(m_i2s_chan_cfg.dma_desc_num * m_i2s_chan_cfg.dma_frame_num, "delay_r");
+ m_fft_items.buffer.alloc_array(m_fft_items.SIZE, "buffer");
+ m_fft_items.window.alloc_array(m_fft_items.SIZE, "window");
+ m_fft_items.work.alloc_array(m_fft_items.SIZE * 2, "work");
+ m_metadataBuff.alloc(4096 + 1, "m_metadataBuff"); // max 4096 + 1 for null terminator, just to make library code 'safe'
+ m_httpRespHdrBuff.alloc(4096, "m_httpRespHdrBuff"); // enough space to store http response header
+
+ if (!m_outBuff.valid() || !m_vu_items.delay_l.valid() || !m_vu_items.delay_r.valid() || !m_resamplesBuff.valid() || !m_fft_items.buffer.valid() || !m_fft_items.buffer.valid() ||
+ !m_fft_items.work.valid()) {
+ result = false;
+ goto exit;
+ }
+
+ //---------------------------- FFT INIT-----------------------------------------
+ for (int i = 0; i < m_fft_items.SIZE; i++) { // Hann window
+ m_fft_items.window[i] = 0.5f * (1.0f - cosf(2.0f * M_PI * i / (m_fft_items.SIZE - 1)));
+ }
+
+ if (dsps_fft2r_init_fc32(nullptr, m_fft_items.SIZE) == ESP_OK) {
+ m_fft_items.initialized = true;
+ } else {
+ AUDIO_LOG_ERROR("FFT init failed (size={})", m_fft_items.SIZE);
+ result = false;
+ goto exit;
+ }
+ //-----------------------------------------------------------------------------
+
+ I2Sstop();
+ if (i2s_channel_reconfig_std_gpio(m_i2s_tx_handle, &gpio_cfg) != ESP_OK) {
+ result = false;
+ goto exit;
+ }
+ I2Sstart();
+
+exit:
+
+ calculateVolumeLimits(); // first init, vol = 21, vol_steps = 21
+ startAudioTask();
+
+ m_f_I2S_init = result;
+ return m_f_I2S_init;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getFileSize() { // returns the size of webfile or local file
+ if (!m_audiofile) {
+ if (m_audioFileSize > 0) { return m_audioFileSize; }
+ return 0;
+ }
+ return m_audiofile.size();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getAudioFileDuration() {
+ if (!getBitRate()) return 0;
+ if (m_playlistFormat == FORMAT_M3U8) return 0;
+ if (!m_audioDataSize) return 0;
+ return m_audioFileDuration;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getAudioCurrentTime() { // return current time in seconds
+ return m_audioCurrentTime;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getAudioFilePosition() {
+ if (!m_f_stream) return 0;
+ if ((m_dataMode != AUDIO_LOCALFILE) && (m_streamType != ST_WEBFILE)) {
+ AUDIO_LOG_WARN("audio is not a file");
+ return 0;
+ }
+ return m_audioFilePosition - inBufferFilled();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setAudioFilePosition(uint32_t pos) {
+ if (!m_f_stream) return false;
+ if ((m_dataMode != AUDIO_LOCALFILE) && (m_streamType != ST_WEBFILE)) {
+ AUDIO_LOG_WARN("audio is not a file");
+ return false;
+ }
+ if ((m_streamType == ST_WEBFILE) && (!m_f_acceptRanges)) {
+ AUDIO_LOG_WARN("server does not accept ranges");
+ return false;
+ }
+ if (pos > m_audioDataStart + m_audioDataSize) {
+ AUDIO_LOG_WARN("given position is too large");
+ return false;
+ }
+ if (pos < m_audioDataStart) {
+ AUDIO_LOG_WARN("set audiodatastart at {}", m_audioDataStart);
+ m_resumeFilePos = m_audioDataStart;
+ }
+ m_resumeFilePos = pos;
+
+ /* m_cat.tota_samples m_cat.sum_samples
+ ------------------ = ----------------------------------
+ m_audioDataSize m_resumeFilePos - m_audioDataStart */
+
+ m_cat.sum_samples = (float)m_cat.tota_samples * ((float)(m_resumeFilePos - m_audioDataStart) / m_audioDataSize);
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setAudioPlayTime(uint16_t sec) {
+ // e.g. setAudioPlayTime(300) sets the pointer at pos 5 min
+ if ((m_dataMode != AUDIO_LOCALFILE) && (m_streamType != ST_WEBFILE)) return false; // guard
+ if (!getBitRate()) return false; // guard
+ if (!m_f_running) return false; // guard
+
+ if (sec > getAudioFileDuration()) {
+ AUDIO_LOG_WARN("setAudioPlayTime: {}s >= audioFileDuration: {}s -> stopSong", sec, getAudioFileDuration());
+ stopSong();
+ return false;
+ }
+ uint32_t filepos = m_audioDataStart + (getBitRate() * sec / 8);
+ m_resumeFilePos = filepos;
+ m_cat.sum_samples = (float)m_cat.tota_samples * ((float)(m_resumeFilePos - m_audioDataStart) / m_audioDataSize);
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setTimeOffset(int sec) { // fast forward or rewind the current position in seconds
+ // info(*this, evt_info, "time offset {} sec", sec);
+ if ((m_dataMode != AUDIO_LOCALFILE) && (m_streamType != ST_WEBFILE)) {
+ AUDIO_LOG_WARN("{}", "not a file");
+ return false;
+ } // guard
+ if (!getBitRate()) return false; // guard
+ if (!m_f_running) return false; // guard
+
+ int32_t newTime = getAudioCurrentTime() + sec;
+ if (newTime < 0) newTime = 0;
+ if (newTime > getAudioFileDuration()) {
+ stopSong();
+ return true;
+ }
+
+ uint32_t oneSec = getBitRate() / 8; // bytes decoded in one sec
+ int32_t offset = oneSec * sec; // bytes to be wind/rewind
+ int32_t pos = m_audioFilePosition - inBufferFilled();
+ pos += offset;
+ m_resumeFilePos = pos;
+ m_cat.sum_samples = (float)m_cat.tota_samples * ((float)(m_resumeFilePos - m_audioDataStart) / m_audioDataSize);
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setVolumeSteps(uint8_t steps) {
+ // clamp new steps
+ uint8_t new_steps = max(steps, (uint8_t)21);
+ uint8_t old_steps = m_audio_items.volume_steps;
+
+ if (new_steps == old_steps) return;
+
+ // ratio OLD → NEW
+ float corr = (float)new_steps / (float)old_steps;
+
+ // scale target volume
+ float new_volume = (float)m_audio_items.volume * corr;
+
+ // also scale the current volume (very important!)
+ m_audio_items.cur_volume *= corr;
+
+ // take over + clamp
+ m_audio_items.volume_steps = new_steps;
+
+ m_audio_items.volume = (uint8_t)lroundf(new_volume);
+ if (m_audio_items.volume > new_steps) m_audio_items.volume = new_steps;
+
+ if (m_audio_items.cur_volume > (float)new_steps) m_audio_items.cur_volume = (float)new_steps;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint8_t Audio::getVolumeSteps() {
+ return m_audio_items.volume_steps;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setMute(bool mute) {
+ m_audio_items.mute = mute;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::getMute() {
+ return m_audio_items.mute;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::audioFileRead() {
+ int res = -1;
+ if (m_dataMode == AUDIO_LOCALFILE) {
+ res = m_audiofile.read();
+ if (res >= 0) m_audioFilePosition++;
+ } else {
+ res = m_client->read();
+ if (res >= 0) m_audioFilePosition++;
+ }
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::audioFileRead(uint16_t timeout_ms) {
+ int32_t res = -1;
+ uint32_t timeout = millis() + timeout_ms;
+
+ while (true) {
+ res = audioFileRead();
+ if (res >= 0) break;
+ if (timeout < millis()) {
+ AUDIO_LOG_ERROR("timeout");
+ return -1;
+ }
+ vTaskDelay(10);
+ }
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::audioFileRead(uint8_t* buff, size_t len) {
+ if (buff && len == 0) return 0; // nothing to do
+ int32_t readed_bytes = 0;
+ uint32_t offset = 0;
+ int res = -1;
+
+ // read len
+ if (m_dataMode == AUDIO_LOCALFILE) {
+ readed_bytes = m_audiofile.read(buff + offset, len);
+ if (readed_bytes >= 0) {
+ m_audioFilePosition += readed_bytes;
+ len -= readed_bytes;
+ offset += readed_bytes;
+ res = offset;
+ }
+ } else {
+ readed_bytes = m_client->read(buff + offset, len);
+ if (readed_bytes > 0) {
+ m_audioFilePosition += readed_bytes;
+ len -= readed_bytes;
+ offset += readed_bytes;
+ res = offset;
+ }
+ }
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::audioFileRead(uint8_t* buff, size_t len, uint16_t timeout_ms) {
+
+ uint32_t timeout = millis() + timeout_ms;
+ int32_t bytes_has_read = 0;
+ uint8_t cnt = 0;
+ while (bytes_has_read < len) {
+ int32_t res = audioFileRead(buff + bytes_has_read, len - bytes_has_read);
+ if (res <= 0) { vTaskDelay(10); }
+ if (res > 0) { bytes_has_read += res; }
+ if (timeout < millis()) {
+ AUDIO_LOG_ERROR("timeout, len: {} != bytes_has_read: {}", len, bytes_has_read);
+ return -1;
+ }
+ AUDIO_LOG_DEBUG("buffFillValue {}, res {}, bytes_has_read {}", len, res, bytes_has_read);
+ }
+ AUDIO_LOG_DEBUG("len: {} != bytes_has_read: {}", len, bytes_has_read);
+ return bytes_has_read;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::audioFileSeek(uint32_t position, size_t len) {
+ int32_t res = -1;
+
+ if (m_dataMode == AUDIO_LOCALFILE) {
+ uint32_t actualPos = m_audiofile.position(); // starts with 1
+ if (actualPos != m_audioFilePosition) {
+ AUDIO_LOG_DEBUG("actualPos != m_audioFilePosition {} != {}", actualPos, m_audioFilePosition);
+ m_audioFilePosition = actualPos;
+ }
+ if (!m_audiofile) return -1;
+ if (position > m_audiofile.size()) {
+ AUDIO_LOG_WARN("position larger than size {} > {}", position, m_audiofile.size());
+ position = m_audiofile.size();
+ }
+ bool r = m_audiofile.seek(position);
+ m_audioFilePosition = m_audiofile.position();
+ if (r == false) {
+ AUDIO_LOG_ERROR("something went wrong");
+ return -1;
+ } else {
+ return position;
+ }
+ } else {
+ if (m_f_acceptRanges) {
+ bool r;
+ if (len == 0) len = UINT32_MAX;
+ r = httpRange(position, len);
+ if (res == false) {
+ AUDIO_LOG_ERROR("http range request was not successful");
+ return 0;
+ }
+ r = parseHttpRangeHeader();
+ if (r == false) {
+ AUDIO_LOG_ERROR("http range response was not successful");
+ return 0;
+ }
+ m_audioFilePosition = position;
+ return position;
+ }
+ }
+ return res;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::fsRange(uint32_t range) {
+
+ if ((m_dataMode == AUDIO_LOCALFILE) && !m_audiofile) {
+ AUDIO_LOG_WARN("{}", "local file not accessibble");
+ return false;
+ } // guard
+ uint32_t startAB = m_audioDataStart; // audioblock begin
+ uint32_t endAB = m_audioDataStart + m_audioDataSize; // audioblock end
+ if (range < (int32_t)startAB) { range = startAB; }
+ if (range >= (int32_t)endAB) { range = endAB; }
+
+ m_validSamples = 0;
+ m_resumeFilePos = range; // used in processLocalFile()
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setSampleRate(uint32_t sampRate) {
+
+ if (!sampRate) return false;
+ if (sampRate < 8000) {
+ AUDIO_LOG_WARN("Sample rate must not be smaller than 8kHz, found: {}", sampRate);
+ return false;
+ }
+ m_i2s_items.sampleRate = sampRate;
+ reconfigI2S();
+ IIR_calculateCoefficients();
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getSampleRate() {
+ return m_i2s_items.sampleRate;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setBitsPerSample(int bits) {
+ if ((bits != 32) && (bits != 24) && (bits != 16) && (bits != 8)) return false;
+ m_bitsPerSample = bits;
+ return true;
+}
+uint8_t Audio::getBitsPerSample() {
+ return m_bitsPerSample;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::setChannels(int ch) {
+ m_channels = ch;
+ return true;
+}
+uint8_t Audio::getChannels() {
+ if (m_channels == 0) { // this should not happen! #209
+ m_channels = 2;
+ }
+ return m_channels;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getBitRate() {
+ if (m_nominal_bitrate) return m_nominal_bitrate;
+ return m_avr_bitrate;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+uint64_t Audio::getLastGranulePosition(uint8_t codec) {
+ if (codec != CODEC_OPUS && codec != CODEC_VORBIS) return 0; // only opus or vorbis
+ if (m_audioFileSize == 0) { return 0; } // only files
+
+ uint64_t granulePos = 0;
+ ps_ptr buff;
+ buff.alloc(UINT16_MAX, "buff");
+
+ int rangeStart = m_audioFileSize - UINT16_MAX - 1;
+ audioFileSeek(rangeStart, UINT16_MAX);
+ audioFileRead((uint8_t*)buff.get(), UINT16_MAX, 3000);
+
+ int32_t pos = buff.last_special_index_of("OggS", UINT16_MAX);
+ if (buff[pos + 5] & 0x04) { // is last page;
+ for (int j = 0; j < 8; j++) { granulePos |= ((uint64_t)buff[pos + 6 + j] << (j * 8)); }
+ }
+ AUDIO_LOG_DEBUG("granulePos {}", granulePos);
+
+ m_resumeFilePos = 0;
+ return granulePos;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setI2SCommFMT_LSB(bool commFMT) {
+ // false: I2S communication format is by default I2S_COMM_FORMAT_I2S_MSB, right->left (AC101, PCM5102A)
+ // true: changed to I2S_COMM_FORMAT_I2S_LSB for some DACs (PT8211)
+ // Japanese or called LSBJ (Least Significant Bit Justified) format
+
+ if (commFMT) {
+ info(*this, evt_info, "commFMT = LSBJ (Least Significant Bit Justified)");
+ } else {
+ info(*this, evt_info, "commFMT = Philips");
+ }
+ m_i2s_items.commFMT = commFMT;
+ reconfigI2S();
+ return;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::reconfigI2S() {
+
+ i2s_channel_disable(m_i2s_tx_handle);
+
+ if (m_i2s_items.commFMT) {
+ m_i2s_std_cfg.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO);
+ } else {
+ m_i2s_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO);
+ }
+ i2s_channel_reconfig_std_slot(m_i2s_tx_handle, &m_i2s_std_cfg.slot_cfg);
+
+ if (m_output_sr) {
+ m_resampler.phase = 0; // prepare resampler
+ m_resampler.phaseStep = ((uint64_t)m_i2s_items.sampleRate << 32) / m_output_sr;
+ m_resampler.g_lpCoeffs = makeButterworthLPF_Q31(m_i2s_items.sampleRate);
+ m_resampler.lpLeft = {};
+ m_resampler.lpRight = {};
+ m_i2s_std_cfg.clk_cfg.sample_rate_hz = m_output_sr;
+ AUDIO_LOG_DEBUG("output samplerate is {}", m_i2s_std_cfg.clk_cfg.sample_rate_hz);
+ } else {
+ m_i2s_std_cfg.clk_cfg.sample_rate_hz = m_i2s_items.sampleRate;
+ }
+ i2s_channel_reconfig_std_clock(m_i2s_tx_handle, &m_i2s_std_cfg.clk_cfg);
+ i2s_channel_enable(m_i2s_tx_handle);
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::calculateVUlevel(int32_t* sample) { // Envelope-Follower
+
+ uint16_t DELAY_BUFFER_SIZE = m_i2s_chan_cfg.dma_desc_num * m_i2s_chan_cfg.dma_frame_num; // Runtime in I2S-DMA
+ // delay line
+ m_vu_items.delay_l[m_vu_items.delay_line_index] = sample[LEFTCHANNEL];
+ m_vu_items.delay_r[m_vu_items.delay_line_index] = sample[RIGHTCHANNEL];
+ m_vu_items.delay_line_index++;
+ if (m_vu_items.delay_line_index == DELAY_BUFFER_SIZE) m_vu_items.delay_line_index = 0;
+
+ int16_t pos = m_vu_items.delay_line_index - 1;
+ if (pos == -1) pos = DELAY_BUFFER_SIZE - 1;
+
+ // FFT buffer
+ float mono = 0.5f * (float)((m_vu_items.delay_l[pos] >> 20) + (m_vu_items.delay_r[pos] >> 20));
+
+ // --- FFT analyzer AGC ---
+ constexpr float TARGET = 0.1f; // gewünschte RMS-Amplitude
+ constexpr float ATTACK = 0.05f;
+ constexpr float RELEASE_FFT = 0.005f;
+
+ float level = fabsf(mono);
+
+ if (level > 1e-6f) {
+ float desired = TARGET / level;
+ if (desired < m_fft_items.gain)
+ m_fft_items.gain += ATTACK * (desired - m_fft_items.gain);
+ else
+ m_fft_items.gain += RELEASE_FFT * (desired - m_fft_items.gain);
+ }
+ if (fabsf(mono) < 1e-4f) mono = 0.0f;
+ mono *= m_fft_items.gain;
+
+ m_fft_items.buffer[m_fft_items.buffer_index] = mono;
+ m_fft_items.buffer_index++;
+ if (m_fft_items.buffer_index == m_fft_items.SIZE) m_fft_items.buffer_index = 0;
+
+ uint8_t l = 0, r = 0;
+ l = abs(m_vu_items.delay_l[pos] >> 23);
+ r = abs(m_vu_items.delay_r[pos] >> 23);
+
+ // Attack immediately
+ constexpr float RELEASE = 1.0f; // the bigger, the more sluggish
+ if (l > m_vu_items.left) {
+ m_vu_items.left = l;
+ } else if (m_vu_items.left > RELEASE) {
+ m_vu_items.left -= RELEASE;
+ }
+ if (r > m_vu_items.right) {
+ m_vu_items.right = r;
+ } else if (m_vu_items.right > RELEASE) {
+ m_vu_items.right -= RELEASE;
+ }
+
+ // LEFT
+ if (m_vu_items.left > m_vu_items.left_peak) {
+ m_vu_items.left_peak = m_vu_items.left;
+ m_vu_items.left_hold = settings.PEAK_HOLD_SAMPLES;
+ } else {
+ if (m_vu_items.left_hold > 0) {
+ m_vu_items.left_hold--;
+ } else if (m_vu_items.left_peak > settings.PEAK_RELEASE) {
+ m_vu_items.left_peak -= settings.PEAK_RELEASE;
+ }
+ }
+
+ // RIGHT
+ if (m_vu_items.right > m_vu_items.right_peak) {
+ m_vu_items.right_peak = m_vu_items.right;
+ m_vu_items.right_hold = settings.PEAK_HOLD_SAMPLES;
+ } else {
+ if (m_vu_items.right_hold > 0) {
+ m_vu_items.right_hold--;
+ } else if (m_vu_items.right_peak > settings.PEAK_RELEASE) {
+ m_vu_items.right_peak -= settings.PEAK_RELEASE;
+ }
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint16_t Audio::getVUlevel() {
+ if (!m_f_running) return 0;
+ AUDIO_LOG_DEBUG("{}", m_vu_items.left);
+ // avg 0 ... 255 MSB LSB
+ return ((uint8_t)m_vu_items.right_peak << 8) + (uint8_t)m_vu_items.left_peak; // returns rrrrrrrrllllllll
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setTone(float gainLowPass, float gainBandPass, float gainHighPass) {
+
+ // gainLowPass set between -12 ... +12 dB
+ // gainBandPass set between -12 ... +12 dB
+ // gainHighPass set between -12 ... +12 dB
+
+ m_audio_items.gain_ls_db = fminf(fmaxf(gainLowPass, -12.0f), 12.0f);
+ m_audio_items.gain_peq_db = fminf(fmaxf(gainBandPass, -12.0f), 12.0f);
+ m_audio_items.gain_hs_db = fminf(fmaxf(gainHighPass, -12.0f), 12.0f);
+
+ IIR_calculateCoefficients();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::forceMono(bool m) { // #100 mono option
+ m_f_forceMono = m; // false stereo, true mono
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setOutputSampleRate(OutputSR_t sr) { //
+ if (sr == SR_44100) {
+ m_output_sr = SR_44100; // output sr is always 44.1KHz
+ } else if (sr == SR_48000) {
+ m_output_sr = SR_48000; // output sr is always 48KHz
+ } else {
+ m_output_sr = SR_ORIGIN; // output sr is source sr
+ }
+ reconfigI2S();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setBalance(float balance) { // left -16.0dB ... 0dB ... -16.0dB right
+ m_audio_items.balance = fminf(fmaxf(balance, -16.0f), 16.0f);
+ calculateVolumeLimits();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setVolume(uint8_t volume, uint8_t curve) {
+
+ m_audio_items.volume = min(volume, m_audio_items.volume_steps);
+
+ // curve is obsolete
+ calculateVolumeLimits();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::setVolumeCurve(VolumeCurveFn curve) {
+
+ // user defined curve, set it in youe code e.g. somewhere in setup()
+ // t is volume / steps; (range 0.0f … 1.0f)
+ // should return a value between -60.0[dB] and 0.0[dB]
+ // example:
+ // audio.setVolumeCurve([](float t) {
+ // return -60.0f + 60.0f * t * t;
+ // });
+
+ m_volumeCurve = curve;
+ calculateVolumeLimits();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint8_t Audio::getVolume() {
+ return m_audio_items.volume;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::getI2sPort() {
+ return m_i2s_items.i2s_num;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::gain_ramp() {
+ settings.VOL_FADING_SPEED = std::clamp(settings.VOL_FADING_SPEED, 1.0f, 100.0f); // avoid div0
+ // determine goal
+ float target = m_audio_items.mute ? 0.0f : (float)m_audio_items.volume;
+
+ // maximum change per tick
+ float step = (float)m_audio_items.volume_steps / settings.VOL_FADING_SPEED;
+
+ float diff = target - m_audio_items.cur_volume;
+
+ if (fabsf(diff) <= step) {
+ // Goal achieved (no oscillation!)
+ m_audio_items.cur_volume = target;
+ } else {
+ // Ramp
+ m_audio_items.cur_volume += (diff > 0.0f ? step : -step);
+ }
+
+ // Security-Clamp
+ if (m_audio_items.cur_volume < 0.0f)
+ m_audio_items.cur_volume = 0.0f;
+ else if (m_audio_items.cur_volume > m_audio_items.volume_steps)
+ m_audio_items.cur_volume = m_audio_items.volume_steps;
+
+ calculateVolumeLimits();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::calculateVolumeLimits() { // is calculated when the volume or balance changes
+
+ constexpr float BALANCE_DB = -16.0f;
+ constexpr float MIN_DB = -60.0f; // quiet
+ constexpr float MAX_DB = 0.0f; // full level
+
+ auto volumeToLinear = [&](float volume, uint8_t steps) {
+ if (volume <= 0.0f) {
+ return 0.0f; // real silence
+ }
+
+ float t = volume / (float)steps; // 0.0f … 1.0f
+ t = fminf(fmaxf(t, 0.0f), 1.0f);
+
+ // float dB = MIN_DB + t * (MAX_DB - MIN_DB);
+ float dB = m_volumeCurve ? m_volumeCurve(t) : (-112.0f * t * t * t + 172.0f * t * t + MIN_DB);
+ dB = fminf(fmaxf(dB, MIN_DB), MAX_DB);
+
+ return powf(10.0f, dB / 20.0f);
+ };
+
+ float vol = volumeToLinear(m_audio_items.cur_volume, m_audio_items.volume_steps);
+
+ float l_db = 0.0f;
+ float r_db = 0.0f;
+
+ if (m_audio_items.balance > 0.0f) { // emphasize on the right → quieter on the left
+ l_db = BALANCE_DB * ((float)m_audio_items.balance / 16.0f);
+ } else if (m_audio_items.balance < 0.0f) { // emphasize on the left → quieter on the right
+ r_db = BALANCE_DB * ((float)-m_audio_items.balance / 16.0f);
+ }
+
+ m_audio_items.limiter[LEFTCHANNEL] = vol * powf(10.0f, l_db / 20.0f);
+ m_audio_items.limiter[RIGHTCHANNEL] = vol * powf(10.0f, r_db / 20.0f);
+ AUDIO_LOG_DEBUG("m_limiter[LEFTCHANNEL] {}, m_limiter[RIGHTCHANNEL] {}", m_audio_items.limiter[LEFTCHANNEL], m_audio_items.limiter[RIGHTCHANNEL]);
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::processSpectrum() {
+
+ // --- 10 Hz update ---
+ uint32_t now = millis();
+ if (now - m_fft_items.last_ms < 100) return;
+ m_fft_items.last_ms = now;
+
+ // --- Window + real → complex ---
+ for (int i = 0; i < m_fft_items.SIZE; i++) {
+ m_fft_items.work[2 * i] = m_fft_items.buffer[i] * m_fft_items.window[i];
+ m_fft_items.work[2 * i + 1] = 0.0f;
+ }
+
+ // --- FFT ---
+ dsps_fft2r_fc32(m_fft_items.work.get(), m_fft_items.SIZE);
+ dsps_bit_rev_fc32(m_fft_items.work.get(), m_fft_items.SIZE);
+ dsps_cplx2reC_fc32(m_fft_items.work.get(), m_fft_items.SIZE);
+
+ const float bin_hz = (float)m_i2s_items.sampleRate / m_fft_items.SIZE;
+ const float norm = 2.0f / m_fft_items.SIZE;
+
+ // --- 5 internal bands ---
+ float band[m_fft_items.BANDS] = {0};
+ int bins[m_fft_items.BANDS] = {0};
+
+ for (int i = 1; i < m_fft_items.SIZE / 2; i++) {
+
+ float re = m_fft_items.work[2 * i];
+ float im = m_fft_items.work[2 * i + 1];
+ float mag = sqrtf(re * re + im * im) * norm;
+ float f = i * bin_hz;
+
+ int b = -1;
+ if (f < 250)
+ b = 0;
+ else if (f < 400)
+ b = -1;
+ else if (f < 700)
+ b = 1;
+ else if (f < 1000)
+ b = -1;
+ else if (f < 1550)
+ b = 2;
+ else if (f < 2200)
+ b = -1;
+ else if (f < 4250)
+ b = 3;
+ else if (f < 6300)
+ b = -1;
+ else if (f < 11150)
+ b = 4;
+ else if (f < 16000)
+ b = 5;
+ else
+ b = -1;
+
+ if (b >= 0) {
+ band[b] += mag * mag;
+ bins[b]++;
+ }
+ }
+
+ // --- RMS + weighting ---
+ for (int i = 0; i < m_fft_items.BANDS; i++) {
+ if (bins[i])
+ band[i] = sqrtf(band[i] / bins[i]);
+ else
+ band[i] = 0.0f;
+ }
+
+ // band weighting (psychoacoustic / UI)
+ band[0] *= 0.5f;
+ band[1] *= 0.8f;
+ band[2] *= 3.0f;
+ band[3] *= 2.8f;
+ band[4] *= 1.5f;
+ band[5] *= 1.5f;
+
+ // log scale
+ for (int i = 0; i < m_fft_items.BANDS; i++) { band[i] = log10f(band[i] + 1e-6f); }
+
+ // --- temporal smoothing (only displayed bands) ---
+ auto smooth = [](float old, float in) {
+ constexpr float ATTACK = 0.6f;
+ constexpr float RELEASE = 0.6f;
+ return (in > old) ? old + ATTACK * (in - old) : old + RELEASE * (in - old);
+ };
+
+ for (int i = 0; i < m_fft_items.BANDS; i++) { m_fft_items.spec_smooth[i] = smooth(m_fft_items.spec_smooth[i], band[i]); }
+
+ // --- map to 0..255 using dB window ---
+ constexpr float DB_MIN = -50.0f;
+ constexpr float DB_MAX = 0.0f;
+
+ for (int i = 0; i < m_fft_items.BANDS; i++) {
+
+ float db = m_fft_items.spec_smooth[i] * 20.0f;
+
+ if (db < DB_MIN) db = DB_MIN;
+ if (db > DB_MAX) db = DB_MAX;
+
+ float norm = (db - DB_MIN) / (DB_MAX - DB_MIN);
+
+ m_fft_items.spectrum[i] = (uint8_t)(norm * 255.0f);
+ }
+ // AUDIO_LOG_INFO("{:4}, {:4}, {:4}, {:4}, {:4}, {:4} ", m_fft_items.spectrum[0], m_fft_items.spectrum[1], m_fft_items.spectrum[2], m_fft_items.spectrum[3], m_fft_items.spectrum[4],
+ // m_fft_items.spectrum[3]);
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::Gain(int32_t* sample) {
+ /* important: these multiplications must all be signed ints, or the result will be invalid */
+ int32_t* s32 = (int32_t*)sample;
+ s32[LEFTCHANNEL] *= m_audio_items.limiter[LEFTCHANNEL];
+ s32[RIGHTCHANNEL] *= m_audio_items.limiter[RIGHTCHANNEL];
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::inBufferFilled() {
+ // current audio input buffer fillsize in bytes
+ return InBuff.bufferFilled();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::inBufferFree() {
+ // current audio input buffer free space in bytes
+ return InBuff.freeSpace();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::getInBufferSize() {
+ // current audio input buffer size in bytes
+ return InBuff.getBufsize();
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::stereo2mono(int32_t* buff, uint16_t validSamples) {
+
+ for (uint16_t i = 0; i < validSamples * 2; i += 2) {
+ int64_t l = buff[i];
+ int64_t r = buff[i + 1];
+ int32_t m = (int32_t)((l + r) >> 1); // average, without overflow
+ buff[i] = m; // Left
+ buff[i + 1] = m; // Right
+ }
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+// *** D i g i t a l b i q u a d r a t i c f i l t e r ***
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::IIR_calculateCoefficients() { // Infinite Impulse Response (IIR) filters
+
+ AUDIO_LOG_DEBUG("gain gain_ls_db {}, gain gain_peq_db {}, gain gain_hs_db {}", m_audio_items.gain_ls_db, m_audio_items.gain_peq_db, m_audio_items.gain_hs_db);
+
+ const float FcLS = settings.FREQ_LS_HZ; // Frequency LowShelf(Hz)
+ const float FcPKEQ = settings.FREQ_PEAK_HZ; // Frequency PeakEQ(Hz)
+ const float FcHS = settings.FREQ_HS_HZ; // Frequency HighShelf(Hz)
+ const float QS = settings.QUALITY_SLOPE; // Quality Slope (Shelf)
+
+ float normFreqLS = FcLS / m_i2s_items.sampleRate; // filter cut off frequency
+ float normFreqPEQ = FcPKEQ / m_i2s_items.sampleRate; // filter center frequency
+ float normFreqHS = FcHS / m_i2s_items.sampleRate; // filter cut off frequency
+
+ float total_boost_db = fmax(fmax(fmax(0, m_audio_items.gain_ls_db), m_audio_items.gain_peq_db), m_audio_items.gain_hs_db); // dynamic headroom
+ m_audio_items.pre_gain = powf(10.0, -total_boost_db / 20);
+
+ auto dsps_biquad_gen_peakingEQ_f32 = [&](float* c, float f, int8_t g, const float Q) -> void {
+ float A = powf(10.0f, g / 40.0f);
+ float w0 = 2.0f * M_PI * f;
+ float cw = cosf(w0);
+ float sw = sinf(w0);
+ float alpha = sw / (2.0f * Q);
+ float b0 = 1.0f + alpha * A;
+ float b1 = -2.0f * cw;
+ float b2 = 1.0f - alpha * A;
+ float a0 = 1.0f + alpha / A;
+ float a1 = -2.0f * cw;
+ float a2 = 1.0f - alpha / A;
+ c[0] = b0 / a0;
+ c[1] = b1 / a0;
+ c[2] = b2 / a0;
+ c[3] = a1 / a0;
+ c[4] = a2 / a0;
+ };
+
+ dsps_biquad_gen_lowShelf_f32(m_audio_items.coeffs[LOWSHELF], normFreqLS, m_audio_items.gain_ls_db, QS);
+ dsps_biquad_gen_peakingEQ_f32(m_audio_items.coeffs[PEAKINGEQ], normFreqPEQ, m_audio_items.gain_peq_db, QS); // my own calc.
+ dsps_biquad_gen_highShelf_f32(m_audio_items.coeffs[HIFGSHELF], normFreqHS, m_audio_items.gain_hs_db, QS);
+
+ AUDIO_LOG_DEBUG("\n([{}, {}, {}], [1.0, {}, {}]), # LOWSHELF\n([{}, {}, {} ], [1.0, {}, {} ]), # PEAKINGEQ\n([{}, {}, {}], [1.0, {}, {}]), # HIGHSHELF\n", m_audio_items.coeffs[0][0],
+ m_audio_items.coeffs[0][1], m_audio_items.coeffs[0][2], m_audio_items.coeffs[0][3], m_audio_items.coeffs[0][4], m_audio_items.coeffs[1][0], m_audio_items.coeffs[1][1],
+ m_audio_items.coeffs[1][2], m_audio_items.coeffs[1][3], m_audio_items.coeffs[1][4], m_audio_items.coeffs[2][0], m_audio_items.coeffs[2][1], m_audio_items.coeffs[2][2],
+ m_audio_items.coeffs[2][3], m_audio_items.coeffs[2][4]);
+ AUDIO_LOG_DEBUG("m_audio_items.pre_gain {}", m_audio_items.pre_gain);
+ memset(m_audio_items.state_biquad, 0, sizeof(m_audio_items.state_biquad));
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::IIR_filter(int32_t* sample) {
+
+ int32_t* s32 = sample;
+ float s[2];
+ s[LEFTCHANNEL] = (float)(s32[LEFTCHANNEL] * m_audio_items.pre_gain);
+ s[RIGHTCHANNEL] = (float)(s32[RIGHTCHANNEL] * m_audio_items.pre_gain);
+ dsps_biquad_sf32(s, s, 1, m_audio_items.coeffs[0], m_audio_items.state_biquad[0]);
+ dsps_biquad_sf32(s, s, 1, m_audio_items.coeffs[1], m_audio_items.state_biquad[1]);
+ dsps_biquad_sf32(s, s, 1, m_audio_items.coeffs[2], m_audio_items.state_biquad[2]);
+ s32[LEFTCHANNEL] = (int32_t)std::clamp(s[LEFTCHANNEL], -2147483648.0f, 2147483647.0f);
+ s32[RIGHTCHANNEL] = (int32_t)std::clamp(s[RIGHTCHANNEL], -2147483648.0f, 2147483647.0f);
+ return;
+}
+
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+// AAC - T R A N S P O R T S T R E A M
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+bool Audio::ts_parsePacket(uint8_t* packet, uint8_t* packetStart, uint8_t* packetLength) {
+
+ bool log = false;
+
+ const uint8_t TS_PACKET_SIZE = 188;
+ const uint8_t PAYLOAD_SIZE = 184;
+ const uint8_t PID_ARRAY_LEN = 4;
+
+ (void)PAYLOAD_SIZE; // suppress [-Wunused-variable]
+
+ if (packet == NULL) {
+ if (log) AUDIO_LOG_WARN("parseTS reset");
+ m_tspp.reset();
+ return true;
+ }
+
+ // --------------------------------------------------------------------------------------------------------
+ // 0. Byte SyncByte | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | always bit pattern of 0x47
+ //---------------------------------------------------------------------------------------------------------
+ // 1. Byte |PUSI|TP| |PID|PID|PID|PID|PID|
+ //---------------------------------------------------------------------------------------------------------
+ // 2. Byte |PID|PID|PID|PID|PID|PID|PID|PID|
+ //---------------------------------------------------------------------------------------------------------
+ // 3. Byte |TSC|TSC|AFC|AFC|CC |CC |CC |CC |
+ //---------------------------------------------------------------------------------------------------------
+ // 4.-187. Byte |Payload data if AFC==01 or 11 |
+ //---------------------------------------------------------------------------------------------------------
+
+ // PUSI Payload unit start indicator, set when this packet contains the first byte of a new payload unit.
+ // The first byte of the payload will indicate where this new payload unit starts.
+ // TP Transport priority, set when the current packet has a higher priority than other packets with the same PID.
+ // PID Packet Identifier, describing the payload data.
+ // TSC Transport scrambling control, '00' = Not scrambled.
+ // AFC Adaptation field control, 01 – no adaptation field, payload only, 10 – adaptation field only, no payload,
+ // 11 – adaptation field followed by payload, 00 – RESERVED for future use
+ // CC Continuity counter, Sequence number of payload packets (0x00 to 0x0F) within each stream (except PID 8191)
+
+ // for(int i = 1; i < 188; i++) {printf("%02X ", packet[i - 1]); if(i && (i % 16 == 0)) printf("\n");}
+ // printf("\n----------\n");
+
+ if (packet[0] != 0x47) {
+ AUDIO_LOG_ERROR("ts SyncByte not found, first bytes are 0x{:02X} 0x{:02X} 0x{:02X} 0x{:02X}", packet[0], packet[1], packet[2], packet[3]);
+ stopSong();
+ return false;
+ }
+ int PID = (packet[1] & 0x1F) << 8 | (packet[2] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("PID: 0x{:04X} ({})", PID, PID);
+ int PUSI = (packet[1] & 0x40) >> 6;
+ if (log) AUDIO_LOG_DEBUG("Payload Unit Start Indicator: {}", PUSI);
+ int AFC = (packet[3] & 0x30) >> 4;
+ if (log) AUDIO_LOG_DEBUG("Adaption Field Control: {}", AFC);
+
+ int AFL = -1;
+ if ((AFC & 0b10) == 0b10) { // AFC '11' Adaptation Field followed
+ AFL = packet[4] & 0xFF; // Adaptation Field Length
+ if (log) AUDIO_LOG_DEBUG("Adaptation Field Length: {}", AFL);
+ }
+ int PLS = PUSI ? 5 : 4; // PayLoadStart, Payload Unit Start Indicator
+ if (AFL > 0) PLS += AFL + 1; // skip adaption field
+
+ if (AFC == 2) { // The TS package contains only an adaptation Field and no user data.
+ *packetStart = AFL + 1;
+ *packetLength = 0;
+ return true;
+ }
+
+ if (PID == 0) {
+ // Program Association Table (PAT) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (log) AUDIO_LOG_DEBUG("PAT");
+ m_tspp.pidNumber = 0;
+ m_tspp.pidOfAAC = 0;
+
+ int startOfProgramNums = 8;
+ int lengthOfPATValue = 4;
+ int sectionLength = ((packet[PLS + 1] & 0x0F) << 8) | (packet[PLS + 2] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("Section Length: {}", sectionLength);
+ int program_number, program_map_PID;
+ int indexOfPids = 0;
+ (void)program_number; // [-Wunused-but-set-variable]
+ for (int i = startOfProgramNums; i <= sectionLength; i += lengthOfPATValue) {
+ program_number = ((packet[PLS + i] & 0xFF) << 8) | (packet[PLS + i + 1] & 0xFF);
+ program_map_PID = ((packet[PLS + i + 2] & 0x1F) << 8) | (packet[PLS + i + 3] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("Program Num: 0x{:04X}({}) PMT PID: 0x{:04X}({})", program_number, program_number, program_map_PID, program_map_PID);
+ m_tspp.pids[indexOfPids++] = program_map_PID;
+ }
+ m_tspp.pidNumber = indexOfPids;
+ *packetStart = 0;
+ *packetLength = 0;
+ return true;
+ } else if (PID == m_tspp.pidOfAAC) {
+ if (log) AUDIO_LOG_DEBUG("AAC");
+ uint8_t posOfPacketStart = 4;
+ if (AFL >= 0) {
+ posOfPacketStart = 5 + AFL;
+ if (log) AUDIO_LOG_DEBUG("posOfPacketStart: {}", posOfPacketStart);
+ }
+ // Packetized Elementary Stream (PES) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (log) AUDIO_LOG_DEBUG("PES_DataLength {}", m_tspp.PES_DataLength);
+ if (m_tspp.PES_DataLength > 0) {
+ *packetStart = posOfPacketStart + m_tspp.fillData;
+ *packetLength = TS_PACKET_SIZE - posOfPacketStart - m_tspp.fillData;
+ if (log) AUDIO_LOG_DEBUG("packetlength {}", *packetLength);
+ m_tspp.fillData = 0;
+ m_tspp.PES_DataLength -= (*packetLength);
+ return true;
+ } else {
+ int firstByte = packet[posOfPacketStart] & 0xFF;
+ int secondByte = packet[posOfPacketStart + 1] & 0xFF;
+ int thirdByte = packet[posOfPacketStart + 2] & 0xFF;
+ if (log) AUDIO_LOG_DEBUG("First 3 bytes: 0x{:02X}, 0x{:02X}, 0x{:02X}", firstByte, secondByte, thirdByte);
+ if (firstByte == 0x00 && secondByte == 0x00 && thirdByte == 0x01) { // Packet start code prefix
+ // --------------------------------------------------------------------------------------------------------
+ // posOfPacketStart + 0...2 0x00, 0x00, 0x01 PES-Startcode
+ //---------------------------------------------------------------------------------------------------------
+ // posOfPacketStart + 3 0xE0 (Video) od 0xC0 (Audio) StreamID
+ //---------------------------------------------------------------------------------------------------------
+ // posOfPacketStart + 4...5 0xLL, 0xLL PES Packet length
+ //---------------------------------------------------------------------------------------------------------
+ // posOfPacketStart + 6...7 PTS/DTS Flags
+ //---------------------------------------------------------------------------------------------------------
+ // posOfPacketStart + 8 0xXX header length
+ //---------------------------------------------------------------------------------------------------------
+
+ uint8_t StreamID = packet[posOfPacketStart + 3] & 0xFF;
+ if (StreamID >= 0xC0 && StreamID <= 0xDF) { ; } // okay ist audio stream
+ if (StreamID >= 0xE0 && StreamID <= 0xEF) {
+ AUDIO_LOG_ERROR("video stream!");
+ return false;
+ }
+ int PES_PacketLength = ((packet[posOfPacketStart + 4] & 0xFF) << 8) + (packet[posOfPacketStart + 5] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("PES PacketLength: {}", PES_PacketLength);
+ bool PTS_flag = false;
+ bool DTS_flag = false;
+ int flag_byte1 = packet[posOfPacketStart + 6] & 0xFF;
+ int flag_byte2 = packet[posOfPacketStart + 7] & 0xFF;
+ (void)flag_byte2; // unused yet
+ if (flag_byte1 & 0b10000000) PTS_flag = true;
+ if (flag_byte1 & 0b00000100) DTS_flag = true;
+ if (log && PTS_flag) AUDIO_LOG_DEBUG("PTS_flag is set");
+ if (log && DTS_flag) AUDIO_LOG_DEBUG("DTS_flag is set");
+ uint8_t PES_HeaderDataLength = packet[posOfPacketStart + 8] & 0xFF;
+ if (log) AUDIO_LOG_DEBUG("PES_headerDataLength {}", PES_HeaderDataLength);
+
+ m_tspp.PES_DataLength = PES_PacketLength;
+ int startOfData = PES_HeaderDataLength + 9;
+ if (posOfPacketStart + startOfData >= 188) { // only fillers in packet
+ if (log) AUDIO_LOG_DEBUG("posOfPacketStart + startOfData {}", posOfPacketStart + startOfData);
+ *packetStart = 0;
+ *packetLength = 0;
+ m_tspp.PES_DataLength -= (PES_HeaderDataLength + 3);
+ m_tspp.fillData = (posOfPacketStart + startOfData) - 188;
+ if (log) AUDIO_LOG_DEBUG("fillData {}", m_tspp.fillData);
+ return true;
+ }
+ if (log) AUDIO_LOG_DEBUG("First AAC data byte: {:02X}", packet[posOfPacketStart + startOfData]);
+ if (log) AUDIO_LOG_DEBUG("Second AAC data byte: {:02X}", packet[posOfPacketStart + startOfData + 1]);
+ *packetStart = posOfPacketStart + startOfData;
+ *packetLength = TS_PACKET_SIZE - posOfPacketStart - startOfData;
+ m_tspp.PES_DataLength -= (*packetLength);
+ m_tspp.PES_DataLength -= (PES_HeaderDataLength + 3);
+ return true;
+ }
+ if (firstByte == 0 && secondByte == 0 && thirdByte == 0) {
+ // PES packet startcode prefix is 0x000000
+ // skip such packets
+ return true;
+ }
+ }
+ *packetStart = 0;
+ *packetLength = 0;
+ AUDIO_LOG_ERROR("PES not found");
+ return false;
+ } else if (m_tspp.pidNumber) {
+ // Program Map Table (PMT) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ for (int i = 0; i < m_tspp.pidNumber; i++) {
+ if (PID == m_tspp.pids[i]) {
+ if (log) AUDIO_LOG_DEBUG("PMT");
+ int staticLengthOfPMT = 12;
+ int sectionLength = ((packet[PLS + 1] & 0x0F) << 8) | (packet[PLS + 2] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("Section Length: {}", sectionLength);
+ int programInfoLength = ((packet[PLS + 10] & 0x0F) << 8) | (packet[PLS + 11] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("Program Info Length: {}", programInfoLength);
+ int cursor = staticLengthOfPMT + programInfoLength;
+ while (cursor < sectionLength - 1) {
+ int streamType = packet[PLS + cursor] & 0xFF;
+ int elementaryPID = ((packet[PLS + cursor + 1] & 0x1F) << 8) | (packet[PLS + cursor + 2] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("Stream Type: 0x{:02X} Elementary PID: 0x{:04X}", streamType, elementaryPID);
+
+ if (streamType == 0x0F || streamType == 0x11 || streamType == 0x04) {
+ if (log) AUDIO_LOG_DEBUG("AAC PID discover");
+ m_tspp.pidOfAAC = elementaryPID;
+ }
+ int esInfoLength = ((packet[PLS + cursor + 3] & 0x0F) << 8) | (packet[PLS + cursor + 4] & 0xFF);
+ if (log) AUDIO_LOG_DEBUG("ES Info Length: 0x{:04X}", esInfoLength);
+ cursor += 5 + esInfoLength;
+ }
+ }
+ }
+ *packetStart = 0;
+ *packetLength = 0;
+ return true;
+ }
+ // PES received before PAT and PMT seen
+ *packetStart = 0;
+ *packetLength = 0;
+ if (PID > 0) { return true; }
+ return false;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+// W E B S T R E A M - H E L P F U N C T I O N S
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+bool Audio::readMetadata(uint32_t maxBytes, uint16_t* readedBytes, bool first) {
+ *readedBytes = 0;
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (first) {
+ m_rmet.pos_ml = 0; // determines the current position in metaline
+ m_rmet.metaDataSize = 0;
+ m_rmet.res = 0;
+ m_metadataBuff.clear();
+ return true;
+ }
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ if (!maxBytes) return false; // guard
+ if (!m_rmet.metaDataSize) {
+ int b = audioFileRead(); // First byte of metadata?
+ if (b < 0) {
+ AUDIO_LOG_WARN("client->read() failed ({})", b);
+ return false;
+ }
+ m_rmet.metaDataSize = b * 16; // New count for metadata including length byte, max 4096
+ m_rmet.pos_ml = 0;
+ m_metadataBuff[m_rmet.pos_ml] = 0; // Prepare for new line
+ *readedBytes = 1;
+ maxBytes -= 1;
+ }
+ if (!m_rmet.metaDataSize) { return *readedBytes; } // metalen is 0
+
+ int32_t a = audioFileRead((uint8_t*)&m_metadataBuff[m_rmet.pos_ml], min(m_rmet.metaDataSize - m_rmet.pos_ml, maxBytes));
+ if (a > 0) {
+ m_rmet.res += a;
+ *readedBytes += a;
+ m_rmet.pos_ml += a;
+ }
+ if (m_rmet.pos_ml == m_rmet.metaDataSize) {
+ m_metadataBuff[m_rmet.pos_ml] = '\0';
+ // m_metadataBuff.hex_dump(m_rmet.metaDataSize);
+ if (m_metadataBuff.strlen() > 0) { // Any info present?
+ // metaline contains artist and song name. For example:
+ // "StreamTitle='Don McLean - American Pie';StreamUrl='';"
+ // Sometimes it is just other info like:
+ // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';"
+ // Isolate the StreamTitle, remove leading and trailing quotes if present.
+ latinToUTF8(m_metadataBuff); // convert to UTF-8 if necessary
+ int pos = m_metadataBuff.index_of_icase("song_spot", 0); // remove some irrelevant infos
+ if (pos > 3) { // e.g. song_spot="T" MediaBaseId="0" itunesTrackId="0"
+ m_metadataBuff[pos] = 0;
+ }
+ showstreamtitle(m_metadataBuff.get()); // Show artist and title if present in metadata
+ }
+ m_metacount = m_metaint;
+ m_rmet.metaDataSize = 0;
+ m_rmet.pos_ml = 0;
+ m_metadataBuff[m_rmet.pos_ml] = 0; // Prepare for new line
+ } else {
+ return false; // not enough data, next round
+ }
+ m_metadataBuff.clear();
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+int32_t Audio::getChunkSize(uint16_t* readedBytes, bool first) {
+
+ // \r\n
+ // \r\n
+ // hex_str;
+ int16_t idx1, idx2, idx3;
+ int32_t chunkSize = -1;
+
+ if (first) {
+ m_gchs.reset();
+ return 0;
+ }
+ if (!m_gchs.chunkLine.valid()) m_gchs.chunkLine.calloc(CHUNK_LINE_LENGTH, "m_gchs1.chunkLine");
+
+ auto hex_to_int = [&](ps_ptr hexstr) -> int32_t {
+ if (!hexstr.valid()) return -1;
+ for (int i = 0; i < hexstr.strlen(); i++) {
+ if (!isxdigit(hexstr[i])) return -1;
+ }
+ return hexstr.to_int32(16);
+ };
+
+ *readedBytes = 0;
+
+ while (true) {
+ int16_t b = audioFileRead();
+ if (b == -1) { // need mor data
+ chunkSize = -1;
+ break;
+ }
+
+ m_gchs.chunkLine[m_gchs.position] = b;
+ *readedBytes += 1;
+ m_gchs.position += 1;
+
+ if (m_gchs.chunkLine == "\r\n") { // skip CRLF
+ m_gchs.chunkLine.clear();
+ m_gchs.position = 0;
+ AUDIO_LOG_DEBUG("skip CRLF");
+ }
+
+ if (m_gchs.position == CHUNK_LINE_LENGTH) {
+ AUDIO_LOG_ERROR("ChunkLine is too long");
+ m_gchs.chunkLine.hex_dump(CHUNK_LINE_LENGTH);
+ goto error;
+ }
+ if (m_gchs.chunkLine.ends_with("\r\n") && m_gchs.chunkSize == -1) {
+ idx1 = m_gchs.chunkLine.index_of(";");
+ if (idx1 > 0) { // extension follows, e.g. "AF4;test=123\r\n"
+ hex_str = m_gchs.chunkLine.substr(0, idx1); // "AF4;test=123\r\n" -> "AF4"
+ ps_ptr extension = m_gchs.chunkLine.substr(idx1 + 1); // "AF4;test=123\r\n" -> "test=123\r\n"
+ extension = extension.substr(0, extension.strlen() - 2); // "test=123\r\n" -> "test=123"
+ if (extension != m_gchs.extension) {
+ m_gchs.extension = extension;
+ AUDIO_LOG_INFO("extension {}", m_gchs.extension);
+ }
+ } else {
+ hex_str = m_gchs.chunkLine.substr(0, m_gchs.chunkLine.strlen() - 2); // "AF4\r\n" -> "AF4"
+ }
+ m_gchs.chunkSize = hex_to_int(hex_str); // "AF4" -> 2804
+ if (m_gchs.chunkSize == -1) {
+ AUDIO_LOG_ERROR("Invalid char in hex_str");
+ hex_str.hex_dump(20);
+ goto error;
+ }
+ }
+ if (m_gchs.chunkSize > 0) {
+ chunkSize = m_gchs.chunkSize;
+ break;
+ } else { // m_gchs1.chunkSize is 0
+ if (m_gchs.chunkLine.ends_with("\r\n\r\n")) {
+ idx1 = m_gchs.chunkLine.index_of("\r\n");
+ idx2 = m_gchs.chunkLine.index_of("\r\n\r");
+ if (idx1 == idx2) {
+ // e.g. "000\r\n\r\n" or "0\r\n\r\n" or "0;end=true\r\n\r\n"
+ } else { // can have trailer "0\r\nContent-MD5: abcdef\r\nServer: xyz\r\n\r\n"
+ m_gchs.trailer = m_gchs.chunkLine.substr(idx1 + 1, idx1 - idx2);
+ AUDIO_LOG_INFO("trailer {}", m_gchs.trailer);
+ }
+ chunkSize = 0;
+ break;
+ }
+ }
+ }
+
+ AUDIO_LOG_DEBUG("chunkSize {}", chunkSize);
+ if (chunkSize == -1) {
+ if (m_gchs.timeStamp + 3000 < millis()) {
+ AUDIO_LOG_WARN("timeout while get next chunkSize");
+ m_gchs.timeStamp = millis();
+ }
+ }
+ if (chunkSize >= 0) {
+ m_gchs.chunkSize = -1;
+ m_gchs.position = 0;
+ m_gchs.chunkLine.clear();
+ m_gchs.timeStamp = millis();
+ }
+ return chunkSize;
+
+error:
+ m_gchs.reset();
+ return -100;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+bool Audio::readID3V1Tag() {
+ if (m_codec != CODEC_MP3) return false;
+ ps_ptr chBuff;
+ chBuff.alloc(256, "chBuff");
+
+ const uint8_t* p = InBuff.getReadPtr();
+
+ // Lambda for simplification
+ auto readID3Field = [&](ps_ptr& field, const uint8_t* src, size_t len, const char* label = nullptr) {
+ field.alloc(len + 1, "field");
+ memcpy(field.get(), src, len);
+ field[len] = '\0';
+ latinToUTF8(field);
+ if (field.strlen() > 0 && label) {
+ field.insert(label, 0);
+ info(*this, evt_id3data, "{}", field.get());
+ }
+ };
+
+ if (InBuff.bufferFilled() == 128 && startsWith((const char*)p, "TAG")) {
+ ps_ptr title, artist, album, year, comment;
+
+ readID3Field(title, p + 3, 30, "Title: ");
+ readID3Field(artist, p + 33, 30, "Artist: ");
+ readID3Field(album, p + 63, 30, "Album: ");
+ readID3Field(year, p + 93, 4, "Year: ");
+ readID3Field(comment, p + 97, 30, "Comment: ");
+
+ uint8_t zeroByte = p[125];
+ uint8_t track = p[126];
+ uint8_t genre8 = p[127];
+
+ info(*this, evt_info, zeroByte ? "ID3 version: 1" : "ID3 Version 1.1");
+
+ if (zeroByte == 0) {
+ sprintf(chBuff.get(), "Track Number: %d", track);
+ info(*this, evt_id3data, "{}", chBuff.get());
+ }
+
+ if (genre8 < 192) {
+ sprintf(chBuff.get(), "Genre: %d", genre8);
+ info(*this, evt_id3data, "{}", chBuff.get());
+ }
+
+ return true;
+ }
+
+ if (InBuff.bufferFilled() == 227 && startsWith((const char*)p, "TAG+")) { // "TAG+" "can exist as an extension, does not overwrite" TAG"
+ info(*this, evt_info, "ID3 version: 1 - Enhanced TAG");
+
+ ps_ptr title, artist, album, genre;
+
+ readID3Field(title, p + 4, 60, "Title: ");
+ readID3Field(artist, p + 64, 60, "Artist: ");
+ readID3Field(album, p + 124, 60, "Album: ");
+ readID3Field(genre, p + 185, 30, "Genre: ");
+
+ // optional expansion: speed, start-time, end-time
+ return true;
+ }
+
+ return false;
+ // [1] https://en.wikipedia.org/wiki/List_of_ID3v1_Genres
+ // [2] https://en.wikipedia.org/wiki/ID3#ID3v1_and_ID3v1.1[5]
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+int32_t Audio::newInBuffStart(int32_t resumeFilePos) {
+
+ if ((m_controlCounter != 100) || (m_resumeFilePos >= (int32_t)m_audioDataStart + m_audioDataSize) || ((m_codec == CODEC_M4A) && !m_stsz_position)) {
+ AUDIO_LOG_WARN("timeOffset not possible");
+ return 0;
+ }
+
+ // keep resumeFilePos within the audio data
+ if (resumeFilePos < (int32_t)m_audioDataStart) resumeFilePos = m_audioDataStart;
+
+ uint32_t buffFillValue = std::min(m_audioDataSize - resumeFilePos, UINT16_MAX);
+
+ AUDIO_LOG_DEBUG("new InBuff start at m_resumeFilePos {}, m_audioDataStart {}", m_resumeFilePos, m_audioDataStart);
+
+ // --- enter critical area ------------------------------------------
+ xSemaphoreTake(mutex_audioTaskIsDecoding, 1 * configTICK_RATE_HZ);
+ m_f_lockInBuffer = true;
+
+ // ------- prepare InBuff ------
+ m_f_allDataReceived = false;
+ audioFileSeek(resumeFilePos);
+ InBuff.reset();
+ int32_t bw = audioFileRead(InBuff.getWritePtr(), buffFillValue, 3000);
+ if (bw == buffFillValue)
+ InBuff.bytesWritten(bw);
+ else
+ return -1;
+
+ int32_t offset = 0;
+ int32_t newFilePos = resumeFilePos;
+
+ // ---------------------------------------------------------------------------
+ // codec specific handling
+ // ---------------------------------------------------------------------------
+
+ auto fail = [&]() {
+ AUDIO_LOG_ERROR("can't set newFilePos");
+ InBuff.bytesWasRead(0);
+ m_f_lockInBuffer = false;
+ xSemaphoreGive(mutex_audioTaskIsDecoding);
+ stopSong();
+ return -1;
+ };
+
+ switch (m_codec) {
+ case CODEC_M4A:
+ offset = m4a_correctResumeFilePos();
+ if (offset < 0) return fail();
+ break;
+
+ case CODEC_WAV:
+ // WAV have 4-Byte-Boundaries
+ offset = wav_correctResumeFilePos();
+ if (offset < 0) return fail();
+ break;
+
+ case CODEC_MP3:
+ offset = mp3_correctResumeFilePos();
+ if (offset < 0) return fail();
+ break;
+
+ case CODEC_FLAC:
+ offset = flac_correctResumeFilePos();
+ if (offset < 0) return fail();
+ break;
+
+ case CODEC_VORBIS:
+ offset = ogg_correctResumeFilePos(); // next OggS
+ if (offset < 0) return fail();
+ break;
+
+ case CODEC_OPUS:
+ offset = ogg_correctResumeFilePos(); // next OggS
+ if (offset < 0) return fail();
+ break;
+
+ default: return fail();
+ }
+
+ AUDIO_LOG_DEBUG("offset {}, readSpace {}", offset, InBuff.readSpace());
+ newFilePos += offset;
+ m_decoder->clear();
+
+ // --- leaf critical area ----------------------------------------------
+ InBuff.bytesWasRead(offset);
+ m_f_lockInBuffer = false;
+ xSemaphoreGive(mutex_audioTaskIsDecoding);
+
+ return newFilePos;
+}
+
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+boolean Audio::streamDetection(uint32_t bytesAvail) {
+
+ if (InBuff.bufferFilled() < InBuff.getMaxBlockSize()) {
+ if (m_sdet.cnt_slow == 0) m_sdet.tmr_slow = millis();
+ m_sdet.cnt_slow++;
+ } else {
+ m_sdet.cnt_slow = 0;
+ m_sdet.cnt_lost = 0;
+ }
+
+ // if within one second the content of the audio buffer falls below the size of an audio frame 100 times,
+ // issue a message
+ if (m_sdet.cnt_slow && m_sdet.tmr_slow + 2000 < millis()) {
+ m_sdet.tmr_slow = millis();
+ info(*this, evt_info, "slow stream");
+ m_sdet.cnt_slow = 0;
+ m_sdet.cnt_lost++;
+ }
+
+ if (bytesAvail) { m_sdet.cnt_lost = 0; }
+ if (InBuff.bufferFilled() > InBuff.getMaxBlockSize() * 2) return true; // enough data available to play
+
+ // if no audio data is received within 10 seconds, a new connection attempt is started.
+ if (m_sdet.cnt_lost == 5) {
+ info(*this, evt_info, "Stream lost");
+ connecttohost(m_lastHost.get());
+ m_sdet.cnt_slow = 0;
+ m_sdet.cnt_lost = 0;
+ return false;
+ }
+ return false;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+uint32_t Audio::m4a_correctResumeFilePos() {
+ // In order to jump within an m4a file, the exact beginning of an aac block must be found. Since m4a cannot be
+ // streamed, i.e. there is no syncword, an imprecise jump can lead to a crash.
+
+ if (!m_stsz_position) return m_audioDataStart; // guard
+
+ typedef union {
+ uint8_t u8[4];
+ uint32_t u32;
+ } tu;
+ tu uu = {};
+
+ uint32_t i = 0, pos = m_audioDataStart;
+ uint32_t filePtr = m_audioFilePosition;
+ bool found = false;
+ audioFileSeek(m_stsz_position, m_stsz_numEntries * 4);
+
+ auto read_next = [&]() -> int {
+ int r = 0;
+ int cnt = 0;
+ while (true) {
+ r = audioFileRead();
+ if (r >= 0) break;
+ vTaskDelay(10);
+ cnt++;
+ if (cnt > 300) {
+ AUDIO_LOG_ERROR("timeout");
+ break;
+ }
+ }
+ return r;
+ };
+
+ while (i < m_stsz_numEntries) {
+ i++;
+ uu.u8[3] = read_next();
+ uu.u8[2] = read_next();
+ uu.u8[1] = read_next();
+ uu.u8[0] = read_next();
+
+ pos += uu.u32;
+ if (pos >= m_resumeFilePos) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) return -1; // not found
+
+ audioFileSeek(filePtr); // restore file pointer
+ return pos - m_resumeFilePos; // return the number of bytes to jump
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+uint32_t Audio::ogg_correctResumeFilePos() {
+ // The starting point is the next OggS magic word
+ if (InBuff.bufferFilled() < 0xFFFF) return 0;
+ vTaskDelay(1);
+ // // AUDIO_LOG_INFO("in_resumeFilePos {}", resumeFilePos);
+
+ auto find_sync_word = [&](uint8_t* pos, uint32_t av) -> int {
+ int steps = 0;
+ while (av--) {
+ if (pos[steps] == 'O') {
+ if (pos[steps + 1] == 'g') {
+ if (pos[steps + 2] == 'g') {
+ if (pos[steps + 3] == 'S') { // Check for the second part of magic word
+ return steps; // Magic word found, return the number of steps
+ }
+ }
+ }
+ }
+ steps++;
+ }
+ return -1; // Return -1 if OggS magic word is not found
+ };
+
+ uint8_t* readPtr = InBuff.getReadPtr();
+ size_t av = InBuff.readSpace();
+ int32_t steps = 0;
+
+ if (av < InBuff.getMaxBlockSize()) return -1; // guard
+
+ steps = find_sync_word(readPtr, av);
+ if (steps == -1) return -1;
+ return steps; // Return the number of steps to the sync word
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+int32_t Audio::flac_correctResumeFilePos() {
+ uint8_t* p = InBuff.getReadPtr();
+ size_t av = InBuff.readSpace();
+
+ if (av < 32) return -1; // sicher gehen
+
+ auto utf8_length = [&](uint8_t first) -> int {
+ if ((first & 0x80) == 0x00) return 1; // 0xxxxxxx
+ if ((first & 0xE0) == 0xC0) return 2; // 110xxxxx
+ if ((first & 0xF0) == 0xE0) return 3; // 1110xxxx
+ if ((first & 0xF8) == 0xF0) return 4; // 11110xxx
+ if ((first & 0xFC) == 0xF8) return 5; // 111110xx
+ if ((first & 0xFE) == 0xFC) return 6; // 1111110x
+ if (first == 0xFE) return 7; // 11111110
+ return -1; // invalid UTF-8
+ };
+
+ // FLAC CRC-8
+ auto crc8_update = [](uint8_t crc, uint8_t data) -> uint8_t {
+ crc ^= data;
+ for (int i = 0; i < 8; ++i) {
+ if (crc & 0x80)
+ crc = (crc << 1) ^ 0x07;
+ else
+ crc <<= 1;
+ }
+ return crc;
+ };
+
+ for (size_t i = 0; i + 14 < av; ++i) {
+ // 14-Bit-Sync: 0xFFF8 oder 0xFFF9 (last 2 Bits variable!)
+ uint8_t b0 = p[i + 0]; // 11111111 sync
+ uint8_t b1 = p[i + 1]; // 111111rb sync r-reserved b-blocking strategy
+ if (b0 != 0xFF) continue;
+ if ((b1 & 0xFE) != 0xF8) continue;
+
+ uint8_t b2 = p[i + 2]; // bbbbssss b-blocksize s samplerate
+ uint8_t blocksize_code = (b2 & 0xF0) >> 4;
+ uint8_t samplerate_code = (b2 & 0x0F);
+
+ uint8_t b3 = p[i + 3]; // ccccsssr c-channel assignment, s sample size, r reserved
+ uint8_t channel_assign = (b3 & 0xF0) >> 4;
+ uint8_t bps_code = (b3 & 0x0E) >> 1; // bits per sample
+ uint8_t reserved_bit = b3 & 0x01; // has to be 0
+
+ if (reserved_bit != 0) continue;
+ if (channel_assign >= 11) continue; // 11-15 reserved
+ if (bps_code == 0x07) continue; // reserved
+
+ size_t pos = i + 4; // first byte of UTF8
+ int len = utf8_length(p[pos]);
+ if (len < 1 || pos + len >= av) continue; // invalid/incomplete header
+
+ size_t crc_end = pos + len; // this is where the CRC byte lives
+
+ AUDIO_LOG_DEBUG("samplerate_code {}, bps_code {} channel_assign {}, reserved_bit {}", samplerate_code, bps_code, channel_assign, reserved_bit);
+ AUDIO_LOG_DEBUG("b0 {}, b1 {}, b2 {}, b3 {}", b0, b1, b2, b3);
+
+ uint8_t crc = 0;
+ for (size_t k = i; k < crc_end; ++k) crc = crc8_update(crc, p[k]);
+
+ if (crc != p[crc_end]) continue;
+
+ // === When we get here: 99.9999% certain it’s a real frame ===
+ AUDIO_LOG_DEBUG(">>> REAL FLAC-FRAME found at offset {}", i);
+ return (int32_t)i;
+ }
+
+ return -1; // No frame found in the first 64 KB
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+int32_t Audio::mp3_correctResumeFilePos() {
+
+ int32_t steps = 0;
+ int32_t sumSteps = 0;
+ uint8_t* pos = InBuff.getReadPtr();
+ size_t av = InBuff.readSpace();
+
+ if (av < InBuff.getMaxBlockSize()) return -1; // guard
+
+ while (true) {
+ steps = m_decoder->findSyncWord(pos, av);
+ if (steps == 0) break;
+ if (steps == -1) return -1;
+ pos += steps;
+ sumSteps += steps;
+ }
+ // AUDIO_LOG_INFO("found sync word at {} sync1 = 0x{:02X}, sync2 = 0x{;02X}", readPtr - pos, *readPtr, *(readPtr + 1));
+ return sumSteps; // return the position of the first byte of the frame
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+int32_t Audio::wav_correctResumeFilePos() {
+ int32_t pos = m_resumeFilePos;
+ uint8_t offset = 0;
+ ;
+ // WAV must be on 4 byte limit
+ while (pos % 4 != 0) {
+ offset++;
+ pos++;
+ }
+ if (pos >= m_audioDataStart + m_audioDataSize) return 0;
+ return offset;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-
+uint8_t Audio::determineCodec(uint8_t presumed_codec) {
+ if (presumed_codec == CODEC_OGG) {
+ // if we have contentType == application/ogg; codec cn be OPUS, FLAC or VORBIS
+ // let's have a look, what it is
+ uint8_t res = CODEC_NONE;
+ int idx = -1;
+ idx = specialIndexOf(InBuff.getReadPtr(), "OggS", 127);
+ AUDIO_LOG_DEBUG("idx {}", idx);
+ if (idx == 0) {
+ idx = specialIndexOf(InBuff.getReadPtr(), "OpusHead", 127);
+ if (idx >= 28) { res = CODEC_OPUS; }
+ idx = specialIndexOf(InBuff.getReadPtr(), "fLaC", 127);
+ if (idx >= 28) { res = CODEC_FLAC; }
+ idx = specialIndexOf(InBuff.getReadPtr(), "vorbis", 127);
+ if (idx >= 28) { res = CODEC_VORBIS; }
+
+ m_lastGranulePosition = getLastGranulePosition(res); // VORBIS or OPUS only
+ audioFileSeek(0); // if isFile?
+ AUDIO_LOG_DEBUG("lastGranulePosition {}", m_lastGranulePosition);
+ }
+ return res;
+ }
+
+ if (m_playlistFormat == FORMAT_M3U8) return presumed_codec; // HLS or TS, nothing todo
+ if (m_streamType != ST_WEBSTREAM) return presumed_codec; // is webfile, nothing todo
+
+ if (presumed_codec == CODEC_AAC || presumed_codec == CODEC_MP3) { // only webstream, is AAC or MP3?
+ uint8_t* data = InBuff.getReadPtr();
+ uint8_t b0 = data[0];
+ uint8_t b1 = data[1];
+ int8_t mimeType = CODEC_NONE;
+ if (b0 == 0xFF && (b1 & 0xF6) == 0xF0) { // AAC ADTS
+ mimeType = CODEC_AAC;
+ }
+
+ else if (b0 == 0x56 && (b1 & 0xE0) == 0xE0) { // AAC LATM
+ mimeType = CODEC_AAC;
+ }
+
+ else if (b0 == 0xFF && (b1 & 0xE0) == 0xE0) { // MP3
+ uint8_t layer = (b1 >> 1) & 0x03;
+ if (layer != 0) { // layer must not be "reserved".
+ mimeType = CODEC_MP3;
+ }
+ }
+
+ if (presumed_codec == mimeType) return presumed_codec;
+ if (mimeType == CODEC_NONE) return presumed_codec; // is not AAC or MP3
+ info(*this, evt_info, "contentType is {}, but {} found", codecname[presumed_codec], codecname[mimeType]);
+ return mimeType;
+ }
+
+ return presumed_codec; // all other (native FLAC or WAV)
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+const char* Audio::getVersion() {
+ trim(audioI2SVers);
+ return audioI2SVers + 8;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::get_info() {
+ if (m_info_queue.e.size() == 0) return false;
+ msg_t i = {0};
+ while (m_info_queue.e.size()) {
+ ps_ptr msg = m_info_queue.msg.back();
+ i.msg = msg.c_get();
+ i.e = (event_t)m_info_queue.e.back();
+ ps_ptr evtstr = m_info_queue.s.back();
+ i.s = evtstr.c_get();
+ i.arg1 = m_info_queue.arg1.back();
+ i.arg2 = m_info_queue.arg2.back();
+ i.i2s_num = m_i2s_items.i2s_num;
+ i.vec = m_info_queue.vec.back();
+
+ m_info_queue.msg.pop_back();
+ m_info_queue.e.pop_back();
+ m_info_queue.s.pop_back();
+ m_info_queue.arg1.pop_back();
+ m_info_queue.arg2.pop_back();
+ m_info_queue.vec.pop_back();
+ audio_info_callback(i);
+ }
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::trim(char* str) {
+ char* start = str; // keep the original pointer
+ char* end;
+ while (isspace((unsigned char)*start)) start++; // find the first non-space character
+
+ if (*start == 0) { // all characters were spaces
+ str[0] = '\0'; // return a empty string
+ return;
+ }
+
+ end = start + strlen(start) - 1; // find the end of the string
+
+ while (end > start && isspace((unsigned char)*end)) end--;
+ end[1] = '\0'; // Null-terminate the string after the last non-space character
+
+ // Move the trimmed string to the beginning of the memory area
+ memmove(str, start, strlen(start) + 1); // +1 for '\0'
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::startsWith(const char* base, const char* str) {
+ // fb
+ char c;
+ while ((c = *str++) != '\0')
+ if (c != *base++) return false;
+ return true;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::indexOf(const char* base, const char* str, int startIndex) {
+ // fbi
+ const char* p = base;
+ for (; startIndex > 0; startIndex--)
+ if (*p++ == '\0') return -1;
+ char* pos = strstr(p, str);
+ if (pos == nullptr) return -1;
+ return pos - base;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::indexOf(const char* base, char ch, int startIndex) {
+ // fb
+ const char* p = base;
+ for (; startIndex > 0; startIndex--)
+ if (*p++ == '\0') return -1;
+ char* pos = strchr(p, ch);
+ if (pos == nullptr) return -1;
+ return pos - base;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int Audio::specialIndexOf(uint8_t* base, const char* str, int baselen, bool exact) {
+ int result = 0; // seek for str in buffer or in header up to baselen, not nullterninated
+ if (strlen(str) > baselen) return -1; // if exact == true seekstr in buffer must have "\0" at the end
+ for (int i = 0; i < baselen - strlen(str); i++) {
+ result = i;
+ for (int j = 0; j < strlen(str) + exact; j++) {
+ if (*(base + i + j) != *(str + j)) {
+ result = -1;
+ break;
+ }
+ }
+ if (result >= 0) break;
+ }
+ return result;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+int32_t Audio::min3(int32_t a, int32_t b, int32_t c) {
+ uint32_t min_val = a;
+ if (b < min_val) min_val = b;
+ if (c < min_val) min_val = c;
+ return min_val;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+// some other functions
+uint64_t Audio::bigEndian(uint8_t* base, uint8_t numBytes, uint8_t shiftLeft) {
+ uint64_t result = 0; // Use uint64_t for greater caching
+ if (numBytes < 1 || numBytes > 8) return 0;
+ for (int i = 0; i < numBytes; i++) {
+ result |= (uint64_t)(*(base + i)) << ((numBytes - i - 1) * shiftLeft); // Make sure the calculation is done correctly
+ }
+ if (result > SIZE_MAX) {
+ log_e("range overflow");
+ return 0;
+ }
+ return result;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+bool Audio::b64encode(const char* source, uint16_t sourceLength, char* dest) {
+ size_t size = base64_encode_expected_len(sourceLength) + 1;
+ char* buffer = (char*)malloc(size);
+ if (buffer) {
+ base64_encodestate _state;
+ base64_init_encodestate(&_state);
+ int len = base64_encode_block(&source[0], sourceLength, &buffer[0], &_state);
+ base64_encode_blockend((buffer + len), &_state);
+ memcpy(dest, buffer, strlen(buffer));
+ dest[strlen(buffer)] = '\0';
+ free(buffer);
+ return true;
+ }
+ return false;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::vector_clear_and_shrink(std::vector>& vec) {
+ for (int i = 0; i < vec.size(); i++) vec[i].reset();
+ vec.clear(); // unique_ptr takes care of free()
+ vec.shrink_to_fit(); // put back memory
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+void Audio::deque_clear_and_shrink(std::deque>& deq) {
+ for (int i = 0; i < deq.size(); i++) deq[i].reset();
+ deq.clear(); // unique_ptr takes care of free()
+ deq.shrink_to_fit(); // put back memory
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+uint32_t Audio::simpleHash(const char* str) {
+ if (str == NULL) return 0;
+ uint32_t hash = 0;
+ for (int i = 0; i < strlen(str); i++) {
+ if (str[i] < 32) continue; // ignore control sign
+ hash += (str[i] - 31) * i * 32;
+ }
+ return hash;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+ps_ptr Audio::urlencode(const char* str, bool spacesOnly) {
+ if (!str) { return {}; } // Enter is zero
+
+ // Reserve memory for the result (3x the length of the input string, worst-case)
+ size_t inputLength = strlen(str);
+ size_t bufferSize = inputLength * 3 + 1; // Worst-case-Szenario
+ ps_ptr encoded;
+ encoded.alloc(bufferSize, "encoded");
+ if (!encoded.valid()) { return {}; } // memory allocation failed
+
+ const char* p_input = str; // Copy of the input pointer
+ char* p_encoded = encoded.get(); // pointer of the output buffer
+ size_t remainingSpace = bufferSize; // remaining space in the output buffer
+
+ while (*p_input) {
+ if (isalnum((unsigned char)*p_input)) {
+ // adopt alphanumeric characters directly
+ if (remainingSpace > 1) {
+ *p_encoded++ = *p_input;
+ remainingSpace--;
+ } else {
+ return {}; // security check failed
+ }
+ } else if (spacesOnly && *p_input != 0x20) {
+ // Nur Leerzeichen nicht kodieren
+ if (remainingSpace > 1) {
+ *p_encoded++ = *p_input;
+ remainingSpace--;
+ } else {
+ return {}; // security check failed
+ }
+ } else {
+ // encode unsafe characters as '%XX'
+ if (remainingSpace > 3) {
+ int written = snprintf(p_encoded, remainingSpace, "%%%02X", (unsigned char)*p_input);
+ if (written < 0 || written >= (int)remainingSpace) {
+ return {}; // error writing to buffer
+ }
+ p_encoded += written;
+ remainingSpace -= written;
+ } else {
+ return {}; // security check failed
+ }
+ }
+ p_input++;
+ }
+
+ // Null-terminieren
+ if (remainingSpace > 0) {
+ *p_encoded = '\0';
+ } else {
+ return {}; // security check failed
+ }
+ encoded.shrink_to_fit();
+ return encoded;
+}
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+// separate task for decoding and outputting the data. 'playAudioData()' is started periodically and fetches the data from the InBuffer. This ensures
+// that the I2S-DMA is always sufficiently filled, even if the Arduino 'loop' is stuck.
+// —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+
+void Audio::setAudioTaskCore(uint8_t coreID) { // Recommendation:If the ARDUINO RUNNING CORE is 1, the audio task should be core 0 or vice versa
+ if (coreID > 1) return;
+ stopAudioTask();
+ xSemaphoreTake(mutex_audioTask, 0.3 * configTICK_RATE_HZ);
+ m_audioTaskCoreId = coreID;
+ xSemaphoreGive(mutex_audioTask);
+ startAudioTask();
+}
+
+void Audio::startAudioTask() {
+
+ if (m_f_audioTaskIsRunning) {
+ AUDIO_LOG_DEBUG("audio task is already running.");
+ return;
+ }
+ AUDIO_LOG_INFO("start audio task.");
+ m_f_audioTaskIsRunning = true;
+
+ m_audioTaskHandle = xTaskCreateStaticPinnedToCore(&Audio::audioTaskWrapper, /* Function to implement the task */
+ "PeriodicTask", /* Name of the task */
+ AUDIO_STACK_SIZE, /* Stack size in words */
+ this, /* Task input parameter */
+ 2, /* Priority of the task */
+ xAudioStack, /* Task stack */
+ &xAudioTaskBuffer, /* Memory for the task's control block */
+ m_audioTaskCoreId /* Core where the task should run */
+ );
+}
+
+void Audio::stopAudioTask() {
+ if (!m_f_audioTaskIsRunning) {
+ AUDIO_LOG_DEBUG("audio task is not running.");
+ return;
+ }
+ AUDIO_LOG_INFO("stop audio task.");
+ xSemaphoreTake(mutex_audioTask, 0.3 * configTICK_RATE_HZ);
+ m_f_audioTaskIsRunning = false;
+ if (m_audioTaskHandle != nullptr) {
+ vTaskDelete(m_audioTaskHandle);
+ m_audioTaskHandle = nullptr;
+ }
+ xSemaphoreGive(mutex_audioTask);
+}
+
+void Audio::audioTaskWrapper(void* param) {
+ Audio* audioRunner = static_cast