Compare commits
131 Commits
resiprocat
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
9a4823e6a9 | |
|
|
c3deb8378d | |
|
|
e0f07b6f7a | |
|
|
a7993c8d20 | |
|
|
1559190bcc | |
|
|
786cefed45 | |
|
|
186ec4ccb4 | |
|
|
ffd371d6e7 | |
|
|
f90d64b279 | |
|
|
cf27012d27 | |
|
|
e267415700 | |
|
|
3e87b9e7ec | |
|
|
3f9dbda40a | |
|
|
eec5aabc42 | |
|
|
4e00f659e3 | |
|
|
1599bfc9fc | |
|
|
6df835bb95 | |
|
|
fd878f0d0e | |
|
|
706339ec97 | |
|
|
a35db35b48 | |
|
|
46de58ccf4 | |
|
|
f891caedcc | |
|
|
cba8e24622 | |
|
|
84cc39e669 | |
|
|
916de57660 | |
|
|
e8bebc14c2 | |
|
|
a74f8a6cdf | |
|
|
dd242dfa48 | |
|
|
190d4fde54 | |
|
|
d105e582f4 | |
|
|
d4a47807d8 | |
|
|
e6cb2a22f7 | |
|
|
6b7b086bf7 | |
|
|
5a05d9d40b | |
|
|
48f6bf21d9 | |
|
|
6f55c91c7a | |
|
|
906a422865 | |
|
|
375bf64275 | |
|
|
b8c107fb5b | |
|
|
178ebac88a | |
|
|
6b6f4147db | |
|
|
011919f846 | |
|
|
94fea9d5a9 | |
|
|
028ce8b982 | |
|
|
5ddd7bbeed | |
|
|
e90ae17212 | |
|
|
1ada2a9159 | |
|
|
64d2209b02 | |
|
|
f9c009c40e | |
|
|
c67319838f | |
|
|
ebc9c3220e | |
|
|
9dc59d5ed6 | |
|
|
8b33a3a7e6 | |
|
|
d852fdde3a | |
|
|
f63fba6c6a | |
|
|
80451a1a32 | |
|
|
474256bc88 | |
|
|
fadaf3541f | |
|
|
efa31427ca | |
|
|
83373cb586 | |
|
|
8d0c8ba4de | |
|
|
a0b7cdcd99 | |
|
|
933d52b297 | |
|
|
efbf9c5bf8 | |
|
|
f30e111ebe | |
|
|
a7176fefeb | |
|
|
caa2239da1 | |
|
|
93bf339f00 | |
|
|
7510ff5697 | |
|
|
5ce85e3a09 | |
|
|
34e43bf301 | |
|
|
84d11d3b04 | |
|
|
cedc44e24c | |
|
|
6fef519a4b | |
|
|
f8dd94dfa4 | |
|
|
c45c686582 | |
|
|
b5d0242c74 | |
|
|
4ba1ea7fa2 | |
|
|
bf2d513080 | |
|
|
fe52356799 | |
|
|
0e044faf16 | |
|
|
4d7ca3e4f3 | |
|
|
9afbd9e0fd | |
|
|
d895385d19 | |
|
|
cde5cca940 | |
|
|
34c28e35e1 | |
|
|
e0b02db39d | |
|
|
8cb34fad83 | |
|
|
f0c37b1bb1 | |
|
|
5bf8d665e2 | |
|
|
f3ebe11a42 | |
|
|
3a81626b8e | |
|
|
10ec751e43 | |
|
|
7c346fbe9b | |
|
|
caf1283f0a | |
|
|
2c2167c840 | |
|
|
d9b8f4d2d0 | |
|
|
62e4828241 | |
|
|
81bc17fbe2 | |
|
|
5a0a7e1070 | |
|
|
20c2ac3e63 | |
|
|
d08b2e27a5 | |
|
|
4cff4a0988 | |
|
|
5e6b4bb929 | |
|
|
7aadde3a09 | |
|
|
62f2d996c6 | |
|
|
05e3cc8cb6 | |
|
|
5030064925 | |
|
|
9f875b5f15 | |
|
|
4ca4e84547 | |
|
|
cf9be1c3f2 | |
|
|
8a851e5e86 | |
|
|
62d72fda5c | |
|
|
37471d56ff | |
|
|
69374d2973 | |
|
|
5eb4bf2690 | |
|
|
677faa0615 | |
|
|
8ec6245496 | |
|
|
4ce7f50964 | |
|
|
3f33c86949 | |
|
|
c924329203 | |
|
|
75c4888e43 | |
|
|
2eb0d17fba | |
|
|
8dd494157f | |
|
|
22653e6241 | |
|
|
fa053324fa | |
|
|
978079fd55 | |
|
|
ace77323d2 | |
|
|
6ff23247ec | |
|
|
9ef74113b3 | |
|
|
3beeac2d0c |
|
|
@ -0,0 +1,12 @@
|
||||||
|
[submodule "src/libs/resiprocate"]
|
||||||
|
path = src/libs/resiprocate
|
||||||
|
url = git@git.sevana.biz:public/resiprocate.git
|
||||||
|
branch = sevana
|
||||||
|
|
||||||
|
[submodule "src/libs/libsrtp"]
|
||||||
|
path = src/libs/libsrtp
|
||||||
|
url = git@git.sevana.biz:public/libsrtp.git
|
||||||
|
branch = master
|
||||||
|
[submodule "src/libs/libraries"]
|
||||||
|
path = src/libs/libraries
|
||||||
|
url = git@git.sevana.biz:public/libraries.git
|
||||||
|
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
# RTPhone Platform
|
||||||
|
|
||||||
|
RTPhone is a comprehensive real-time communication (RTC) platform that provides a complete software stack for building VoIP/SIP-based communication applications. Developed by VoIP Objects (Sevana), RTPhone delivers production-ready voice communication capabilities with extensive codec support and cross-platform compatibility.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
RTPhone serves as a static library (`librtphone.a`) that can be integrated into larger telephony and communication systems. It provides a JSON-based command interface for easy integration and control, making it suitable for building softphones, PBX systems, WebRTC gateways, and carrier-grade voice solutions.
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Audio Codec Support
|
||||||
|
RTPhone supports an extensive range of audio codecs:
|
||||||
|
|
||||||
|
**Standard Codecs:**
|
||||||
|
- G.711 (A-law/¼-law)
|
||||||
|
- G.722 (16kHz wideband)
|
||||||
|
- G.729
|
||||||
|
- GSM (Full Rate, Half Rate, Enhanced Full Rate)
|
||||||
|
- iLBC (20ms/30ms)
|
||||||
|
- ISAC (16kHz/32kHz)
|
||||||
|
|
||||||
|
**Advanced Codecs:**
|
||||||
|
- AMR-NB/AMR-WB (Adaptive Multi-Rate Narrowband/Wideband) - please be aware - there is no patents for AMR codecs usage included ! You should acquire them on your own.
|
||||||
|
- EVS (Enhanced Voice Services) - 3GPP's latest codec. Again - please be aware - there is no patents for EVS codec usage included ! You should acquire them on your own.
|
||||||
|
- Opus - Modern low-latency codec
|
||||||
|
- Speex (with acoustic echo cancellation)
|
||||||
|
|
||||||
|
**Codec Features:**
|
||||||
|
- Bandwidth-efficient and octet-aligned modes
|
||||||
|
- IuUP (Iu User Plane) protocol support for 3G networks
|
||||||
|
- Dynamic codec switching
|
||||||
|
- Packet loss concealment (PLC)
|
||||||
|
- Comfort noise generation (CNG)
|
||||||
|
|
||||||
|
### Network & Protocol Support
|
||||||
|
|
||||||
|
**SIP Features:**
|
||||||
|
- Full SIP 2.0 implementation via reSIProcate
|
||||||
|
- Multiple transport protocols (UDP, TCP, TLS)
|
||||||
|
- Registration, authentication, and session management
|
||||||
|
- SIP MESSAGE, presence, and REFER support
|
||||||
|
|
||||||
|
**Media Transport:**
|
||||||
|
- RTP/RTCP for media streaming
|
||||||
|
- SRTP for secure media
|
||||||
|
- ICE for NAT traversal with STUN/TURN support
|
||||||
|
- WebRTC integration components
|
||||||
|
- IPv4 and IPv6 support
|
||||||
|
|
||||||
|
### Cross-Platform Audio Support
|
||||||
|
- DirectSound/WMME (Windows)
|
||||||
|
- Core Audio (macOS/iOS)
|
||||||
|
- ALSA/PulseAudio (Linux)
|
||||||
|
- Oboe (Android) for low-latency audio
|
||||||
|
- PortAudio fallback support
|
||||||
|
|
||||||
|
### Audio Quality Features
|
||||||
|
- 48kHz sample rate support
|
||||||
|
- Acoustic Echo Cancellation (AEC)
|
||||||
|
- Audio resampling and format conversion
|
||||||
|
- Multi-channel audio mixing
|
||||||
|
- Perceptual Voice Quality Assessment (PVQA)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The platform is organized into several core modules:
|
||||||
|
|
||||||
|
- **Engine/Agent**: JSON-based command interface
|
||||||
|
- **Engine/Endpoint**: SIP user agent implementation
|
||||||
|
- **Engine/Media**: Audio codec management and processing
|
||||||
|
- **Engine/Audio**: Cross-platform audio I/O handling
|
||||||
|
- **Engine/Helper**: Utility functions (networking, logging, threading)
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
- Linux (x64, ARM/Raspberry Pi)
|
||||||
|
- Windows (32/64-bit)
|
||||||
|
- macOS
|
||||||
|
- Android (with Oboe integration)
|
||||||
|
- iOS
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
RTPhone uses a CMake-based build system with cross-compilation support:
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
```bash
|
||||||
|
python3 build_linux.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android
|
||||||
|
```bash
|
||||||
|
python3 build_android.py
|
||||||
|
# or
|
||||||
|
./build_android.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- CMake 3.10+
|
||||||
|
- OpenSSL 1.1+
|
||||||
|
- Boost libraries
|
||||||
|
- Platform-specific audio libraries
|
||||||
|
|
||||||
|
## Recent Updates
|
||||||
|
|
||||||
|
Recent development has focused on:
|
||||||
|
- AMR codec parsing and decoding improvements
|
||||||
|
- Octet-aligned mode fixes for AMR-WB
|
||||||
|
- RTP SSRC handling enhancements
|
||||||
|
- Build system optimizations
|
||||||
|
- Code modernization to C++20
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
RTPhone is good for building:
|
||||||
|
- VoIP softphones and mobile applications
|
||||||
|
- PBX and telephony server systems
|
||||||
|
- RTP proxies
|
||||||
|
- Carrier-grade voice communication platforms
|
||||||
|
- 3GPP/IMS-compliant systems
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
RTPhone includes comprehensive security features:
|
||||||
|
- OpenSSL 1.1 integration for encryption
|
||||||
|
- TLS transport layer security
|
||||||
|
- SRTP media encryption
|
||||||
|
- Certificate management support
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
The platform provides a developer-friendly interface with:
|
||||||
|
- Event-driven architecture
|
||||||
|
- Comprehensive logging system
|
||||||
|
- Modern C++20 codebase
|
||||||
|
|
||||||
|
For detailed integration instructions and API documentation, please refer to the source code and header files in the `/src/engine/` directory.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Our source code is licensed under the MPL license. Naturally, any third-party components we use are subject to their respective licenses.
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import multiprocessing
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# Temporary build directory
|
||||||
|
DIR_BUILD = 'build_android'
|
||||||
|
|
||||||
|
# Android NDK home directory
|
||||||
|
NDK_HOME = os.environ['ANDROID_NDK_HOME']
|
||||||
|
|
||||||
|
# CMake toolchain file
|
||||||
|
TOOLCHAIN_FILE = f'{NDK_HOME}/build/cmake/android.toolchain.cmake'
|
||||||
|
|
||||||
|
# This directory
|
||||||
|
DIR_THIS = Path(__file__).parent.resolve()
|
||||||
|
|
||||||
|
# Path to app
|
||||||
|
DIR_SOURCE = (DIR_THIS / '../src').resolve()
|
||||||
|
|
||||||
|
def make_build() -> Path:
|
||||||
|
if Path(DIR_BUILD).exists():
|
||||||
|
shutil.rmtree(DIR_BUILD)
|
||||||
|
os.mkdir(DIR_BUILD)
|
||||||
|
os.chdir(DIR_BUILD)
|
||||||
|
|
||||||
|
cmd = f'cmake -DCMAKE_TOOLCHAIN_FILE={TOOLCHAIN_FILE} '
|
||||||
|
cmd += f'-DANDROID_NDK=$NDK_HOME '
|
||||||
|
cmd += f'-DANDROID_PLATFORM=24 '
|
||||||
|
cmd += f'-DCMAKE_BUILD=Release '
|
||||||
|
cmd += f'-DANDROID_ABI="arm64-v8a" '
|
||||||
|
cmd += '../src'
|
||||||
|
retcode = os.system(cmd)
|
||||||
|
if retcode != 0:
|
||||||
|
raise RuntimeError('Problem when configuring the project')
|
||||||
|
|
||||||
|
cmd = f'cmake --build . -j {multiprocessing.cpu_count()}'
|
||||||
|
retcode = os.system(cmd)
|
||||||
|
if retcode != 0:
|
||||||
|
raise RuntimeError('Problem when building the project')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
make_build()
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import multiprocessing
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# Temporary build directory
|
||||||
|
DIR_BUILD = 'build_linux'
|
||||||
|
|
||||||
|
# This directory
|
||||||
|
DIR_THIS = Path(__file__).parent.resolve()
|
||||||
|
|
||||||
|
# Path to app
|
||||||
|
DIR_SOURCE = (DIR_THIS / '../src').resolve()
|
||||||
|
|
||||||
|
def make_build() -> Path:
|
||||||
|
if Path(DIR_BUILD).exists():
|
||||||
|
shutil.rmtree(DIR_BUILD)
|
||||||
|
os.mkdir(DIR_BUILD)
|
||||||
|
os.chdir(DIR_BUILD)
|
||||||
|
|
||||||
|
cmd = f'cmake ../src -G Ninja'
|
||||||
|
retcode = os.system(cmd)
|
||||||
|
if retcode != 0:
|
||||||
|
raise RuntimeError('Problem when configuring the project')
|
||||||
|
|
||||||
|
cmd = f'cmake --build . -j {multiprocessing.cpu_count()}'
|
||||||
|
retcode = os.system(cmd)
|
||||||
|
if retcode != 0:
|
||||||
|
raise RuntimeError('Problem when building the project')
|
||||||
|
|
||||||
|
os.chdir('..')
|
||||||
|
return Path(DIR_BUILD) / 'librtphone.a'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
p = make_build()
|
||||||
|
print (f'Built: {p}')
|
||||||
|
|
@ -1,61 +1,12 @@
|
||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(rtphone)
|
project(rtphone)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.0)
|
# Rely on C++ 20
|
||||||
|
set (CMAKE_CXX_STANDARD 20)
|
||||||
macro(configure_msvc_runtime)
|
|
||||||
if(MSVC)
|
|
||||||
# Default to statically-linked runtime.
|
|
||||||
if("${MSVC_RUNTIME}" STREQUAL "")
|
|
||||||
set(MSVC_RUNTIME "static")
|
|
||||||
endif()
|
|
||||||
# Set compiler options.
|
|
||||||
set(variables
|
|
||||||
CMAKE_C_FLAGS_DEBUG
|
|
||||||
CMAKE_C_FLAGS_MINSIZEREL
|
|
||||||
CMAKE_C_FLAGS_RELEASE
|
|
||||||
CMAKE_C_FLAGS_RELWITHDEBINFO
|
|
||||||
CMAKE_CXX_FLAGS_DEBUG
|
|
||||||
CMAKE_CXX_FLAGS_MINSIZEREL
|
|
||||||
CMAKE_CXX_FLAGS_RELEASE
|
|
||||||
CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
|
||||||
)
|
|
||||||
if(${MSVC_RUNTIME} STREQUAL "static")
|
|
||||||
message(STATUS
|
|
||||||
"rtphone: MSVC -> forcing use of statically-linked runtime."
|
|
||||||
)
|
|
||||||
foreach(variable ${variables})
|
|
||||||
if(${variable} MATCHES "/MD")
|
|
||||||
string(REGEX REPLACE "/MD" "/MT" ${variable} "${${variable}}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
else()
|
|
||||||
message(STATUS
|
|
||||||
"rtphone: MSVC -> forcing use of dynamically-linked runtime."
|
|
||||||
)
|
|
||||||
foreach(variable ${variables})
|
|
||||||
if(${variable} MATCHES "/MT")
|
|
||||||
string(REGEX REPLACE "/MT" "/MD" ${variable} "${${variable}}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(variable ${variables})
|
|
||||||
string(REGEX REPLACE "/Z[iI7]" ""
|
|
||||||
${variable}
|
|
||||||
"${${variable}}")
|
|
||||||
|
|
||||||
set(${variable} "${${variable}} /Zi /Oy-")
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
|
|
||||||
# Rely on C++ 17
|
|
||||||
set (CMAKE_CXX_STANDARD 17)
|
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set (rtphone_libs libs)
|
set (L libs)
|
||||||
set (rtphone_engine engine)
|
set (E engine)
|
||||||
|
|
||||||
option (USE_AMR_CODEC "Use AMR codec. Requires libraries." ON)
|
option (USE_AMR_CODEC "Use AMR codec. Requires libraries." ON)
|
||||||
option (USE_EVS_CODEC "Use EVS codec." ON)
|
option (USE_EVS_CODEC "Use EVS codec." ON)
|
||||||
|
|
@ -64,149 +15,347 @@ option (USE_MUSL "Build with MUSL library" OFF)
|
||||||
|
|
||||||
# PIC code by default
|
# PIC code by default
|
||||||
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
set (RUNTIME_CPU_CAPABILITY_DETECTION ON)
|
||||||
|
|
||||||
if (NOT DEFINED LIB_PLATFORM)
|
set (LIB_PLATFORM ${CMAKE_CURRENT_SOURCE_DIR}/libs/libraries)
|
||||||
set (LIB_PLATFORM ${CMAKE_CURRENT_SOURCE_DIR}/../../libraries)
|
|
||||||
endif()
|
|
||||||
include (${LIB_PLATFORM}/platform_libs.cmake)
|
include (${LIB_PLATFORM}/platform_libs.cmake)
|
||||||
|
|
||||||
|
|
||||||
message("Libraries: ${LIB_PLATFORM}")
|
message("Libraries: ${LIB_PLATFORM}")
|
||||||
set (OPENSSL_INCLUDE ${LIB_PLATFORM}/openssl/1.1/include)
|
set (OPENSSL_INCLUDE ${LIB_PLATFORM}/openssl/1.1/include)
|
||||||
message ("Using OpenSSL include files from ${OPENSSL_INCLUDE}")
|
message ("Using OpenSSL include files from ${OPENSSL_INCLUDE}")
|
||||||
message ("Using OpenSSL libs: ${OPENSSL_SSL} and ${OPENSSL_CRYPTO}")
|
message ("Using OpenSSL libs: ${OPENSSL_SSL} and ${OPENSSL_CRYPTO}")
|
||||||
add_definitions(-DUSE_OPENSSL)
|
|
||||||
include_directories(${OPENSSL_INCLUDE})
|
include_directories(${OPENSSL_INCLUDE})
|
||||||
|
|
||||||
|
# Used defines for our project
|
||||||
|
set (DEFINES -DUSE_OPENSSL)
|
||||||
|
|
||||||
|
# Libraries for our project
|
||||||
|
set (LIBS_STATIC "")
|
||||||
|
set (LIBS_DYNAMIC "")
|
||||||
|
|
||||||
|
# Try to prefer static libraries anyway
|
||||||
|
set (CMAKE_FIND_LIBRARY_SUFFIXES .a .so .dylib)
|
||||||
|
|
||||||
|
# Windows-specific definitions
|
||||||
if (CMAKE_SYSTEM MATCHES "Windows*")
|
if (CMAKE_SYSTEM MATCHES "Windows*")
|
||||||
add_definitions (-DTARGET_WIN -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS -D_UNICODE -D_CRT_SECURE_NO_WARNINGS)
|
set (DEFINES ${DEFINES} -DTARGET_WIN -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS -D_UNICODE -D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
set (TARGET_WIN ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Linux-specific definitions
|
||||||
if (CMAKE_SYSTEM MATCHES "Linux*")
|
if (CMAKE_SYSTEM MATCHES "Linux*")
|
||||||
add_definitions (-DTARGET_LINUX -DHAVE_NETINET_IN_H)
|
set (DEFINES ${DEFINES} -DTARGET_LINUX -DHAVE_NETINET_IN_H)
|
||||||
|
set (TARGET_LINUX ON)
|
||||||
|
set (LIBS_STATIC ${LIBS_STATIC} dl)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# macOS-specific definitions
|
||||||
if (CMAKE_SYSTEM MATCHES "Darwin*")
|
if (CMAKE_SYSTEM MATCHES "Darwin*")
|
||||||
add_definitions (-DTARGET_OSX)
|
set (DEFINES ${DEFINES} -DTARGET_OSX)
|
||||||
|
set (TARGET_OSX ON)
|
||||||
|
set (LIBS_STATIC ${LIBS_STATIC} dl)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
#
|
||||||
if (CMAKE_SYSTEM MATCHES "Android")
|
if (CMAKE_SYSTEM MATCHES "Android")
|
||||||
message("Adding the Oboe library")
|
message("Adding the Oboe library")
|
||||||
set (OBOE_DIR libs/oboe)
|
set (OBOE_DIR libs/oboe)
|
||||||
add_subdirectory (${OBOE_DIR} ./oboe)
|
add_subdirectory (${OBOE_DIR} ./oboe)
|
||||||
include_directories (${OBOE_DIR}/include)
|
include_directories (${OBOE_DIR}/include)
|
||||||
add_definitions(-DTARGET_ANDROID -DHAVE_NETINET_IN_H)
|
set (DEFINES ${DEFINES} -DTARGET_ANDROID -DHAVE_NETINET_IN_H)
|
||||||
|
set (TARGET_ANDROID ON)
|
||||||
|
set (LIBS_STATIC ${LIBS} oboe)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_MUSL)
|
if (USE_MUSL)
|
||||||
add_definitions(-DTARGET_MUSL)
|
set (DEFINES ${DEFINES} -DTARGET_MUSL)
|
||||||
|
set (TARGET_MUSL ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set (RTPHONE_SOURCES
|
set (RTPHONE_SOURCES
|
||||||
${rtphone_engine}/media/MT_Statistics.cpp
|
${E}/engine_config.h
|
||||||
${rtphone_engine}/media/MT_WebRtc.cpp
|
${E}/media/MT_Statistics.cpp
|
||||||
${rtphone_engine}/media/MT_Stream.cpp
|
${E}/media/MT_WebRtc.cpp
|
||||||
${rtphone_engine}/media/MT_SrtpHelper.cpp
|
${E}/media/MT_Stream.cpp
|
||||||
${rtphone_engine}/media/MT_SingleAudioStream.cpp
|
${E}/media/MT_SrtpHelper.cpp
|
||||||
${rtphone_engine}/media/MT_NativeRtpSender.cpp
|
${E}/media/MT_SingleAudioStream.cpp
|
||||||
${rtphone_engine}/media/MT_Dtmf.cpp
|
${E}/media/MT_NativeRtpSender.cpp
|
||||||
${rtphone_engine}/media/MT_CodecList.cpp
|
${E}/media/MT_Dtmf.cpp
|
||||||
${rtphone_engine}/media/MT_Codec.cpp
|
${E}/media/MT_CodecList.cpp
|
||||||
${rtphone_engine}/media/MT_Box.cpp
|
${E}/media/MT_Codec.cpp
|
||||||
${rtphone_engine}/media/MT_AudioStream.cpp
|
${E}/media/MT_Box.cpp
|
||||||
${rtphone_engine}/media/MT_AudioReceiver.cpp
|
${E}/media/MT_AudioStream.cpp
|
||||||
${rtphone_engine}/media/MT_AudioCodec.cpp
|
${E}/media/MT_AudioReceiver.cpp
|
||||||
${rtphone_engine}/media/MT_CngHelper.cpp
|
${E}/media/MT_AudioCodec.cpp
|
||||||
${rtphone_engine}/agent/Agent_Impl.cpp
|
${E}/media/MT_CngHelper.cpp
|
||||||
${rtphone_engine}/agent/Agent_Impl.h
|
${E}/agent/Agent_Impl.cpp
|
||||||
${rtphone_engine}/agent/Agent_AudioManager.cpp
|
${E}/agent/Agent_Impl.h
|
||||||
${rtphone_engine}/agent/Agent_AudioManager.h
|
${E}/agent/Agent_AudioManager.cpp
|
||||||
${rtphone_engine}/endpoint/EP_Account.cpp
|
${E}/agent/Agent_AudioManager.h
|
||||||
${rtphone_engine}/endpoint/EP_Account.h
|
${E}/endpoint/EP_Account.cpp
|
||||||
${rtphone_engine}/endpoint/EP_AudioProvider.cpp
|
${E}/endpoint/EP_Account.h
|
||||||
${rtphone_engine}/endpoint/EP_AudioProvider.h
|
${E}/endpoint/EP_AudioProvider.cpp
|
||||||
${rtphone_engine}/endpoint/EP_DataProvider.cpp
|
${E}/endpoint/EP_AudioProvider.h
|
||||||
${rtphone_engine}/endpoint/EP_DataProvider.h
|
${E}/endpoint/EP_DataProvider.cpp
|
||||||
${rtphone_engine}/endpoint/EP_Engine.cpp
|
${E}/endpoint/EP_DataProvider.h
|
||||||
${rtphone_engine}/endpoint/EP_Engine.h
|
${E}/endpoint/EP_Engine.cpp
|
||||||
${rtphone_engine}/endpoint/EP_NetworkQueue.cpp
|
${E}/endpoint/EP_Engine.h
|
||||||
${rtphone_engine}/endpoint/EP_NetworkQueue.h
|
${E}/endpoint/EP_NetworkQueue.cpp
|
||||||
${rtphone_engine}/endpoint/EP_Observer.cpp
|
${E}/endpoint/EP_NetworkQueue.h
|
||||||
${rtphone_engine}/endpoint/EP_Observer.h
|
${E}/endpoint/EP_Observer.cpp
|
||||||
${rtphone_engine}/endpoint/EP_Session.cpp
|
${E}/endpoint/EP_Observer.h
|
||||||
${rtphone_engine}/endpoint/EP_Session.h
|
${E}/endpoint/EP_Session.cpp
|
||||||
|
${E}/endpoint/EP_Session.h
|
||||||
|
|
||||||
${rtphone_engine}/media/MT_Statistics.h
|
${E}/media/MT_Statistics.h
|
||||||
${rtphone_engine}/media/MT_WebRtc.h
|
${E}/media/MT_WebRtc.h
|
||||||
${rtphone_engine}/media/MT_Stream.h
|
${E}/media/MT_Stream.h
|
||||||
${rtphone_engine}/media/MT_SrtpHelper.h
|
${E}/media/MT_SrtpHelper.h
|
||||||
${rtphone_engine}/media/MT_SingleAudioStream.h
|
${E}/media/MT_SingleAudioStream.h
|
||||||
${rtphone_engine}/media/MT_NativeRtpSender.h
|
${E}/media/MT_NativeRtpSender.h
|
||||||
${rtphone_engine}/media/MT_Dtmf.h
|
${E}/media/MT_Dtmf.h
|
||||||
${rtphone_engine}/media/MT_CodecList.h
|
${E}/media/MT_CodecList.h
|
||||||
${rtphone_engine}/media/MT_Codec.h
|
${E}/media/MT_Codec.h
|
||||||
${rtphone_engine}/media/MT_Box.h
|
${E}/media/MT_Box.h
|
||||||
${rtphone_engine}/media/MT_AudioStream.h
|
${E}/media/MT_AudioStream.h
|
||||||
${rtphone_engine}/media/MT_AudioReceiver.h
|
${E}/media/MT_AudioReceiver.h
|
||||||
${rtphone_engine}/media/MT_AudioCodec.h
|
${E}/media/MT_AudioCodec.h
|
||||||
${rtphone_engine}/media/MT_CngHelper.h
|
${E}/media/MT_CngHelper.h
|
||||||
|
|
||||||
|
${E}/media/MT_Statistics.cpp
|
||||||
|
${E}/media/MT_WebRtc.cpp
|
||||||
|
${E}/media/MT_Stream.cpp
|
||||||
|
${E}/media/MT_SrtpHelper.cpp
|
||||||
|
${E}/media/MT_SingleAudioStream.cpp
|
||||||
|
${E}/media/MT_NativeRtpSender.cpp
|
||||||
|
${E}/media/MT_Dtmf.cpp
|
||||||
|
${E}/media/MT_CodecList.cpp
|
||||||
|
${E}/media/MT_Codec.cpp
|
||||||
|
${E}/media/MT_Box.cpp
|
||||||
|
${E}/media/MT_AudioStream.cpp
|
||||||
|
${E}/media/MT_AudioReceiver.cpp
|
||||||
|
${E}/media/MT_AudioCodec.cpp
|
||||||
|
${E}/media/MT_CngHelper.cpp
|
||||||
|
${E}/media/MT_AmrCodec.cpp
|
||||||
|
${E}/media/MT_EvsCodec.cpp
|
||||||
|
${E}/media/MT_Statistics.h
|
||||||
|
${E}/media/MT_WebRtc.h
|
||||||
|
${E}/media/MT_Stream.h
|
||||||
|
${E}/media/MT_SrtpHelper.h
|
||||||
|
${E}/media/MT_SingleAudioStream.h
|
||||||
|
${E}/media/MT_NativeRtpSender.h
|
||||||
|
${E}/media/MT_Dtmf.h
|
||||||
|
${E}/media/MT_CodecList.h
|
||||||
|
${E}/media/MT_Codec.h
|
||||||
|
${E}/media/MT_Box.h
|
||||||
|
${E}/media/MT_AudioStream.h
|
||||||
|
${E}/media/MT_AudioReceiver.h
|
||||||
|
${E}/media/MT_AudioCodec.h
|
||||||
|
${E}/media/MT_CngHelper.h
|
||||||
|
${E}/media/MT_AmrCodec.h
|
||||||
|
${E}/media/MT_EvsCodec.h
|
||||||
|
|
||||||
|
${E}/helper/HL_AsyncCommand.cpp
|
||||||
|
${E}/helper/HL_AsyncCommand.h
|
||||||
|
${E}/helper/HL_Base64.h
|
||||||
|
${E}/helper/HL_ByteBuffer.h
|
||||||
|
${E}/helper/HL_Calculator.cpp
|
||||||
|
${E}/helper/HL_Calculator.h
|
||||||
|
${E}/helper/HL_CrashRpt.cpp
|
||||||
|
${E}/helper/HL_CrashRpt.h
|
||||||
|
${E}/helper/HL_CsvReader.cpp
|
||||||
|
${E}/helper/HL_CsvReader.h
|
||||||
|
${E}/helper/HL_Epoll.cpp
|
||||||
|
${E}/helper/HL_Epoll.h
|
||||||
|
${E}/helper/HL_Exception.h
|
||||||
|
${E}/helper/HL_File.cpp
|
||||||
|
${E}/helper/HL_File.h
|
||||||
|
${E}/helper/HL_HepSupport.cpp
|
||||||
|
${E}/helper/HL_HepSupport.h
|
||||||
|
${E}/helper/HL_InternetAddress.h
|
||||||
|
${E}/helper/HL_IuUP.cpp
|
||||||
|
${E}/helper/HL_IuUP.h
|
||||||
|
${E}/helper/HL_Log.cpp
|
||||||
|
${E}/helper/HL_Log.h
|
||||||
|
${E}/helper/HL_NetworkFrame.cpp
|
||||||
|
${E}/helper/HL_NetworkFrame.h
|
||||||
|
${E}/helper/HL_NetworkSocket.cpp
|
||||||
|
${E}/helper/HL_NetworkSocket.h
|
||||||
|
${E}/helper/HL_Optional.hpp
|
||||||
|
${E}/helper/HL_OsVersion.cpp
|
||||||
|
${E}/helper/HL_OsVersion.h
|
||||||
|
${E}/helper/HL_Pointer.cpp
|
||||||
|
${E}/helper/HL_Pointer.h
|
||||||
|
${E}/helper/HL_Process.cpp
|
||||||
|
${E}/helper/HL_Process.h
|
||||||
|
${E}/helper/HL_Rtp.cpp
|
||||||
|
${E}/helper/HL_Rtp.h
|
||||||
|
${E}/helper/HL_Singletone.cpp
|
||||||
|
${E}/helper/HL_Singletone.h
|
||||||
|
${E}/helper/HL_SocketHeap.cpp
|
||||||
|
${E}/helper/HL_SocketHeap.h
|
||||||
|
${E}/helper/HL_Statistics.cpp
|
||||||
|
${E}/helper/HL_Statistics.h
|
||||||
|
${E}/helper/HL_StreamState.h
|
||||||
|
${E}/helper/HL_String.cpp
|
||||||
|
${E}/helper/HL_String.h
|
||||||
|
${E}/helper/HL_Sync.cpp
|
||||||
|
${E}/helper/HL_Sync.h
|
||||||
|
${E}/helper/HL_ThreadPool.cpp
|
||||||
|
${E}/helper/HL_ThreadPool.h
|
||||||
|
${E}/helper/HL_Time.cpp
|
||||||
|
${E}/helper/HL_Time.h
|
||||||
|
${E}/helper/HL_Types.h
|
||||||
|
${E}/helper/HL_Types.cpp
|
||||||
|
${E}/helper/HL_Usb.cpp
|
||||||
|
${E}/helper/HL_Usb.h
|
||||||
|
${E}/helper/HL_Uuid.cpp
|
||||||
|
${E}/helper/HL_Uuid.h
|
||||||
|
${E}/helper/HL_VariantMap.cpp
|
||||||
|
${E}/helper/HL_VariantMap.h
|
||||||
|
${E}/helper/HL_Xcap.cpp
|
||||||
|
${E}/helper/HL_Xcap.h
|
||||||
|
|
||||||
|
${E}/audio/Audio_Resampler.cpp
|
||||||
|
${E}/audio/Audio_Resampler.h
|
||||||
|
${E}/audio/Audio_Quality.cpp
|
||||||
|
${E}/audio/Audio_Quality.h
|
||||||
|
${E}/audio/Audio_Mixer.cpp
|
||||||
|
${E}/audio/Audio_Mixer.h
|
||||||
|
${E}/audio/Audio_Interface.cpp
|
||||||
|
${E}/audio/Audio_Interface.h
|
||||||
|
${E}/audio/Audio_Helper.cpp
|
||||||
|
${E}/audio/Audio_Helper.h
|
||||||
|
${E}/audio/Audio_DataWindow.cpp
|
||||||
|
${E}/audio/Audio_DataWindow.h
|
||||||
|
${E}/audio/Audio_DevicePair.cpp
|
||||||
|
${E}/audio/Audio_DevicePair.h
|
||||||
|
${E}/audio/Audio_Player.cpp
|
||||||
|
${E}/audio/Audio_Player.h
|
||||||
|
${E}/audio/Audio_Null.cpp
|
||||||
|
${E}/audio/Audio_Null.h
|
||||||
|
${E}/audio/Audio_CoreAudio.cpp
|
||||||
|
${E}/audio/Audio_CoreAudio.h
|
||||||
|
${E}/audio/Audio_DirectSound.cpp
|
||||||
|
${E}/audio/Audio_DirectSound.h
|
||||||
|
${E}/audio/Audio_AndroidOboe.cpp
|
||||||
|
${E}/audio/Audio_AndroidOboe.h
|
||||||
|
${E}/audio/Audio_WavFile.cpp
|
||||||
|
${E}/audio/Audio_WavFile.h
|
||||||
|
|
||||||
|
${L}/ice/hmac_sha1_impl.cpp
|
||||||
|
${L}/ice/hmac_sha1_impl.h
|
||||||
|
${L}/ice/ICEAction.h
|
||||||
|
${L}/ice/ICEAddress.cpp
|
||||||
|
${L}/ice/ICEAddress.h
|
||||||
|
${L}/ice/ICEAuthTransaction.cpp
|
||||||
|
${L}/ice/ICEAuthTransaction.h
|
||||||
|
${L}/ice/ICEBinding.cpp
|
||||||
|
${L}/ice/ICEBinding.h
|
||||||
|
${L}/ice/ICEBox.cpp
|
||||||
|
${L}/ice/ICEBox.h
|
||||||
|
${L}/ice/ICEBoxImpl.cpp
|
||||||
|
${L}/ice/ICEBoxImpl.h
|
||||||
|
${L}/ice/ICEByteBuffer.cpp
|
||||||
|
${L}/ice/ICEByteBuffer.h
|
||||||
|
${L}/ice/ICECandidate.cpp
|
||||||
|
${L}/ice/ICECandidate.h
|
||||||
|
${L}/ice/ICECandidatePair.cpp
|
||||||
|
${L}/ice/ICECandidatePair.h
|
||||||
|
${L}/ice/ICECheckList.cpp
|
||||||
|
${L}/ice/ICECheckList.h
|
||||||
|
${L}/ice/ICECRC32.cpp
|
||||||
|
${L}/ice/ICECRC32.h
|
||||||
|
${L}/ice/ICEError.cpp
|
||||||
|
${L}/ice/ICEError.h
|
||||||
|
${L}/ice/ICEEvent.h
|
||||||
|
${L}/ice/ICELog.cpp
|
||||||
|
${L}/ice/ICELog.h
|
||||||
|
${L}/ice/ICEMD5.cpp
|
||||||
|
${L}/ice/ICEMD5.h
|
||||||
|
${L}/ice/ICENetworkHelper.cpp
|
||||||
|
${L}/ice/ICENetworkHelper.h
|
||||||
|
${L}/ice/ICEPacketTimer.cpp
|
||||||
|
${L}/ice/ICEPacketTimer.h
|
||||||
|
${L}/ice/ICEPlatform.cpp
|
||||||
|
${L}/ice/ICEPlatform.h
|
||||||
|
${L}/ice/ICERelaying.cpp
|
||||||
|
${L}/ice/ICERelaying.h
|
||||||
|
${L}/ice/ICESession.cpp
|
||||||
|
${L}/ice/ICESession.h
|
||||||
|
${L}/ice/ICESHA1.cpp
|
||||||
|
${L}/ice/ICESHA1.h
|
||||||
|
${L}/ice/ICESocket.h
|
||||||
|
${L}/ice/ICEStream.cpp
|
||||||
|
${L}/ice/ICEStream.h
|
||||||
|
${L}/ice/ICEStunAttributes.cpp
|
||||||
|
${L}/ice/ICEStunAttributes.h
|
||||||
|
${L}/ice/ICEStunConfig.cpp
|
||||||
|
${L}/ice/ICEStunConfig.h
|
||||||
|
${L}/ice/ICEStunMessage.cpp
|
||||||
|
${L}/ice/ICEStunMessage.h
|
||||||
|
${L}/ice/ICEStunTransaction.cpp
|
||||||
|
${L}/ice/ICEStunTransaction.h
|
||||||
|
${L}/ice/ICESync.cpp
|
||||||
|
${L}/ice/ICESync.h
|
||||||
|
${L}/ice/ICETime.cpp
|
||||||
|
${L}/ice/ICETime.h
|
||||||
|
${L}/ice/ICETransactionList.cpp
|
||||||
|
${L}/ice/ICETransactionList.h
|
||||||
|
${L}/ice/ICETypes.h
|
||||||
|
${L}/ice/md5_impl.cpp
|
||||||
|
${L}/ice/md5_impl.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (USE_AMR_CODEC)
|
|
||||||
add_definitions(-DUSE_AMR_CODEC)
|
|
||||||
set (RTPHONE_SOURCES ${RTPHONE_SOURCES} ${rtphone_engine}/media/MT_AmrCodec.cpp ${rtphone_engine}/media/MT_AmrCodec.h)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_EVS_CODEC)
|
|
||||||
add_definitions(-DUSE_EVS_CODEC)
|
|
||||||
set (RTPHONE_SOURCES ${RTPHONE_SOURCES} ${rtphone_engine}/media/MT_EvsCodec.cpp ${rtphone_engine}/media/MT_EvsCodec.h)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_OPUS_CODEC)
|
if (USE_OPUS_CODEC)
|
||||||
add_definitions(-DUSE_OPUS_CODEC)
|
set (DEFINES ${DEFINES} -DUSE_OPUS_CODEC)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library (rtphone STATIC ${RTPHONE_SOURCES})
|
add_library (rtphone STATIC ${RTPHONE_SOURCES})
|
||||||
|
|
||||||
add_subdirectory(${rtphone_libs}/resiprocate)
|
add_subdirectory(${L}/resiprocate)
|
||||||
add_subdirectory(${rtphone_libs}/ice)
|
add_subdirectory(${L}/jrtplib/src)
|
||||||
add_subdirectory(${rtphone_libs}/jrtplib/src)
|
add_subdirectory(${L}/libg729)
|
||||||
add_subdirectory(${rtphone_libs}/libg729)
|
|
||||||
|
|
||||||
if (USE_EVS_CODEC)
|
if (USE_EVS_CODEC)
|
||||||
add_subdirectory(${rtphone_libs}/libevs)
|
add_subdirectory(${L}/libevs)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(${rtphone_libs}/libgsm)
|
add_subdirectory(${L}/libgsm)
|
||||||
add_subdirectory(${rtphone_libs}/gsmhr)
|
add_subdirectory(${L}/gsmhr)
|
||||||
add_subdirectory(${rtphone_libs}/g722)
|
add_subdirectory(${L}/g722)
|
||||||
add_subdirectory(${rtphone_libs}/speexdsp)
|
add_subdirectory(${L}/speexdsp)
|
||||||
add_subdirectory(${rtphone_libs}/srtp)
|
add_subdirectory(${L}/libsrtp)
|
||||||
add_subdirectory(${rtphone_libs}/webrtc)
|
add_subdirectory(${L}/webrtc)
|
||||||
add_subdirectory(${rtphone_engine}/helper)
|
add_subdirectory(${L}/opus)
|
||||||
add_subdirectory(${rtphone_engine}/audio)
|
|
||||||
add_subdirectory(${rtphone_engine}/media)
|
|
||||||
|
|
||||||
set (LIBS ice_stack jrtplib g729_codec gsm_codec
|
set (LIBS_STATIC ${LIBS_STATIC} jrtplib g729_codec gsm_codec opus
|
||||||
gsmhr_codec g722_codec srtp resiprocate helper_lib audio_lib webrtc speexdsp)
|
gsmhr_codec g722_codec srtp3 resiprocate webrtc speexdsp)
|
||||||
|
|
||||||
if (CMAKE_SYSTEM MATCHES "Win*")
|
if (USE_AMR_CODEC)
|
||||||
set (LIBS ${LIBS} )
|
include (${LIB_PLATFORM}/platform_libs.cmake)
|
||||||
else ()
|
message("Media: AMR NB and WB codecs will be included.")
|
||||||
set (LIBS ${LIBS} dl)
|
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
|
||||||
|
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (CMAKE_SYSTEM MATCHES "Android")
|
if (USE_EVS_CODEC)
|
||||||
set (LIBS ${LIBS} oboe)
|
message("Media: EVS codec will be included.")
|
||||||
|
set (DEFINES ${DEFINES} -DUSE_EVS_CODEC)
|
||||||
|
set (LIBS_STATIC ${LIBS_STATIC} evs_codec)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(rtphone PUBLIC ${DEFINES})
|
||||||
|
|
||||||
target_link_libraries(rtphone
|
if (TARGET_LINUX)
|
||||||
${LIBS}
|
target_link_options(rtphone PUBLIC -Wl,-Bstatic)
|
||||||
${OPENSSL_SSL}
|
endif()
|
||||||
${OPENSSL_CRYPTO}
|
target_link_libraries(rtphone PUBLIC ${LIBS_STATIC} ${OPENSSL_SSL} ${OPENSSL_CRYPTO})
|
||||||
)
|
|
||||||
|
|
||||||
|
if (TARGET_LINUX)
|
||||||
|
target_link_options(rtphone PUBLIC -Wl,-Bdynamic)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(rtphone
|
target_include_directories(rtphone
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
|
@ -214,15 +363,18 @@ target_include_directories(rtphone
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/engine>
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/engine>
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs
|
${CMAKE_CURRENT_SOURCE_DIR}/libs
|
||||||
${LIB_PLATFORM}/opus/include
|
${LIB_PLATFORM}/opus/include
|
||||||
|
${E}/helper
|
||||||
|
${E}/audio
|
||||||
|
${E}/media
|
||||||
|
${L}
|
||||||
|
${L}/ice
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/
|
${L}/libevs/lib_com
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/libevs/lib_com
|
${L}/libevs/lib_enc
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/libevs/lib_enc
|
${L}/libevs/lib_dec
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/libevs/lib_dec
|
${L}/speex/include
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/speex/include
|
${L}/libs/json
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/opus/include/
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs/json
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# For MSVC static builds
|
# For MSVC static builds
|
||||||
configure_msvc_runtime()
|
# set_property(TARGET rtphone PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "Agent_AudioManager.h"
|
#include "Agent_AudioManager.h"
|
||||||
#include "../engine/audio/Audio_WavFile.h"
|
#include "../engine/audio/Audio_WavFile.h"
|
||||||
#include "../engine/audio/Audio_Null.h"
|
#include "../engine/audio/Audio_Null.h"
|
||||||
|
#include "HL_String.h"
|
||||||
|
|
||||||
#if defined(TARGET_ANDROID)
|
#if defined(TARGET_ANDROID)
|
||||||
# include "../engine/audio/Audio_Android.h"
|
# include "../engine/audio/Audio_Android.h"
|
||||||
|
|
@ -171,7 +172,7 @@ void AudioManager::startPlayFile(int usageId, const std::string& path, AudioTarg
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
Audio::PWavFileReader r = std::make_shared<Audio::WavFileReader>();
|
Audio::PWavFileReader r = std::make_shared<Audio::WavFileReader>();
|
||||||
#ifdef TARGET_WIN
|
#ifdef TARGET_WIN
|
||||||
r->open(StringHelper::makeTstring(path));
|
r->open(strx::makeTstring(path));
|
||||||
#else
|
#else
|
||||||
r->open(path);
|
r->open(path);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -481,41 +481,6 @@ void AgentImpl::processWaitForEvent(JsonCpp::Value &request, JsonCpp::Value &ans
|
||||||
answer["status"] = Status_Ok;
|
answer["status"] = Status_Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_PVQA_LIBRARY)
|
|
||||||
/*static JsonCpp::Value CsvReportToJson(const std::string& report)
|
|
||||||
{
|
|
||||||
JsonCpp::Value detectorValues;
|
|
||||||
std::istringstream iss(report);
|
|
||||||
CsvReader reader(iss);
|
|
||||||
std::vector<std::string> cells;
|
|
||||||
if (reader.readLine(cells))
|
|
||||||
{
|
|
||||||
JsonCpp::Value detectorNames;
|
|
||||||
for (size_t nameIndex = 0; nameIndex < cells.size(); nameIndex++)
|
|
||||||
detectorNames[static_cast<int>(nameIndex)] = strx::trim(cells[nameIndex]);
|
|
||||||
// Put first line name of columns
|
|
||||||
detectorValues[0] = detectorNames;
|
|
||||||
|
|
||||||
int rowIndex = 1;
|
|
||||||
while (reader.readLine(cells))
|
|
||||||
{
|
|
||||||
// Skip last column for now
|
|
||||||
JsonCpp::Value row;
|
|
||||||
for (size_t valueIndex = 0; valueIndex < cells.size(); valueIndex++)
|
|
||||||
{
|
|
||||||
bool isFloat = true;
|
|
||||||
float v = strx::toFloat(cells[valueIndex], 0.0, &isFloat);
|
|
||||||
if (isFloat)
|
|
||||||
row[static_cast<int>(valueIndex)] = static_cast<double>(v);
|
|
||||||
else
|
|
||||||
row[static_cast<int>(valueIndex)] = cells[valueIndex];
|
|
||||||
}
|
|
||||||
detectorValues[rowIndex++] = row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return detectorValues;
|
|
||||||
}*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer)
|
void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2025 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef __AUDIO_DSOUND_H
|
#ifndef __AUDIO_DSOUND_H
|
||||||
#define __AUDIO_DSOUND_H
|
#define __AUDIO_DSOUND_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,6 @@ TimeSource::TimeSource(int quantTime, int nrOfQuants)
|
||||||
mTailTime = 0;
|
mTailTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSource::~TimeSource()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeSource::start()
|
void TimeSource::start()
|
||||||
{
|
{
|
||||||
#ifdef TARGET_WIN
|
#ifdef TARGET_WIN
|
||||||
|
|
@ -107,7 +103,7 @@ unsigned TimeSource::time()
|
||||||
|
|
||||||
// --- StubTimer ---
|
// --- StubTimer ---
|
||||||
StubTimer::StubTimer(int bufferTime, int bufferCount)
|
StubTimer::StubTimer(int bufferTime, int bufferCount)
|
||||||
:mBufferTime(bufferTime), mBufferCount(bufferCount), mTimeSource(bufferTime, bufferCount), mActive(false)
|
:mBufferTime(bufferTime), mBufferCount(bufferCount), mTimeSource(bufferTime, bufferCount), mActive(false), mCurrentTime(0)
|
||||||
{
|
{
|
||||||
#ifdef TARGET_WIN
|
#ifdef TARGET_WIN
|
||||||
mStubSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
mStubSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ namespace Audio
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimeSource(int quantTime, int nrOfQuants);
|
TimeSource(int quantTime, int nrOfQuants);
|
||||||
~TimeSource();
|
~TimeSource() = default;
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#define __AUDIO_INTERFACE_H
|
#define __AUDIO_INTERFACE_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "../helper/HL_Types.h"
|
#include "../helper/HL_Types.h"
|
||||||
#include "../helper/HL_VariantMap.h"
|
#include "../helper/HL_VariantMap.h"
|
||||||
#include "../helper/HL_Pointer.h"
|
#include "../helper/HL_Pointer.h"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "../helper/HL_Exception.h"
|
#include "../helper/HL_Exception.h"
|
||||||
#include "../helper/HL_Log.h"
|
#include "../helper/HL_Log.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef _RX_MIXER_H
|
#ifndef _RX_MIXER_H
|
||||||
#define _RX_MIXER_H
|
#define _RX_MIXER_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
#include "../helper/HL_ByteBuffer.h"
|
||||||
#include "../helper/HL_Sync.h"
|
#include "../helper/HL_Sync.h"
|
||||||
#include "Audio_Resampler.h"
|
#include "Audio_Resampler.h"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "Audio_Null.h"
|
#include "Audio_Null.h"
|
||||||
#include "helper/HL_Log.h"
|
#include "helper/HL_Log.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <chrono>
|
||||||
#define LOG_SUBSYSTEM "NULL audio"
|
#define LOG_SUBSYSTEM "NULL audio"
|
||||||
|
|
||||||
using namespace Audio;
|
using namespace Audio;
|
||||||
|
|
@ -59,7 +60,7 @@ NullInputDevice::NullInputDevice()
|
||||||
|
|
||||||
NullInputDevice::~NullInputDevice()
|
NullInputDevice::~NullInputDevice()
|
||||||
{
|
{
|
||||||
close();
|
internalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NullInputDevice::open()
|
bool NullInputDevice::open()
|
||||||
|
|
@ -72,7 +73,7 @@ bool NullInputDevice::open()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NullInputDevice::close()
|
void NullInputDevice::internalClose()
|
||||||
{
|
{
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
if (mBuffer)
|
if (mBuffer)
|
||||||
|
|
@ -83,6 +84,10 @@ void NullInputDevice::close()
|
||||||
ICELogInfo(<<"Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes.");
|
ICELogInfo(<<"Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NullInputDevice::close()
|
||||||
|
{
|
||||||
|
internalClose();
|
||||||
|
}
|
||||||
Format NullInputDevice::getFormat()
|
Format NullInputDevice::getFormat()
|
||||||
{
|
{
|
||||||
assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE);
|
assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE);
|
||||||
|
|
@ -105,7 +110,7 @@ NullOutputDevice::NullOutputDevice()
|
||||||
|
|
||||||
NullOutputDevice::~NullOutputDevice()
|
NullOutputDevice::~NullOutputDevice()
|
||||||
{
|
{
|
||||||
close();
|
internalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -118,13 +123,18 @@ bool NullOutputDevice::open()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NullOutputDevice::close()
|
void NullOutputDevice::internalClose()
|
||||||
{
|
{
|
||||||
mTimer.reset();
|
mTimer.reset();
|
||||||
free(mBuffer); mBuffer = nullptr;
|
free(mBuffer); mBuffer = nullptr;
|
||||||
ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes.");
|
ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NullOutputDevice::close()
|
||||||
|
{
|
||||||
|
internalClose();
|
||||||
|
}
|
||||||
|
|
||||||
Format NullOutputDevice::getFormat()
|
Format NullOutputDevice::getFormat()
|
||||||
{
|
{
|
||||||
assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE);
|
assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE);
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ namespace Audio
|
||||||
void* mBuffer = nullptr;
|
void* mBuffer = nullptr;
|
||||||
std::shared_ptr<NullTimer> mTimer;
|
std::shared_ptr<NullTimer> mTimer;
|
||||||
int64_t mTimeCounter = 0, mDataCounter = 0;
|
int64_t mTimeCounter = 0, mDataCounter = 0;
|
||||||
|
void internalClose();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NullInputDevice();
|
NullInputDevice();
|
||||||
virtual ~NullInputDevice();
|
virtual ~NullInputDevice();
|
||||||
|
|
@ -55,6 +57,8 @@ namespace Audio
|
||||||
std::shared_ptr<NullTimer> mTimer;
|
std::shared_ptr<NullTimer> mTimer;
|
||||||
void* mBuffer = nullptr;
|
void* mBuffer = nullptr;
|
||||||
int64_t mDataCounter = 0, mTimeCounter = 0;
|
int64_t mDataCounter = 0, mTimeCounter = 0;
|
||||||
|
|
||||||
|
void internalClose();
|
||||||
public:
|
public:
|
||||||
NullOutputDevice();
|
NullOutputDevice();
|
||||||
virtual ~NullOutputDevice();
|
virtual ~NullOutputDevice();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "Audio_Quality.h"
|
#include "Audio_Quality.h"
|
||||||
#include "../helper/HL_Exception.h"
|
#include "../helper/HL_Exception.h"
|
||||||
#include "../helper/HL_Types.h"
|
#include "../helper/HL_Types.h"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
#ifndef __AUDIO_QUALITY_H
|
#ifndef __AUDIO_QUALITY_H
|
||||||
#define __AUDIO_QUALITY_H
|
#define __AUDIO_QUALITY_H
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "../helper/HL_Sync.h"
|
#include "../helper/HL_Sync.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "Audio_Resampler.h"
|
#include "Audio_Resampler.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Audio
|
||||||
|
|
||||||
|
|
||||||
SpeexResampler::SpeexResampler()
|
SpeexResampler::SpeexResampler()
|
||||||
:mContext(NULL), mErrorCode(0), mSourceRate(0), mDestRate(0), mLastSample(0)
|
:mContext(NULL), mErrorCode(0), mSourceRate(0), mDestRate(0), mLastSample(0), mChannels(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,12 +125,13 @@ int SpeexResampler::destRate()
|
||||||
|
|
||||||
size_t SpeexResampler::getDestLength(size_t sourceLen)
|
size_t SpeexResampler::getDestLength(size_t sourceLen)
|
||||||
{
|
{
|
||||||
return size_t(sourceLen * (float(mDestRate) / mSourceRate) + 0.5f) / 2 * 2;
|
return size_t(sourceLen * (float(mDestRate) / mSourceRate) + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SpeexResampler::getSourceLength(size_t destLen)
|
size_t SpeexResampler::getSourceLength(size_t destLen)
|
||||||
{
|
{
|
||||||
return size_t(destLen * (float(mSourceRate) / mDestRate) + 0.5f) / 2 * 2;
|
// Here we want to get 'destLen' number of samples
|
||||||
|
return size_t(destLen * (float(mSourceRate) / mDestRate) + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns instance + speex resampler size in bytes
|
// Returns instance + speex resampler size in bytes
|
||||||
|
|
@ -273,7 +274,7 @@ PResampler UniversalResampler::findResampler(int sourceRate, int destRate)
|
||||||
PResampler r;
|
PResampler r;
|
||||||
if (resamplerIter == mResamplerMap.end())
|
if (resamplerIter == mResamplerMap.end())
|
||||||
{
|
{
|
||||||
r = PResampler(new Resampler());
|
r = std::make_shared<Resampler>();
|
||||||
r->start(AUDIO_CHANNELS, sourceRate, destRate);
|
r->start(AUDIO_CHANNELS, sourceRate, destRate);
|
||||||
mResamplerMap[RatePair(sourceRate, destRate)] = r;
|
mResamplerMap[RatePair(sourceRate, destRate)] = r;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#include "helper/HL_Exception.h"
|
#include "helper/HL_Exception.h"
|
||||||
#include "helper/HL_String.h"
|
#include "helper/HL_String.h"
|
||||||
#include "helper/HL_Log.h"
|
#include "helper/HL_Log.h"
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
|
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
@ -40,7 +40,7 @@ using namespace Audio;
|
||||||
|
|
||||||
// ---------------------- WavFileReader -------------------------
|
// ---------------------- WavFileReader -------------------------
|
||||||
WavFileReader::WavFileReader()
|
WavFileReader::WavFileReader()
|
||||||
:mHandle(nullptr), mSamplerate(0), mLastError(0)
|
:mSamplerate(0), mLastError(0), mChannels(0), mBits(0), mDataLength(0)
|
||||||
{
|
{
|
||||||
mDataOffset = 0;
|
mDataOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -53,38 +53,51 @@ WavFileReader::~WavFileReader()
|
||||||
|
|
||||||
std::string WavFileReader::readChunk()
|
std::string WavFileReader::readChunk()
|
||||||
{
|
{
|
||||||
char name[5];
|
char name[5] = {0};
|
||||||
if (fread(name, 1, 4, mHandle) != 4)
|
readBuffer(name, 4);
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
name[4] = 0;
|
|
||||||
std::string result = name;
|
std::string result = name;
|
||||||
unsigned size;
|
uint32_t size = 0;
|
||||||
if (fread(&size, 4, 1, mHandle) != 1)
|
readBuffer(&size, 4);
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
if (result == "fact")
|
if (result == "fact")
|
||||||
fread(&mDataLength, 4, 1, mHandle);
|
{
|
||||||
|
uint32_t dataLength = 0;
|
||||||
|
readBuffer(&dataLength, sizeof dataLength);
|
||||||
|
mDataLength = dataLength;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
if (result != "data")
|
if (result != "data")
|
||||||
fseek(mHandle, size, SEEK_CUR);
|
mInput->seekg(size, std::ios_base::beg);
|
||||||
else
|
else
|
||||||
mDataLength = size;
|
mDataLength = size;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WavFileReader::open(const std::tstring& filename)
|
void WavFileReader::readBuffer(void* buffer, size_t sz)
|
||||||
|
{
|
||||||
|
auto p = mInput->tellg();
|
||||||
|
mInput->read(reinterpret_cast<char*>(buffer), sz);
|
||||||
|
if (mInput->tellg() - p != sz)
|
||||||
|
throw Exception(ERR_WAVFILE_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t WavFileReader::tryReadBuffer(void* buffer, size_t sz)
|
||||||
|
{
|
||||||
|
auto p = mInput->tellg();
|
||||||
|
mInput->read(reinterpret_cast<char*>(buffer), sz);
|
||||||
|
return mInput->tellg() - p;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WavFileReader::open(const std::filesystem::path& p)
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#ifdef WIN32
|
mPath = p;
|
||||||
mHandle = _wfopen(filename.c_str(), L"rb");
|
mInput = std::make_unique<std::ifstream>(p, std::ios::binary | std::ios::in);
|
||||||
#else
|
if (!mInput->is_open())
|
||||||
mHandle = fopen(strx::makeUtf8(filename).c_str(), "rb");
|
|
||||||
#endif
|
|
||||||
if (NULL == mHandle)
|
|
||||||
{
|
{
|
||||||
#if defined(TARGET_ANDROID) || defined(TARGET_LINUX) || defined(TARGET_OSX)
|
#if defined(TARGET_ANDROID) || defined(TARGET_LINUX) || defined(TARGET_OSX)
|
||||||
mLastError = errno;
|
mLastError = errno;
|
||||||
|
|
@ -98,75 +111,62 @@ bool WavFileReader::open(const std::tstring& filename)
|
||||||
|
|
||||||
// Read the .WAV header
|
// Read the .WAV header
|
||||||
char riff[4];
|
char riff[4];
|
||||||
if (fread(riff, 4, 1, mHandle) < 1)
|
readBuffer(riff, sizeof riff);
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
|
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
|
||||||
THROW_READERROR;
|
THROW_READERROR;
|
||||||
|
|
||||||
// Read the file size
|
// Read the file size
|
||||||
uint32_t filesize = 0;
|
uint32_t filesize = 0;
|
||||||
if (fread(&filesize, 4, 1, mHandle) < 1)
|
readBuffer(&filesize, sizeof(filesize));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
char wavefmt[9];
|
char wavefmt[9] = {0};
|
||||||
if (fread(wavefmt, 8, 1, mHandle) < 1)
|
readBuffer(wavefmt, 8);
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
wavefmt[8] = 0;
|
|
||||||
if (strcmp(wavefmt, "WAVEfmt ") != 0)
|
if (strcmp(wavefmt, "WAVEfmt ") != 0)
|
||||||
THROW_READERROR;
|
THROW_READERROR;
|
||||||
|
|
||||||
uint32_t fmtSize = 0;
|
uint32_t fmtSize = 0;
|
||||||
if (fread(&fmtSize, 4, 1, mHandle) < 1)
|
readBuffer(&fmtSize, sizeof(fmtSize));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
uint32_t fmtStart = ftell(mHandle);
|
auto fmtStart = mInput->tellg();
|
||||||
|
|
||||||
uint16_t formattag = 0;
|
uint16_t formattag = 0;
|
||||||
if (fread(&formattag, 2, 1, mHandle) < 1)
|
readBuffer(&formattag, sizeof(formattag));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
if (formattag != 1/*WAVE_FORMAT_PCM*/)
|
if (formattag != 1/*WAVE_FORMAT_PCM*/)
|
||||||
THROW_READERROR;
|
THROW_READERROR;
|
||||||
|
|
||||||
if (fread(&mChannels, 2, 1, mHandle) < 1)
|
mChannels = 0;
|
||||||
THROW_READERROR;
|
readBuffer(&mChannels, sizeof(mChannels));
|
||||||
|
|
||||||
mSamplerate = 0;
|
mSamplerate = 0;
|
||||||
if (fread(&mSamplerate, 4, 1, mHandle) < 1)
|
readBuffer(&mSamplerate, sizeof(mSamplerate));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
unsigned int avgbytespersec = 0;
|
uint32_t avgbytespersec = 0;
|
||||||
if (fread(&avgbytespersec, 4, 1, mHandle) < 1)
|
readBuffer(&avgbytespersec, sizeof(avgbytespersec));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
unsigned short blockalign = 0;
|
uint16_t blockalign = 0;
|
||||||
if (fread(&blockalign, 2, 1, mHandle) < 1)
|
readBuffer(&blockalign, sizeof(blockalign));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
mBits = 0;
|
mBits = 0;
|
||||||
if (fread(&mBits, 2, 1, mHandle) < 1)
|
readBuffer(&mBits, sizeof(mBits));
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
if (mBits !=8 && mBits != 16)
|
if (mBits !=8 && mBits != 16)
|
||||||
THROW_READERROR;
|
THROW_READERROR;
|
||||||
|
|
||||||
// Read the "chunk"
|
// Look for the chunk 'data'
|
||||||
fseek(mHandle, fmtStart + fmtSize, SEEK_SET);
|
mInput->seekg(fmtStart + std::streampos(fmtSize));
|
||||||
//unsigned pos = ftell(mHandle);
|
|
||||||
mDataLength = 0;
|
mDataLength = 0;
|
||||||
while (readChunk() != "data")
|
while (readChunk() != "data")
|
||||||
;
|
;
|
||||||
|
|
||||||
mFileName = filename;
|
mDataOffset = mInput->tellg();
|
||||||
mDataOffset = ftell(mHandle);
|
|
||||||
|
|
||||||
mResampler.start(AUDIO_CHANNELS, mSamplerate, AUDIO_SAMPLERATE);
|
mResampler.start(AUDIO_CHANNELS, mSamplerate, AUDIO_SAMPLERATE);
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
fclose(mHandle); mHandle = nullptr;
|
mInput.reset();
|
||||||
mLastError = static_cast<unsigned>(-1);
|
mLastError = static_cast<unsigned>(-1);
|
||||||
}
|
}
|
||||||
return isOpened();
|
return isOpened();
|
||||||
|
|
@ -175,10 +175,7 @@ bool WavFileReader::open(const std::tstring& filename)
|
||||||
void WavFileReader::close()
|
void WavFileReader::close()
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
mInput.reset();
|
||||||
if (nullptr != mHandle)
|
|
||||||
fclose(mHandle);
|
|
||||||
mHandle = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int WavFileReader::samplerate() const
|
int WavFileReader::samplerate() const
|
||||||
|
|
@ -205,30 +202,38 @@ size_t WavFileReader::read(short* buffer, size_t samples)
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
|
||||||
if (!mHandle)
|
if (!mInput)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Get number of samples that must be read from source file
|
// Get number of samples that must be read from source file
|
||||||
size_t requiredBytes = mResampler.getSourceLength(samples) * mChannels * mBits / 8;
|
size_t requiredBytes = mResampler.getSourceLength(samples) * mChannels * mBits / 8;
|
||||||
void* temp = alloca(requiredBytes);
|
bool useHeap = requiredBytes > sizeof mTempBuffer;
|
||||||
|
void* temp;
|
||||||
|
if (useHeap)
|
||||||
|
temp = malloc(requiredBytes);
|
||||||
|
else
|
||||||
|
temp = mTempBuffer;
|
||||||
|
|
||||||
memset(temp, 0, requiredBytes);
|
memset(temp, 0, requiredBytes);
|
||||||
|
|
||||||
// Find required size of input buffer
|
// Find required size of input buffer
|
||||||
if (mDataLength)
|
if (mDataLength)
|
||||||
{
|
{
|
||||||
unsigned filePosition = ftell(mHandle);
|
auto filePosition = mInput->tellg();
|
||||||
|
|
||||||
// Check how much data we can read
|
// Check how much data we can read
|
||||||
size_t fileAvailable = mDataLength + mDataOffset - filePosition;
|
size_t fileAvailable = mDataLength + mDataOffset - filePosition;
|
||||||
requiredBytes = fileAvailable < requiredBytes ? fileAvailable : requiredBytes;
|
requiredBytes = fileAvailable < requiredBytes ? fileAvailable : requiredBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t readBytes = fread(temp, 1, requiredBytes, mHandle);
|
size_t readBytes = tryReadBuffer(temp, requiredBytes);
|
||||||
|
|
||||||
size_t processedBytes = 0;
|
size_t processedBytes = 0;
|
||||||
size_t result = mResampler.processBuffer(temp, readBytes, processedBytes,
|
size_t result = mResampler.processBuffer(temp, readBytes, processedBytes,
|
||||||
buffer, samples * 2 * AUDIO_CHANNELS);
|
buffer, samples * 2 * AUDIO_CHANNELS);
|
||||||
|
|
||||||
|
if (useHeap)
|
||||||
|
free(temp);
|
||||||
return result / 2 / AUDIO_CHANNELS;
|
return result / 2 / AUDIO_CHANNELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,54 +242,50 @@ size_t WavFileReader::readRaw(short* buffer, size_t samples)
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
|
||||||
if (!mHandle)
|
if (!mInput)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Get number of samples that must be read from source file
|
// Get number of samples that must be read from source file
|
||||||
int requiredBytes = samples * channels() * sizeof(short);
|
size_t requiredBytes = samples * channels() * sizeof(short);
|
||||||
|
|
||||||
// Find required size of input buffer
|
// Find required size of input buffer
|
||||||
if (mDataLength)
|
if (mDataLength)
|
||||||
{
|
{
|
||||||
unsigned filePosition = ftell(mHandle);
|
auto filePosition = mInput->tellg();
|
||||||
|
|
||||||
// Check how much data we can read
|
// Check how much data we can read
|
||||||
unsigned fileAvailable = mDataLength + mDataOffset - filePosition;
|
size_t fileAvailable = mDataLength + mDataOffset - filePosition;
|
||||||
requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes;
|
requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t readBytes = fread(buffer, 1, requiredBytes, mHandle);
|
size_t readBytes = tryReadBuffer(buffer, requiredBytes);
|
||||||
|
|
||||||
|
|
||||||
return readBytes / channels() / sizeof(short);
|
return readBytes / channels() / sizeof(short);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WavFileReader::isOpened()
|
bool WavFileReader::isOpened()
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
if (!mInput)
|
||||||
return (mHandle != 0);
|
return false;
|
||||||
|
return mInput->is_open();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WavFileReader::rewind()
|
void WavFileReader::rewind()
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
if (mInput)
|
||||||
if (mHandle)
|
mInput->seekg(mDataOffset);
|
||||||
fseek(mHandle, mDataOffset, SEEK_SET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tstring WavFileReader::filename() const
|
std::filesystem::path WavFileReader::path() const
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
return mPath;
|
||||||
return mFileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WavFileReader::size() const
|
size_t WavFileReader::size() const
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
|
||||||
return mDataLength;
|
return mDataLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,9 +299,8 @@ unsigned WavFileReader::lastError() const
|
||||||
#define BITS_PER_CHANNEL 16
|
#define BITS_PER_CHANNEL 16
|
||||||
|
|
||||||
WavFileWriter::WavFileWriter()
|
WavFileWriter::WavFileWriter()
|
||||||
:mHandle(nullptr), mLengthOffset(0), mRate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0)
|
:mLengthOffset(0), mSamplerate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
WavFileWriter::~WavFileWriter()
|
WavFileWriter::~WavFileWriter()
|
||||||
{
|
{
|
||||||
|
|
@ -313,68 +313,74 @@ void WavFileWriter::checkWriteResult(int result)
|
||||||
throw Exception(ERR_WAVFILE_FAILED, errno);
|
throw Exception(ERR_WAVFILE_FAILED, errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WavFileWriter::open(const std::tstring& filename, int rate, int channels)
|
void WavFileWriter::writeBuffer(const void* buffer, size_t sz)
|
||||||
|
{
|
||||||
|
if (!mOutput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto p = mOutput->tellp();
|
||||||
|
mOutput->write(reinterpret_cast<const char*>(buffer), sz);
|
||||||
|
if (mOutput->tellp() - p != sz)
|
||||||
|
throw Exception(ERR_WAVFILE_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WavFileWriter::open(const std::filesystem::path& p, int samplerate, int channels)
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
|
||||||
close();
|
close();
|
||||||
|
mSamplerate = samplerate;
|
||||||
mRate = rate;
|
|
||||||
mChannels = channels;
|
mChannels = channels;
|
||||||
|
|
||||||
#ifdef WIN32
|
mOutput = std::make_unique<std::ofstream>(p, std::ios::binary | std::ios::trunc);
|
||||||
mHandle = _wfopen(filename.c_str(), L"wb");
|
if (!mOutput)
|
||||||
#else
|
|
||||||
mHandle = fopen(strx::makeUtf8(filename).c_str(), "wb");
|
|
||||||
#endif
|
|
||||||
if (nullptr == mHandle)
|
|
||||||
{
|
{
|
||||||
ICELogError(<< "Failed to create .wav file: filename = " << strx::makeUtf8(filename) << " , error = " << errno);
|
int errorcode = errno;
|
||||||
|
ICELogError(<< "Failed to create .wav file: filename = " << p << " , error = " << errorcode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the .WAV header
|
// Write the .WAV header
|
||||||
const char* riff = "RIFF";
|
const char* riff = "RIFF";
|
||||||
checkWriteResult( fwrite(riff, 4, 1, mHandle) );
|
writeBuffer(riff, 4);
|
||||||
|
|
||||||
// Write the file size
|
// Write the file size
|
||||||
unsigned int filesize = 0;
|
uint32_t filesize = 0;
|
||||||
checkWriteResult( fwrite(&filesize, 4, 1, mHandle) );
|
writeBuffer(&filesize, sizeof filesize);
|
||||||
|
|
||||||
const char* wavefmt = "WAVEfmt ";
|
const char* wavefmt = "WAVEfmt ";
|
||||||
checkWriteResult( fwrite(wavefmt, 8, 1, mHandle) );
|
writeBuffer(wavefmt, 8);
|
||||||
|
|
||||||
// Set the format description
|
// Set the format description
|
||||||
DWORD dwFmtSize = 16; /*= 16L*/;
|
uint32_t dwFmtSize = 16; /*= 16L*/;
|
||||||
checkWriteResult( fwrite(&dwFmtSize, sizeof(dwFmtSize), 1, mHandle) );
|
writeBuffer(&dwFmtSize, sizeof(dwFmtSize));
|
||||||
|
|
||||||
WaveFormatEx format;
|
WaveFormatEx format;
|
||||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||||
checkWriteResult( fwrite(&format.wFormatTag, sizeof(format.wFormatTag), 1, mHandle) );
|
writeBuffer(&format.wFormatTag, sizeof(format.wFormatTag));
|
||||||
|
|
||||||
format.nChannels = mChannels;
|
format.nChannels = mChannels;
|
||||||
checkWriteResult( fwrite(&format.nChannels, sizeof(format.nChannels), 1, mHandle) );
|
writeBuffer(&format.nChannels, sizeof(format.nChannels));
|
||||||
|
|
||||||
format.nSamplesPerSec = mRate;
|
format.nSamplesPerSec = mSamplerate;
|
||||||
checkWriteResult( fwrite(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec), 1, mHandle) );
|
writeBuffer(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec));
|
||||||
|
|
||||||
format.nAvgBytesPerSec = mRate * 2 * mChannels;
|
format.nAvgBytesPerSec = mSamplerate * 2 * mChannels;
|
||||||
checkWriteResult( fwrite(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec), 1, mHandle) );
|
writeBuffer(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec));
|
||||||
|
|
||||||
format.nBlockAlign = 2 * mChannels;
|
format.nBlockAlign = 2 * mChannels;
|
||||||
checkWriteResult( fwrite(&format.nBlockAlign, sizeof(format.nBlockAlign), 1, mHandle) );
|
writeBuffer(&format.nBlockAlign, sizeof(format.nBlockAlign));
|
||||||
|
|
||||||
format.wBitsPerSample = BITS_PER_CHANNEL;
|
format.wBitsPerSample = BITS_PER_CHANNEL;
|
||||||
checkWriteResult( fwrite(&format.wBitsPerSample, sizeof(format.wBitsPerSample), 1, mHandle) );
|
writeBuffer(&format.wBitsPerSample, sizeof(format.wBitsPerSample));
|
||||||
|
|
||||||
const char* data = "data";
|
const char* data = "data";
|
||||||
checkWriteResult( fwrite(data, 4, 1, mHandle));
|
writeBuffer(data, 4);
|
||||||
|
|
||||||
mFileName = filename;
|
mPath = p;
|
||||||
mWritten = 0;
|
mWritten = 0;
|
||||||
|
|
||||||
mLengthOffset = ftell(mHandle);
|
mLengthOffset = mOutput->tellp();
|
||||||
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
|
writeBuffer(&mWritten, sizeof mWritten);
|
||||||
|
|
||||||
return isOpened();
|
return isOpened();
|
||||||
}
|
}
|
||||||
|
|
@ -382,51 +388,44 @@ bool WavFileWriter::open(const std::tstring& filename, int rate, int channels)
|
||||||
void WavFileWriter::close()
|
void WavFileWriter::close()
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
mOutput.reset();
|
||||||
if (mHandle)
|
|
||||||
{
|
|
||||||
fclose(mHandle);
|
|
||||||
mHandle = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t WavFileWriter::write(const void* buffer, size_t bytes)
|
size_t WavFileWriter::write(const void* buffer, size_t bytes)
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
|
||||||
if (!mHandle)
|
if (!mOutput)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Seek the end of file
|
// Seek the end of file - here new data will be written
|
||||||
fseek(mHandle, 0, SEEK_END);
|
mOutput->seekp(0, std::ios_base::end);
|
||||||
mWritten += bytes;
|
mWritten += bytes;
|
||||||
|
|
||||||
// Write the data
|
// Write the data
|
||||||
fwrite(buffer, bytes, 1, mHandle);
|
writeBuffer(buffer, bytes);
|
||||||
|
|
||||||
// Write file length
|
// Write file length
|
||||||
fseek(mHandle, 4, SEEK_SET);
|
mOutput->seekp(4, std::ios_base::beg);
|
||||||
int32_t fl = mWritten + 36;
|
uint32_t fl = mWritten + 36;
|
||||||
fwrite(&fl, sizeof(fl), 1, mHandle);
|
writeBuffer(&fl, sizeof(fl));
|
||||||
|
|
||||||
// Write data length
|
// Write data length
|
||||||
fseek(mHandle, static_cast<long>(mLengthOffset), SEEK_SET);
|
mOutput->seekp(mLengthOffset, std::ios_base::beg);
|
||||||
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
|
writeBuffer(&mWritten, sizeof(mWritten));
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WavFileWriter::isOpened()
|
bool WavFileWriter::isOpened() const
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
return mOutput.get();
|
||||||
return (mHandle != nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tstring WavFileWriter::filename()
|
std::filesystem::path WavFileWriter::path() const
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
return mPath;
|
||||||
return mFileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
|
||||||
namespace Audio
|
namespace Audio
|
||||||
|
|
@ -20,24 +22,27 @@ namespace Audio
|
||||||
class WavFileReader
|
class WavFileReader
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
FILE* mHandle;
|
uint16_t mChannels = 0;
|
||||||
uint16_t mChannels;
|
uint16_t mBits = 0;
|
||||||
uint16_t mBits;
|
int mSamplerate = 0;
|
||||||
int mSamplerate;
|
std::filesystem::path mPath;
|
||||||
std::tstring mFileName;
|
mutable std::recursive_mutex mFileMtx;
|
||||||
mutable std::recursive_mutex
|
size_t mDataOffset = 0;
|
||||||
mFileMtx;
|
size_t mDataLength = 0;
|
||||||
size_t mDataOffset;
|
|
||||||
size_t mDataLength;
|
|
||||||
Resampler mResampler;
|
Resampler mResampler;
|
||||||
unsigned mLastError;
|
unsigned mLastError = 0;
|
||||||
|
std::unique_ptr<std::ifstream> mInput;
|
||||||
|
uint8_t mTempBuffer[16384];
|
||||||
|
|
||||||
std::string readChunk();
|
std::string readChunk();
|
||||||
|
void readBuffer(void* buffer, size_t sz); // This raises an exception if sz bytes are not read
|
||||||
|
size_t tryReadBuffer(void* buffer, size_t sz); // This doesn't raise an exception
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WavFileReader();
|
WavFileReader();
|
||||||
~WavFileReader();
|
~WavFileReader();
|
||||||
|
|
||||||
bool open(const std::tstring& filename);
|
bool open(const std::filesystem::path& p);
|
||||||
void close();
|
void close();
|
||||||
bool isOpened();
|
bool isOpened();
|
||||||
void rewind();
|
void rewind();
|
||||||
|
|
@ -52,7 +57,7 @@ namespace Audio
|
||||||
size_t read(short* buffer, size_t samples);
|
size_t read(short* buffer, size_t samples);
|
||||||
size_t readRaw(short* buffer, size_t samples);
|
size_t readRaw(short* buffer, size_t samples);
|
||||||
|
|
||||||
std::tstring filename() const;
|
std::filesystem::path path() const;
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
|
|
||||||
unsigned lastError() const;
|
unsigned lastError() const;
|
||||||
|
|
@ -63,25 +68,25 @@ namespace Audio
|
||||||
class WavFileWriter
|
class WavFileWriter
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
FILE* mHandle; /// Handle of audio file.
|
std::unique_ptr<std::ofstream> mOutput; /// Handle of audio file.
|
||||||
std::tstring mFileName; /// Path to requested audio file.
|
std::filesystem::path mPath; /// Path to requested audio file.
|
||||||
std::recursive_mutex mFileMtx; /// Mutex to protect this instance.
|
mutable std::recursive_mutex mFileMtx; /// Mutex to protect this instance.
|
||||||
size_t mWritten; /// Amount of written data (in bytes)
|
size_t mWritten = 0; /// Amount of written data (in bytes)
|
||||||
size_t mLengthOffset; /// Position of length field.
|
size_t mLengthOffset = 0; /// Position of length field.
|
||||||
int mRate,
|
int mSamplerate = 0,
|
||||||
mChannels;
|
mChannels = 0;
|
||||||
|
|
||||||
void checkWriteResult(int result);
|
void checkWriteResult(int result);
|
||||||
|
void writeBuffer(const void* buffer, size_t sz);
|
||||||
public:
|
public:
|
||||||
WavFileWriter();
|
WavFileWriter();
|
||||||
~WavFileWriter();
|
~WavFileWriter();
|
||||||
|
|
||||||
bool open(const std::tstring& filename, int rate, int channels);
|
bool open(const std::filesystem::path& p, int samplerate, int channels);
|
||||||
void close();
|
void close();
|
||||||
bool isOpened();
|
bool isOpened() const;
|
||||||
size_t write(const void* buffer, size_t bytes);
|
size_t write(const void* buffer, size_t bytes);
|
||||||
std::tstring filename();
|
std::filesystem::path path() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<WavFileWriter> PWavFileWriter;
|
typedef std::shared_ptr<WavFileWriter> PWavFileWriter;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
#ifdef TARGET_WIN
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
project (audio_lib)
|
project (audio_lib)
|
||||||
|
|
||||||
# Rely on C++ 11
|
# Rely on C++ 11
|
||||||
set (CMAKE_CXX_STANDARD 11)
|
set (CMAKE_CXX_STANDARD 20)
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
/* Copyright(C) 2007-2020 VoIP objects (voipobjects.com)
|
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef __TOOLKIT_CONFIG_H
|
|
||||||
#define __TOOLKIT_CONFIG_H
|
|
||||||
|
|
||||||
#define USE_SPEEX_AEC
|
|
||||||
|
|
||||||
// TODO: test implementation with webrtc aec; be careful - it needs fixes!
|
|
||||||
//#define USE_WEBRTC_AEC
|
|
||||||
#define USER
|
|
||||||
|
|
||||||
|
|
||||||
#define AUDIO_SAMPLE_WIDTH 16
|
|
||||||
#define AUDIO_CHANNELS 1
|
|
||||||
|
|
||||||
// Samplerate must be 8 / 16 / 24 / 32 / 48 KHz
|
|
||||||
#define AUDIO_SAMPLERATE 8000
|
|
||||||
#define AUDIO_MIC_BUFFER_COUNT 16
|
|
||||||
#define AUDIO_MIC_BUFFER_LENGTH 10
|
|
||||||
#define AUDIO_MIC_BUFFER_SIZE (AUDIO_MIC_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
|
|
||||||
#define AUDIO_SPK_BUFFER_COUNT 16
|
|
||||||
#define AUDIO_SPK_BUFFER_LENGTH 10
|
|
||||||
#define AUDIO_SPK_BUFFER_SIZE (AUDIO_SPK_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
|
|
||||||
#define AUDIO_MIX_CHANNEL_COUNT 16
|
|
||||||
#define AUDIO_DEVICEPAIR_INPUTBUFFER 16384
|
|
||||||
|
|
||||||
// Avoid too high resampler quality - it can take many CPU and cause gaps in playing
|
|
||||||
#define AUDIO_RESAMPLER_QUALITY 1
|
|
||||||
#define AEC_FRAME_TIME 10
|
|
||||||
#define AEC_TAIL_TIME 160
|
|
||||||
|
|
||||||
|
|
||||||
// Defined these two lines to get dumping of audio input/output
|
|
||||||
//#define AUDIO_DUMPINPUT
|
|
||||||
//#define AUDIO_DUMPOUTPUT
|
|
||||||
|
|
||||||
|
|
||||||
#define UA_REGISTRATION_TIME 3600
|
|
||||||
#define UA_MEDIA_PORT_START 20000
|
|
||||||
#define UA_MEDIA_PORT_FINISH 30000
|
|
||||||
#define UA_MAX_UDP_PACKET_SIZE 576
|
|
||||||
#define UA_PUBLICATION_ID "314"
|
|
||||||
|
|
||||||
#define MT_SAMPLERATE AUDIO_SAMPLERATE
|
|
||||||
|
|
||||||
#define MT_MAXAUDIOFRAME 1440
|
|
||||||
#define MT_MAXRTPPACKET 1500
|
|
||||||
#define MT_DTMF_END_PACKETS 3
|
|
||||||
|
|
||||||
#define RTP_BUFFER_HIGH 24480
|
|
||||||
#define RTP_BUFFER_LOW 10
|
|
||||||
#define RTP_BUFFER_PREBUFFER 80
|
|
||||||
#define RTP_DECODED_CAPACITY 2048
|
|
||||||
|
|
||||||
#define DEFAULT_SUBSCRIPTION_TIME 1200
|
|
||||||
#define DEFAULT_SUBSCRIPTION_REFRESHTIME 500
|
|
||||||
|
|
||||||
#define PRESENCE_IN_REG_HEADER "PresenceInReg"
|
|
||||||
|
|
||||||
// Maximum UDP packet length
|
|
||||||
#define MAX_UDPPACKET_SIZE 65535
|
|
||||||
#define MAX_VALID_UDPPACKET_SIZE 2048
|
|
||||||
|
|
||||||
// AMR codec defines - it requires USE_AMR_CODEC defined
|
|
||||||
// #define USE_AMR_CODEC
|
|
||||||
#define MT_AMRNB_PAYLOADTYPE 112
|
|
||||||
#define MT_AMRNB_CODECNAME "amr"
|
|
||||||
|
|
||||||
#define MT_AMRNB_OCTET_PAYLOADTYPE 113
|
|
||||||
|
|
||||||
#define MT_AMRWB_PAYLOADTYPE 96
|
|
||||||
#define MT_AMRWB_CODECNAME "amr-wb"
|
|
||||||
|
|
||||||
#define MT_AMRWB_OCTET_PAYLOADTYPE 97
|
|
||||||
|
|
||||||
#define MT_GSMEFR_PAYLOADTYPE 126
|
|
||||||
#define MT_GSMEFR_CODECNAME "GERAN-EFR"
|
|
||||||
|
|
||||||
#define MT_EVS_PAYLOADTYPE 127
|
|
||||||
#define MT_EVS_CODECNAME "EVS"
|
|
||||||
|
|
||||||
// OPUS codec defines
|
|
||||||
// #define USE_OPUS_CODEC
|
|
||||||
#define MT_OPUS_CODEC_PT 106
|
|
||||||
|
|
||||||
// ILBC codec defines
|
|
||||||
#define MT_ILBC20_PAYLOADTYPE -1
|
|
||||||
#define MT_ILBC30_PAYLOADTYPE -1
|
|
||||||
|
|
||||||
// ISAC codec defines
|
|
||||||
#define MT_ISAC16K_PAYLOADTYPE -1
|
|
||||||
#define MT_ISAC32K_PAYLOADTYPE -1
|
|
||||||
|
|
||||||
// GSM HR payload type
|
|
||||||
#define MT_GSMHR_PAYLOADTYPE -1
|
|
||||||
|
|
||||||
// Mirror buffer capacity
|
|
||||||
#define MT_MIRROR_CAPACITY 32768
|
|
||||||
|
|
||||||
// Mirror buffer readiness threshold - 50 milliseconds
|
|
||||||
#define MT_MIRROR_PREBUFFER (MT_SAMPLERATE / 10)
|
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
|
||||||
# define TEXT(X) X
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// In milliseconds
|
|
||||||
#define MT_SEVANA_FRAME_TIME 680
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -99,7 +99,6 @@ void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, Sdp
|
||||||
else
|
else
|
||||||
sdp.addAttribute("crypto", resip::Data(createCryptoAttribute(mSrtpSuite)));
|
sdp.addAttribute("crypto", resip::Data(createCryptoAttribute(mSrtpSuite)));
|
||||||
}
|
}
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
|
|
||||||
// Use CodecListPriority mCodecPriority adapter to work with codec priorities
|
// Use CodecListPriority mCodecPriority adapter to work with codec priorities
|
||||||
if (mAvailableCodecs.empty())
|
if (mAvailableCodecs.empty())
|
||||||
|
|
@ -114,7 +113,7 @@ void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, Sdp
|
||||||
if (mRemoteTelephoneCodec)
|
if (mRemoteTelephoneCodec)
|
||||||
sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
|
sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// Publish stream state
|
// Publish stream state
|
||||||
const char* attr = nullptr;
|
const char* attr = nullptr;
|
||||||
|
|
@ -229,10 +228,8 @@ bool AudioProvider::processSdpOffer(const resip::SdpContents::Session::Medium& m
|
||||||
for (int localIndex=0; localIndex<mCodecPriority.count(mTerminal.codeclist()); localIndex++)
|
for (int localIndex=0; localIndex<mCodecPriority.count(mTerminal.codeclist()); localIndex++)
|
||||||
{
|
{
|
||||||
MT::Codec::Factory& factory = mCodecPriority.codecAt(mTerminal.codeclist(), localIndex);
|
MT::Codec::Factory& factory = mCodecPriority.codecAt(mTerminal.codeclist(), localIndex);
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
if ((pt = factory.processSdp(media.codecs(), sdpDirection)) != -1)
|
if ((pt = factory.processSdp(media.codecs(), sdpDirection)) != -1)
|
||||||
mAvailableCodecs.push_back(RemoteCodec(&factory, pt));
|
mAvailableCodecs.push_back(RemoteCodec(&factory, pt));
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mAvailableCodecs.size())
|
if (!mAvailableCodecs.size())
|
||||||
|
|
@ -303,26 +300,12 @@ std::string AudioProvider::createCryptoAttribute(SrtpSuite suite)
|
||||||
if (!mActiveStream)
|
if (!mActiveStream)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
// Use tag 1 - it is ok, as we use only single crypto attribute
|
|
||||||
int srtpTag = 1;
|
|
||||||
|
|
||||||
// Print key to base64 string
|
// Print key to base64 string
|
||||||
PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first;
|
PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first;
|
||||||
resip::Data d(keyBuffer->data(), keyBuffer->size());
|
resip::Data d(keyBuffer->data(), keyBuffer->size());
|
||||||
resip::Data keyText = d.base64encode();
|
resip::Data keyText = d.base64encode();
|
||||||
|
|
||||||
// Create "crypto" attribute value
|
return std::format("{} {} inline:{}", 1, toString(suite), keyText.c_str());
|
||||||
char buffer[512];
|
|
||||||
const char* suiteName = NULL;
|
|
||||||
switch (suite)
|
|
||||||
{
|
|
||||||
case SRTP_AES_128_AUTH_80: suiteName = SRTP_SUITE_NAME_1; break;
|
|
||||||
case SRTP_AES_256_AUTH_80: suiteName = SRTP_SUITE_NAME_2; break;
|
|
||||||
default: assert(0);
|
|
||||||
}
|
|
||||||
sprintf(buffer, "%d %s inline:%s", srtpTag, suiteName, keyText.c_str());
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key)
|
SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key)
|
||||||
|
|
@ -343,15 +326,7 @@ SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBu
|
||||||
resip::Data rawkey = keyText.base64decode();
|
resip::Data rawkey = keyText.base64decode();
|
||||||
key = ByteBuffer(rawkey.c_str(), rawkey.size());
|
key = ByteBuffer(rawkey.c_str(), rawkey.size());
|
||||||
|
|
||||||
// Open srtp
|
return toSrtpSuite(suite);
|
||||||
SrtpSuite result = SRTP_NONE;
|
|
||||||
if (strcmp(suite, SRTP_SUITE_NAME_1) == 0)
|
|
||||||
result = SRTP_AES_128_AUTH_80;
|
|
||||||
else
|
|
||||||
if (strcmp(suite, SRTP_SUITE_NAME_2) == 0)
|
|
||||||
result = SRTP_AES_256_AUTH_80;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs)
|
void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs)
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ public:
|
||||||
void setupMirror(bool enable);
|
void setupMirror(bool enable);
|
||||||
|
|
||||||
void configureMediaObserver(MT::Stream::MediaObserver* observer, void* userTag);
|
void configureMediaObserver(MT::Stream::MediaObserver* observer, void* userTag);
|
||||||
|
static SrtpSuite processCryptoAttribute(const resip::Data& value, ByteBuffer& key);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// SDP's stream name
|
// SDP's stream name
|
||||||
|
|
@ -109,7 +110,6 @@ protected:
|
||||||
void* mMediaObserverTag = nullptr;
|
void* mMediaObserverTag = nullptr;
|
||||||
|
|
||||||
std::string createCryptoAttribute(SrtpSuite suite);
|
std::string createCryptoAttribute(SrtpSuite suite);
|
||||||
SrtpSuite processCryptoAttribute(const resip::Data& value, ByteBuffer& key);
|
|
||||||
void findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs);
|
void findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs);
|
||||||
|
|
||||||
// Implements setState() logic. This allows to be called from constructor (it is not virtual function)
|
// Implements setState() logic. This allows to be called from constructor (it is not virtual function)
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ void UserAgent::start()
|
||||||
#if defined(TARGET_WIN)
|
#if defined(TARGET_WIN)
|
||||||
s = new resip::WinSecurity();
|
s = new resip::WinSecurity();
|
||||||
#elif defined(TARGET_OSX)
|
#elif defined(TARGET_OSX)
|
||||||
s = new resip::MacSecurity();
|
s = new resip::Security();
|
||||||
#elif defined(TARGET_LINUX)
|
#elif defined(TARGET_LINUX)
|
||||||
s = new resip::Security("/etc/ssl/certs");
|
s = new resip::Security("/etc/ssl/certs");
|
||||||
#elif defined(TARGET_ANDROID)
|
#elif defined(TARGET_ANDROID)
|
||||||
|
|
@ -486,6 +486,8 @@ void UserAgent::process()
|
||||||
void UserAgent::addRootCert(const ByteBuffer& data)
|
void UserAgent::addRootCert(const ByteBuffer& data)
|
||||||
{
|
{
|
||||||
LOCK;
|
LOCK;
|
||||||
|
if (!mStack)
|
||||||
|
return;
|
||||||
resip::Data b(data.data(), data.size());
|
resip::Data b(data.data(), data.size());
|
||||||
try {
|
try {
|
||||||
mStack->getSecurity()->addRootCertPEM(b);
|
mStack->getSecurity()->addRootCertPEM(b);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
#include "../ice/ICETime.h"
|
#include "../ice/ICETime.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "EP_Session.h"
|
#include "EP_Session.h"
|
||||||
#include "EP_Observer.h"
|
#include "EP_Observer.h"
|
||||||
#include "EP_DataProvider.h"
|
#include "EP_DataProvider.h"
|
||||||
|
|
|
||||||
|
|
@ -445,7 +445,6 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
|
||||||
MT::Statistics stat;
|
MT::Statistics stat;
|
||||||
|
|
||||||
// Iterate all session providers
|
// Iterate all session providers
|
||||||
stat.reset();
|
|
||||||
Stream* media = nullptr;
|
Stream* media = nullptr;
|
||||||
for (Stream& stream: mStreamList)
|
for (Stream& stream: mStreamList)
|
||||||
{
|
{
|
||||||
|
|
@ -469,7 +468,7 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
|
||||||
info[SessionInfo_SentRtp] = static_cast<int>(stat.mSentRtp);
|
info[SessionInfo_SentRtp] = static_cast<int>(stat.mSentRtp);
|
||||||
info[SessionInfo_SentRtcp] = static_cast<int>(stat.mSentRtcp);
|
info[SessionInfo_SentRtcp] = static_cast<int>(stat.mSentRtcp);
|
||||||
if (stat.mFirstRtpTime)
|
if (stat.mFirstRtpTime)
|
||||||
info[SessionInfo_Duration] = static_cast<int>(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - *(stat.mFirstRtpTime)).count());
|
info[SessionInfo_Duration] = static_cast<int>(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - *(stat.mFirstRtpTime)).count());
|
||||||
else
|
else
|
||||||
info[SessionInfo_Duration] = 0;
|
info[SessionInfo_Duration] = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "EP_Account.h"
|
#include "EP_Account.h"
|
||||||
#include "EP_DataProvider.h"
|
#include "EP_DataProvider.h"
|
||||||
#include "EP_AudioProvider.h"
|
#include "EP_AudioProvider.h"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com)
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef __TOOLKIT_CONFIG_H
|
||||||
|
#define __TOOLKIT_CONFIG_H
|
||||||
|
|
||||||
|
#define USE_SPEEX_AEC
|
||||||
|
|
||||||
|
// TODO: test implementation with webrtc aec; be careful - it needs fixes!
|
||||||
|
//#define USE_WEBRTC_AEC
|
||||||
|
#define USER
|
||||||
|
|
||||||
|
|
||||||
|
#define AUDIO_SAMPLE_WIDTH 16
|
||||||
|
#define AUDIO_CHANNELS 1
|
||||||
|
|
||||||
|
// Samplerate must be 8 / 16 / 24 / 32 / 48 KHz
|
||||||
|
#define AUDIO_SAMPLERATE 48000
|
||||||
|
#define AUDIO_MIC_BUFFER_COUNT 16
|
||||||
|
#define AUDIO_MIC_BUFFER_LENGTH 10
|
||||||
|
#define AUDIO_MIC_BUFFER_SIZE (AUDIO_MIC_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
|
||||||
|
#define AUDIO_SPK_BUFFER_COUNT 16
|
||||||
|
#define AUDIO_SPK_BUFFER_LENGTH 10
|
||||||
|
#define AUDIO_SPK_BUFFER_SIZE (AUDIO_SPK_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
|
||||||
|
#define AUDIO_MIX_CHANNEL_COUNT 16
|
||||||
|
#define AUDIO_DEVICEPAIR_INPUTBUFFER 16384
|
||||||
|
|
||||||
|
// Avoid too high resampler quality - it can take many CPU and cause gaps in playing
|
||||||
|
#define AUDIO_RESAMPLER_QUALITY 1
|
||||||
|
#define AEC_FRAME_TIME 10
|
||||||
|
#define AEC_TAIL_TIME 160
|
||||||
|
|
||||||
|
|
||||||
|
// Defined these two lines to get dumping of audio input/output
|
||||||
|
//#define AUDIO_DUMPINPUT
|
||||||
|
//#define AUDIO_DUMPOUTPUT
|
||||||
|
|
||||||
|
|
||||||
|
#define UA_REGISTRATION_TIME 3600
|
||||||
|
#define UA_MEDIA_PORT_START 20000
|
||||||
|
#define UA_MEDIA_PORT_FINISH 30000
|
||||||
|
#define UA_MAX_UDP_PACKET_SIZE 576
|
||||||
|
#define UA_PUBLICATION_ID "314"
|
||||||
|
|
||||||
|
#define MT_SAMPLERATE AUDIO_SAMPLERATE
|
||||||
|
|
||||||
|
#define MT_MAXAUDIOFRAME 1440
|
||||||
|
#define MT_MAXRTPPACKET 1500
|
||||||
|
#define MT_DTMF_END_PACKETS 3
|
||||||
|
|
||||||
|
#define RTP_BUFFER_HIGH 0
|
||||||
|
#define RTP_BUFFER_LOW 0
|
||||||
|
#define RTP_BUFFER_PREBUFFER 0
|
||||||
|
|
||||||
|
// #define RTP_BUFFER_HIGH 160
|
||||||
|
// #define RTP_BUFFER_LOW 10
|
||||||
|
// #define RTP_BUFFER_PREBUFFER 160
|
||||||
|
#define RTP_DECODED_CAPACITY 2048
|
||||||
|
|
||||||
|
#define DEFAULT_SUBSCRIPTION_TIME 1200
|
||||||
|
#define DEFAULT_SUBSCRIPTION_REFRESHTIME 500
|
||||||
|
|
||||||
|
#define PRESENCE_IN_REG_HEADER "PresenceInReg"
|
||||||
|
|
||||||
|
// Maximum UDP packet length
|
||||||
|
#define MAX_UDPPACKET_SIZE 65535
|
||||||
|
#define MAX_VALID_UDPPACKET_SIZE 2048
|
||||||
|
|
||||||
|
// AMR codec defines - it requires USE_AMR_CODEC defined
|
||||||
|
// #define USE_AMR_CODEC
|
||||||
|
#define MT_AMRNB_PAYLOADTYPE 112
|
||||||
|
#define MT_AMRNB_CODECNAME "amr"
|
||||||
|
|
||||||
|
#define MT_AMRNB_OCTET_PAYLOADTYPE 113
|
||||||
|
|
||||||
|
#define MT_AMRWB_PAYLOADTYPE 96
|
||||||
|
#define MT_AMRWB_CODECNAME "amr-wb"
|
||||||
|
|
||||||
|
#define MT_AMRWB_OCTET_PAYLOADTYPE 97
|
||||||
|
|
||||||
|
#define MT_GSMEFR_PAYLOADTYPE 126
|
||||||
|
#define MT_GSMEFR_CODECNAME "GERAN-EFR"
|
||||||
|
|
||||||
|
#define MT_EVS_PAYLOADTYPE 127
|
||||||
|
#define MT_EVS_CODECNAME "EVS"
|
||||||
|
|
||||||
|
// OPUS codec defines
|
||||||
|
// #define USE_OPUS_CODEC
|
||||||
|
#define MT_OPUS_CODEC_PT 106
|
||||||
|
|
||||||
|
// ILBC codec defines
|
||||||
|
#define MT_ILBC20_PAYLOADTYPE -1
|
||||||
|
#define MT_ILBC30_PAYLOADTYPE -1
|
||||||
|
|
||||||
|
// ISAC codec defines
|
||||||
|
#define MT_ISAC16K_PAYLOADTYPE -1
|
||||||
|
#define MT_ISAC32K_PAYLOADTYPE -1
|
||||||
|
|
||||||
|
// GSM HR payload type
|
||||||
|
#define MT_GSMHR_PAYLOADTYPE -1
|
||||||
|
|
||||||
|
// Mirror buffer capacity
|
||||||
|
#define MT_MIRROR_CAPACITY 32768
|
||||||
|
|
||||||
|
// Mirror buffer readiness threshold - 50 milliseconds
|
||||||
|
#define MT_MIRROR_PREBUFFER (MT_SAMPLERATE / 10)
|
||||||
|
|
||||||
|
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
||||||
|
# define TEXT(X) X
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// In milliseconds
|
||||||
|
#define MT_SEVANA_FRAME_TIME 680
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -2,11 +2,9 @@ cmake_minimum_required (VERSION 3.15)
|
||||||
project (helper_lib)
|
project (helper_lib)
|
||||||
|
|
||||||
# Rely on C++ 11
|
# Rely on C++ 11
|
||||||
set (CMAKE_CXX_STANDARD 11)
|
set (CMAKE_CXX_STANDARD 20)
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
option (USE_NULL_UUID "When enabled linking to libuuid is avoided" OFF)
|
|
||||||
|
|
||||||
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
file (GLOB HELPER_LIB_SOURCES "*.cpp" "*.h")
|
file (GLOB HELPER_LIB_SOURCES "*.cpp" "*.h")
|
||||||
|
|
@ -17,3 +15,6 @@ set_property(TARGET helper_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<C
|
||||||
# Private include directories
|
# Private include directories
|
||||||
target_include_directories(helper_lib PUBLIC ../../libs/ ../../engine ../ .)
|
target_include_directories(helper_lib PUBLIC ../../libs/ ../../engine ../ .)
|
||||||
target_compile_definitions(helper_lib PRIVATE -D_CRT_SECURE_NO_WARNINGS -D_UNICODE)
|
target_compile_definitions(helper_lib PRIVATE -D_CRT_SECURE_NO_WARNINGS -D_UNICODE)
|
||||||
|
if (TARGET_LINUX)
|
||||||
|
target_link_libraries (helper_lib PUBLIC uuid)
|
||||||
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -32,23 +32,24 @@ void FileHelper::remove(const char* s)
|
||||||
::remove(s);
|
::remove(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FileHelper::gettempname()
|
// std::string FileHelper::gettempname()
|
||||||
{
|
// {
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
// #if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
||||||
char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
|
// char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
|
||||||
mkstemp(template_filename);
|
// int code = mkstemp(template_filename);
|
||||||
return template_filename;
|
|
||||||
#elif defined(TARGET_WIN)
|
|
||||||
char buffer[L_tmpnam];
|
|
||||||
tmpnam(buffer);
|
|
||||||
|
|
||||||
return buffer;
|
// return template_filename;
|
||||||
#elif defined(TARGET_OSX)
|
// #elif defined(TARGET_WIN)
|
||||||
char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
|
// char buffer[L_tmpnam];
|
||||||
mktemp(template_filename);
|
// tmpnam(buffer);
|
||||||
return template_filename;
|
|
||||||
#endif
|
// return buffer;
|
||||||
}
|
// #elif defined(TARGET_OSX)
|
||||||
|
// char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
|
||||||
|
// mktemp(template_filename);
|
||||||
|
// return template_filename;
|
||||||
|
// #endif
|
||||||
|
// }
|
||||||
|
|
||||||
bool FileHelper::isAbsolute(const std::string& s)
|
bool FileHelper::isAbsolute(const std::string& s)
|
||||||
{
|
{
|
||||||
|
|
@ -109,3 +110,32 @@ size_t FileHelper::getFreespace(const std::string& path)
|
||||||
#endif
|
#endif
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string FileHelper::expandUserHome(const std::string &path)
|
||||||
|
{
|
||||||
|
if (path.empty() || path[0] != '~')
|
||||||
|
return path; // No expansion needed
|
||||||
|
|
||||||
|
const char* home_dir = nullptr;
|
||||||
|
|
||||||
|
#ifdef TARGET_WIN
|
||||||
|
home_dir = std::getenv("USERPROFILE");
|
||||||
|
if (!home_dir)
|
||||||
|
{
|
||||||
|
home_dir = std::getenv("HOMEDRIVE");
|
||||||
|
const char* homepath = std::getenv("HOMEPATH");
|
||||||
|
if (home_dir && homepath) {
|
||||||
|
std::string fullpath(home_dir);
|
||||||
|
fullpath += homepath;
|
||||||
|
return fullpath + path.substr(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
home_dir = std::getenv("HOME");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!home_dir)
|
||||||
|
throw std::runtime_error("Unable to determine the home directory");
|
||||||
|
|
||||||
|
return std::string(home_dir) + path.substr(1);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public:
|
||||||
static void remove(const std::string& s);
|
static void remove(const std::string& s);
|
||||||
static void remove(const char* s);
|
static void remove(const char* s);
|
||||||
|
|
||||||
static std::string gettempname();
|
// static std::string gettempname();
|
||||||
static bool isAbsolute(const std::string& s);
|
static bool isAbsolute(const std::string& s);
|
||||||
|
|
||||||
static std::string getCurrentDir();
|
static std::string getCurrentDir();
|
||||||
|
|
@ -23,6 +23,7 @@ public:
|
||||||
// Returns free space on volume for path
|
// Returns free space on volume for path
|
||||||
// Works for Linux only. For other systems (size_t)-1 is returned (for errors too)
|
// Works for Linux only. For other systems (size_t)-1 is returned (for errors too)
|
||||||
static size_t getFreespace(const std::string& path);
|
static size_t getFreespace(const std::string& path);
|
||||||
|
static std::string expandUserHome(const std::string& path);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
# include <asm/ioctls.h>
|
# include <asm/ioctls.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "HL_NetworkSocket.h"
|
#include "HL_NetworkSocket.h"
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
||||||
|
|
@ -27,7 +27,7 @@ DatagramSocket::DatagramSocket()
|
||||||
|
|
||||||
DatagramSocket::~DatagramSocket()
|
DatagramSocket::~DatagramSocket()
|
||||||
{
|
{
|
||||||
closeSocket();
|
internalClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatagramSocket::open(int family)
|
void DatagramSocket::open(int family)
|
||||||
|
|
@ -85,7 +85,7 @@ unsigned DatagramSocket::recvDatagram(InternetAddress &src, void *packetBuffer,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatagramSocket::closeSocket()
|
void DatagramSocket::internalClose()
|
||||||
{
|
{
|
||||||
if (mHandle != INVALID_SOCKET)
|
if (mHandle != INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
|
|
@ -98,6 +98,11 @@ void DatagramSocket::closeSocket()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DatagramSocket::closeSocket()
|
||||||
|
{
|
||||||
|
internalClose();
|
||||||
|
}
|
||||||
|
|
||||||
bool DatagramSocket::isValid() const
|
bool DatagramSocket::isValid() const
|
||||||
{
|
{
|
||||||
return mHandle != INVALID_SOCKET;
|
return mHandle != INVALID_SOCKET;
|
||||||
|
|
@ -163,7 +168,7 @@ unsigned DatagramAgreggator::count()
|
||||||
bool DatagramAgreggator::hasDataAtIndex(unsigned index)
|
bool DatagramAgreggator::hasDataAtIndex(unsigned index)
|
||||||
{
|
{
|
||||||
PDatagramSocket socket = mSocketVector[index];
|
PDatagramSocket socket = mSocketVector[index];
|
||||||
return (FD_ISSET(socket->mHandle, &mReadSet) != 0);
|
return FD_ISSET(socket->mHandle, &mReadSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
PDatagramSocket DatagramAgreggator::socketAt(unsigned index)
|
PDatagramSocket DatagramAgreggator::socketAt(unsigned index)
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,12 @@ public:
|
||||||
virtual SOCKET socket() const;
|
virtual SOCKET socket() const;
|
||||||
|
|
||||||
virtual void open(int family);
|
virtual void open(int family);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int mFamily;
|
int mFamily;
|
||||||
SOCKET mHandle;
|
SOCKET mHandle;
|
||||||
int mLocalPort;
|
int mLocalPort;
|
||||||
|
void internalClose();
|
||||||
};
|
};
|
||||||
typedef std::shared_ptr<DatagramSocket> PDatagramSocket;
|
typedef std::shared_ptr<DatagramSocket> PDatagramSocket;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ std::string OsProcess::execCommand(const std::string& cmd)
|
||||||
PROCESS_INFORMATION pi = { 0 };
|
PROCESS_INFORMATION pi = { 0 };
|
||||||
|
|
||||||
char* cmdline = (char*)_alloca(cmd.size()+1);
|
char* cmdline = (char*)_alloca(cmd.size()+1);
|
||||||
strcpy(cmdline, StringHelper::replace(cmd, "/", "\\").c_str());
|
strcpy(cmdline, strx::replace(cmd, "/", "\\").c_str());
|
||||||
|
|
||||||
BOOL fSuccess = CreateProcessA( nullptr, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
|
BOOL fSuccess = CreateProcessA( nullptr, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
|
||||||
if (! fSuccess)
|
if (! fSuccess)
|
||||||
|
|
@ -114,7 +114,7 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
|
||||||
memset(&pi, 0, sizeof pi);
|
memset(&pi, 0, sizeof pi);
|
||||||
|
|
||||||
char* cmdbuffer = (char*)_alloca(cmdline.size()+1);
|
char* cmdbuffer = (char*)_alloca(cmdline.size()+1);
|
||||||
strcpy(cmdbuffer, StringHelper::replace(cmdline, "/", "\\").c_str());
|
strcpy(cmdbuffer, strx::replace(cmdline, "/", "\\").c_str());
|
||||||
|
|
||||||
|
|
||||||
BOOL fSuccess = CreateProcessA( nullptr, cmdbuffer, nullptr, nullptr, TRUE,
|
BOOL fSuccess = CreateProcessA( nullptr, cmdbuffer, nullptr, nullptr, TRUE,
|
||||||
|
|
@ -160,14 +160,14 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
|
||||||
{
|
{
|
||||||
std::string line(buf, cr - buf -1);
|
std::string line(buf, cr - buf -1);
|
||||||
if (callback)
|
if (callback)
|
||||||
callback(StringHelper::trim(line));
|
callback(strx::trim(line));
|
||||||
memmove(buf, cr + 1, strlen(cr+1) + 1);
|
memmove(buf, cr + 1, strlen(cr+1) + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} //for
|
} //for
|
||||||
|
|
||||||
if (buf[0])
|
if (buf[0])
|
||||||
callback(StringHelper::trim(std::string(buf)));
|
callback(strx::trim(std::string(buf)));
|
||||||
|
|
||||||
char ctrlc = 3;
|
char ctrlc = 3;
|
||||||
//if (finish_flag)
|
//if (finish_flag)
|
||||||
|
|
@ -278,12 +278,12 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
|
||||||
}
|
}
|
||||||
while (r == sizeof(buffer) - 1);
|
while (r == sizeof(buffer) - 1);
|
||||||
|
|
||||||
if (lines.find("\n") != std::string::npos && line_callback)
|
if (lines.find('\n') != std::string::npos && line_callback)
|
||||||
{
|
{
|
||||||
std::string::size_type p = 0;
|
std::string::size_type p = 0;
|
||||||
while (p < lines.size())
|
while (p < lines.size())
|
||||||
{
|
{
|
||||||
std::string::size_type d = lines.find("\n", p);
|
std::string::size_type d = lines.find('\n', p);
|
||||||
if (d != std::string::npos)
|
if (d != std::string::npos)
|
||||||
{
|
{
|
||||||
if (line_callback)
|
if (line_callback)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@
|
||||||
# include <Windows.h>
|
# include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
||||||
|
# include <arpa/inet.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "HL_Rtp.h"
|
#include "HL_Rtp.h"
|
||||||
#include "HL_Exception.h"
|
#include "HL_Exception.h"
|
||||||
#include "HL_String.h"
|
#include "HL_String.h"
|
||||||
|
|
@ -52,8 +56,12 @@ bool RtpHelper::isRtp(const void* buffer, size_t length)
|
||||||
if (length < 12)
|
if (length < 12)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
unsigned char _type = reinterpret_cast<const RtpHeader*>(buffer)->pt;
|
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
||||||
bool rtp = ( (_type & 0x7F) >= 96 && (_type & 0x7F) <= 127) || ((_type & 0x7F) < 35);
|
if (h->version != 0b10)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned char pt = h->pt;
|
||||||
|
bool rtp = ( (pt & 0x7F) >= 96 && (pt & 0x7F) <= 127) || ((pt & 0x7F) < 35);
|
||||||
return rtp;
|
return rtp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,9 +70,8 @@ bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (length < 12)
|
if (length < 12)
|
||||||
return false;
|
return false;
|
||||||
unsigned char b = ((const unsigned char*)buffer)[0];
|
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
||||||
|
return h->version == 0b10;
|
||||||
return (b & 0xC0 ) == 128;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
||||||
|
|
@ -75,9 +82,9 @@ bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
||||||
unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
|
unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
|
||||||
{
|
{
|
||||||
if (isRtp(buffer, length))
|
if (isRtp(buffer, length))
|
||||||
return reinterpret_cast<const RtpHeader*>(buffer)->ssrc;
|
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
||||||
else
|
else
|
||||||
return reinterpret_cast<const RtcpHeader*>(buffer)->ssrc;
|
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
@ -10,11 +10,8 @@
|
||||||
# include "jrtplib/src/rtppacket.h"
|
# include "jrtplib/src/rtppacket.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "HL_Uuid.h"
|
#include <cstdint>
|
||||||
#include "HL_InternetAddress.h"
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// Class to carry rtp/rtcp socket pair
|
// Class to carry rtp/rtcp socket pair
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
@ -127,7 +127,7 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
|
||||||
if (sock == INVALID_SOCKET)
|
if (sock == INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
// Return null socket
|
// Return null socket
|
||||||
PDatagramSocket result(new DatagramSocket());
|
auto result = std::make_shared<DatagramSocket>();
|
||||||
result->mLocalPort = port;
|
result->mLocalPort = port;
|
||||||
result->mFamily = family;
|
result->mFamily = family;
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -170,7 +170,7 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
|
||||||
closesocket(sock);
|
closesocket(sock);
|
||||||
throw Exception(ERR_NET_FAILED, WSAGetLastError());
|
throw Exception(ERR_NET_FAILED, WSAGetLastError());
|
||||||
}
|
}
|
||||||
PDatagramSocket resultObject(new DatagramSocket());
|
auto resultObject = std::make_shared<DatagramSocket>();
|
||||||
resultObject->mLocalPort = testport;
|
resultObject->mLocalPort = testport;
|
||||||
resultObject->mHandle = sock;
|
resultObject->mHandle = sock;
|
||||||
if (!resultObject->setBlocking(false))
|
if (!resultObject->setBlocking(false))
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef __SOCKET_HEAP_H
|
#ifndef __SOCKET_HEAP_H
|
||||||
#define __SOCKET_HEAP_H
|
#define __SOCKET_HEAP_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,11 @@ int strx::toInt(const char *s, int defaultValue, bool* isOk)
|
||||||
uint64_t strx::toUint64(const char* s, uint64_t def, bool *isOk)
|
uint64_t strx::toUint64(const char* s, uint64_t def, bool *isOk)
|
||||||
{
|
{
|
||||||
uint64_t result = def;
|
uint64_t result = def;
|
||||||
if (sscanf(s, "%" SCNu64, &result) != 1)
|
#if defined(TARGET_WIN)
|
||||||
|
if (sscanf(s, "%I64d", &result) != 1)
|
||||||
|
#else
|
||||||
|
if (sscanf(s, "%llu", &result) != 1)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
if (isOk)
|
if (isOk)
|
||||||
*isOk = false;
|
*isOk = false;
|
||||||
|
|
@ -408,3 +412,46 @@ std::string strx::uppercase(const std::string& s)
|
||||||
std::transform(r.begin(), r.end(), r.begin(), ::toupper);
|
std::transform(r.begin(), r.end(), r.begin(), ::toupper);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string strx::lowercase(const std::string& s)
|
||||||
|
{
|
||||||
|
std::string r(s);
|
||||||
|
std::transform(r.begin(), r.end(), r.begin(), ::tolower);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string strx::removeQuotes(const std::string& s)
|
||||||
|
{
|
||||||
|
std::string r(s);
|
||||||
|
if (s.empty())
|
||||||
|
return s;
|
||||||
|
|
||||||
|
if (r.front() == '"')
|
||||||
|
r = r.substr(1);
|
||||||
|
|
||||||
|
if (r.back() == '"')
|
||||||
|
r = r.substr(0, r.size()-1);
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(TARGET_WIN)
|
||||||
|
|
||||||
|
// MSVC++ lacks memmem support
|
||||||
|
const void *memmem(const void *haystack, size_t haystack_len,
|
||||||
|
const void * const needle, const size_t needle_len)
|
||||||
|
{
|
||||||
|
if (!haystack || !haystack_len || !needle || !needle_len)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
for (const char *h = (const char*)haystack;
|
||||||
|
haystack_len >= needle_len;
|
||||||
|
++h, --haystack_len) {
|
||||||
|
if (!memcmp(h, needle, needle_len)) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <cstdint>
|
||||||
#include "HL_Types.h"
|
#include "HL_Types.h"
|
||||||
|
|
||||||
#ifdef TARGET_OSX
|
#ifdef TARGET_OSX
|
||||||
|
|
@ -19,6 +20,9 @@
|
||||||
class strx
|
class strx
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static std::string extractFilename(const std::string& path);
|
||||||
|
static std::string appendPath(const std::string& s1, const std::string& s2);
|
||||||
|
|
||||||
static std::string makeUtf8(const std::tstring& arg);
|
static std::string makeUtf8(const std::tstring& arg);
|
||||||
static std::string toUtf8(const std::tstring& arg);
|
static std::string toUtf8(const std::tstring& arg);
|
||||||
static std::tstring makeTstring(const std::string& arg);
|
static std::tstring makeTstring(const std::string& arg);
|
||||||
|
|
@ -29,15 +33,13 @@ public:
|
||||||
static std::string toHex(const void* ptr);
|
static std::string toHex(const void* ptr);
|
||||||
static std::string toHex(const uint8_t* input, size_t inputLength);
|
static std::string toHex(const uint8_t* input, size_t inputLength);
|
||||||
static std::string intToString(int value);
|
static std::string intToString(int value);
|
||||||
|
static std::string prefixLines(const std::string& source, const std::string& prefix);
|
||||||
static std::string doubleToString(double value, int precision);
|
static std::string doubleToString(double value, int precision);
|
||||||
static int fromHex2Int(const std::string& s);
|
static int fromHex2Int(const std::string& s);
|
||||||
static std::string fromHex2String(const std::string& s);
|
static std::string fromHex2String(const std::string& s);
|
||||||
static float toFloat(const std::string& s, float defaultValue = 0.0, bool* isOk = nullptr);
|
static float toFloat(const std::string& s, float defaultValue = 0.0, bool* isOk = nullptr);
|
||||||
|
|
||||||
static std::string extractFilename(const std::string& path);
|
|
||||||
static std::string appendPath(const std::string& s1, const std::string& s2);
|
|
||||||
|
|
||||||
static std::string prefixLines(const std::string& source, const std::string& prefix);
|
|
||||||
static const char* findSubstring(const char* buffer, const char* substring, size_t bufferLength);
|
static const char* findSubstring(const char* buffer, const char* substring, size_t bufferLength);
|
||||||
|
|
||||||
static void split(const std::string& src, std::vector<std::string>& dst, const std::string& delims);
|
static void split(const std::string& src, std::vector<std::string>& dst, const std::string& delims);
|
||||||
|
|
@ -49,7 +51,7 @@ public:
|
||||||
std::ostringstream s;
|
std::ostringstream s;
|
||||||
for (const auto& i : v)
|
for (const auto& i : v)
|
||||||
{
|
{
|
||||||
if (&i != &v[0])
|
if (&i != &v.front())
|
||||||
s << delimiter;
|
s << delimiter;
|
||||||
s << i;
|
s << i;
|
||||||
}
|
}
|
||||||
|
|
@ -67,8 +69,10 @@ public:
|
||||||
static bool startsWith(const std::string& s, const std::string& prefix);
|
static bool startsWith(const std::string& s, const std::string& prefix);
|
||||||
static bool endsWith(const std::string& s, const std::string& suffix);
|
static bool endsWith(const std::string& s, const std::string& suffix);
|
||||||
static int stringToDuration(const std::string& s);
|
static int stringToDuration(const std::string& s);
|
||||||
static std::string uppercase(const std::string& s);
|
|
||||||
|
|
||||||
|
static std::string uppercase(const std::string& s);
|
||||||
|
static std::string lowercase(const std::string& s);
|
||||||
|
static std::string removeQuotes(const std::string& s);
|
||||||
};
|
};
|
||||||
|
|
||||||
class XcapHelper
|
class XcapHelper
|
||||||
|
|
@ -81,4 +85,11 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(TARGET_WIN)
|
||||||
|
|
||||||
|
// MSVC++ lacks memmem support
|
||||||
|
extern const void *memmem(const void *haystack, size_t haystack_len,
|
||||||
|
const void * const needle, const size_t needle_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -77,19 +77,21 @@ uint64_t ThreadHelper::getCurrentId()
|
||||||
// ------------------- TimeHelper ---------------
|
// ------------------- TimeHelper ---------------
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
// Milliseconds starting from the epoch
|
||||||
static uint64_t TimestampStartPoint = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
|
static uint64_t TimestampStartPoint = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
|
||||||
|
|
||||||
|
// Seconds starting from the epoch
|
||||||
static time_t TimestampBase = time(nullptr);
|
static time_t TimestampBase = time(nullptr);
|
||||||
|
|
||||||
uint64_t TimeHelper::getTimestamp()
|
// Returns number of milliseconds starting from 01 Jan 1970 GMT
|
||||||
|
uint64_t chronox::getTimestamp()
|
||||||
{
|
{
|
||||||
time_point<steady_clock> t = steady_clock::now();
|
time_point<steady_clock> t = steady_clock::now();
|
||||||
|
|
||||||
uint64_t ms = duration_cast< milliseconds >(t.time_since_epoch()).count();
|
uint64_t ms = duration_cast< milliseconds >(t.time_since_epoch()).count();
|
||||||
|
|
||||||
return ms - TimestampStartPoint + TimestampBase * 1000;
|
return ms - TimestampStartPoint + TimestampBase * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t TimeHelper::getUptime()
|
uint64_t chronox::getUptime()
|
||||||
{
|
{
|
||||||
time_point<steady_clock> t = steady_clock::now();
|
time_point<steady_clock> t = steady_clock::now();
|
||||||
|
|
||||||
|
|
@ -98,7 +100,7 @@ uint64_t TimeHelper::getUptime()
|
||||||
return ms - TimestampStartPoint;
|
return ms - TimestampStartPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t TimeHelper::getDelta(uint32_t later, uint32_t earlier)
|
uint32_t chronox::getDelta(uint32_t later, uint32_t earlier)
|
||||||
{
|
{
|
||||||
if (later > earlier)
|
if (later > earlier)
|
||||||
return later - earlier;
|
return later - earlier;
|
||||||
|
|
@ -109,14 +111,42 @@ uint32_t TimeHelper::getDelta(uint32_t later, uint32_t earlier)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeHelper::ExecutionTime::ExecutionTime()
|
timespec chronox::toTimespec(uint64_t milliseconds)
|
||||||
{
|
{
|
||||||
mStart = TimeHelper::getTimestamp();
|
timespec r;
|
||||||
|
r.tv_sec = milliseconds / 1000;
|
||||||
|
r.tv_nsec = milliseconds % 1000;
|
||||||
|
r.tv_nsec *= 1000 * 1000;
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t TimeHelper::ExecutionTime::getSpentTime() const
|
uint64_t chronox::toTimestamp(const timeval& ts)
|
||||||
{
|
{
|
||||||
return TimeHelper::getTimestamp() - mStart;
|
return ts.tv_sec * 1000 + ts.tv_usec / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t chronox::getDelta(const timespec& a, const timespec& b)
|
||||||
|
{
|
||||||
|
uint64_t ms_a = a.tv_sec * 1000 + a.tv_nsec / 10000000;
|
||||||
|
uint64_t ms_b = b.tv_sec * 1000 + a.tv_nsec / 10000000;
|
||||||
|
return ms_a - ms_b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t chronox::getDelta(const timeval& a, const timeval& b)
|
||||||
|
{
|
||||||
|
int64_t diff_seconds = a.tv_sec - b.tv_sec;
|
||||||
|
int64_t diff_microseconds = a.tv_usec - b.tv_usec;
|
||||||
|
return diff_seconds * 1000 + diff_microseconds / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
chronox::ExecutionTime::ExecutionTime()
|
||||||
|
{
|
||||||
|
mStart = chronox::getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t chronox::ExecutionTime::getSpentTime() const
|
||||||
|
{
|
||||||
|
return chronox::getTimestamp() - mStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------- BufferQueue -----------------
|
// --------------- BufferQueue -----------------
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#if defined(TARGET_WIN)
|
||||||
|
# include <WinSock2.h>
|
||||||
|
# include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef std::recursive_mutex Mutex;
|
typedef std::recursive_mutex Mutex;
|
||||||
typedef std::unique_lock<std::recursive_mutex> Lock;
|
typedef std::unique_lock<std::recursive_mutex> Lock;
|
||||||
|
|
||||||
|
|
@ -46,7 +51,7 @@ public:
|
||||||
static uint64_t getCurrentId();
|
static uint64_t getCurrentId();
|
||||||
};
|
};
|
||||||
|
|
||||||
class TimeHelper
|
class chronox
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Returns current timestamp in milliseconds
|
// Returns current timestamp in milliseconds
|
||||||
|
|
@ -59,6 +64,14 @@ public:
|
||||||
// Handles cases when clock is wrapped.
|
// Handles cases when clock is wrapped.
|
||||||
static uint32_t getDelta(uint32_t later, uint32_t earlier);
|
static uint32_t getDelta(uint32_t later, uint32_t earlier);
|
||||||
|
|
||||||
|
// Converts number of milliseconds starting from Epoch begin to timespec.
|
||||||
|
static timespec toTimespec(uint64_t milliseconds);
|
||||||
|
static uint64_t toTimestamp(const timeval& ts);
|
||||||
|
|
||||||
|
// Returns difference between timestamps in milliseconds
|
||||||
|
static int64_t getDelta(const timespec& a, const timespec& b);
|
||||||
|
static int64_t getDelta(const timeval& a, const timeval& b);
|
||||||
|
|
||||||
class ExecutionTime
|
class ExecutionTime
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ thread_pool::thread_pool(size_t num_of_threads, const std::string& name)
|
||||||
num_of_threads = std::thread::hardware_concurrency();
|
num_of_threads = std::thread::hardware_concurrency();
|
||||||
|
|
||||||
for(size_t idx = 0; idx < num_of_threads; idx++)
|
for(size_t idx = 0; idx < num_of_threads; idx++)
|
||||||
this->workers.push_back(std::thread(&thread_pool::run_worker, this));
|
this->workers.emplace_back(std::thread(&thread_pool::run_worker, this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new work item to the pool
|
// Add new work item to the pool
|
||||||
|
|
@ -20,6 +20,22 @@ void thread_pool::enqueue(const thread_pool::task& t)
|
||||||
this->condition.notify_one();
|
this->condition.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void thread_pool::wait(std::chrono::milliseconds interval)
|
||||||
|
{
|
||||||
|
while (size() != 0)
|
||||||
|
std::this_thread::sleep_for(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t thread_pool::size()
|
||||||
|
{
|
||||||
|
std::unique_lock l(this->queue_mutex);
|
||||||
|
return this->tasks.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t thread_pool::threads()
|
||||||
|
{
|
||||||
|
return this->workers.size();
|
||||||
|
}
|
||||||
|
|
||||||
// the destructor joins all threads
|
// the destructor joins all threads
|
||||||
thread_pool::~thread_pool()
|
thread_pool::~thread_pool()
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,13 @@ class thread_pool
|
||||||
public:
|
public:
|
||||||
typedef std::function<void()> task;
|
typedef std::function<void()> task;
|
||||||
|
|
||||||
thread_pool(size_t num_of_threads, const std::string&);
|
thread_pool(size_t num_of_threads, const std::string& thread_name);
|
||||||
~thread_pool();
|
~thread_pool();
|
||||||
|
|
||||||
void enqueue(const task& task);
|
void enqueue(const task& task);
|
||||||
|
void wait(std::chrono::milliseconds interval = std::chrono::milliseconds(50));
|
||||||
|
size_t size();
|
||||||
|
size_t threads();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// need to keep track of threads so we can join them
|
// need to keep track of threads so we can join them
|
||||||
|
|
@ -33,7 +36,7 @@ private:
|
||||||
// synchronization
|
// synchronization
|
||||||
std::mutex queue_mutex;
|
std::mutex queue_mutex;
|
||||||
std::condition_variable condition;
|
std::condition_variable condition;
|
||||||
bool stop = false;
|
std::atomic_bool stop = false;
|
||||||
|
|
||||||
// thread name prefix for worker threads
|
// thread name prefix for worker threads
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,53 @@
|
||||||
#include "HL_Time.h"
|
#include "HL_Time.h"
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <chrono>
|
||||||
/* return current time in milliseconds */
|
/* return current time in milliseconds */
|
||||||
double now_ms(void) {
|
double now_ms(void)
|
||||||
|
{
|
||||||
|
#if defined(TARGET_WIN)
|
||||||
|
auto tp = std::chrono::steady_clock::now();
|
||||||
|
auto result = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count();
|
||||||
|
return result;
|
||||||
|
#else
|
||||||
struct timespec res;
|
struct timespec res;
|
||||||
clock_gettime(CLOCK_MONOTONIC, &res);
|
clock_gettime(CLOCK_MONOTONIC, &res);
|
||||||
return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
|
return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int compare_timespec(const timespec& lhs, const timespec& rhs)
|
||||||
|
{
|
||||||
|
if (lhs.tv_sec < rhs.tv_sec)
|
||||||
|
return -1;
|
||||||
|
if (lhs.tv_sec > rhs.tv_sec)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (lhs.tv_nsec < rhs.tv_nsec)
|
||||||
|
return -1;
|
||||||
|
if (lhs.tv_nsec > rhs.tv_nsec)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator < (const timespec& lhs, const timespec& rhs)
|
||||||
|
{
|
||||||
|
return compare_timespec(lhs, rhs) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator == (const timespec& lhs, const timespec& rhs)
|
||||||
|
{
|
||||||
|
return compare_timespec(lhs, rhs) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator > (const timespec& lhs, const timespec& rhs)
|
||||||
|
{
|
||||||
|
return compare_timespec(lhs, rhs) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_zero(const timespec& ts)
|
||||||
|
{
|
||||||
|
return !ts.tv_sec && !ts.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,17 @@
|
||||||
#ifndef __HELPER_TIME_H
|
#ifndef __HELPER_TIME_H
|
||||||
#define __HELPER_TIME_H
|
#define __HELPER_TIME_H
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
// Return current monotonic number of milliseconds starting from some point
|
||||||
extern double now_ms();
|
extern double now_ms();
|
||||||
|
|
||||||
|
// Compare the timespec.
|
||||||
|
// Returns -1 if lhs < rhs, 1 if lhs > rhs, 0 if equal
|
||||||
|
extern int compare_timespec(const timespec& lhs, const timespec& rhs);
|
||||||
|
extern bool operator < (const timespec& lhs, const timespec& rhs);
|
||||||
|
extern bool operator == (const timespec& lhs, const timespec& rhs);
|
||||||
|
extern bool operator > (const timespec& lhs, const timespec& rhs);
|
||||||
|
extern bool is_zero(const timespec& ts);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#include "HL_Types.h"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2025 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
@ -26,4 +26,139 @@ enum SdpDirection
|
||||||
Sdp_Offer
|
Sdp_Offer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
template<
|
||||||
|
class K, class V,
|
||||||
|
class HashK = std::hash<K>, class EqK = std::equal_to<K>,
|
||||||
|
class HashV = std::hash<V>, class EqV = std::equal_to<V>
|
||||||
|
>
|
||||||
|
class BiMap {
|
||||||
|
public:
|
||||||
|
using key_type = K;
|
||||||
|
using mapped_type = V;
|
||||||
|
|
||||||
|
BiMap(const std::map<K,V>& initializers) {
|
||||||
|
for (const auto& item: initializers) {
|
||||||
|
insert(item.first, item.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a new (key, value) pair. Returns false if either key or value already exists.
|
||||||
|
bool insert(const K& k, const V& v) {
|
||||||
|
if (contains_key(k) || contains_value(v)) return false;
|
||||||
|
auto ok = forward_.emplace(k, v);
|
||||||
|
try {
|
||||||
|
auto ov = reverse_.emplace(v, k);
|
||||||
|
if (!ov.second) { // shouldn't happen given the guard above
|
||||||
|
forward_.erase(k);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
forward_.erase(k);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return ok.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert(K&& k, V&& v) {
|
||||||
|
if (contains_key(k) || contains_value(v)) return false;
|
||||||
|
auto ok = forward_.emplace(std::move(k), std::move(v));
|
||||||
|
try {
|
||||||
|
auto ov = reverse_.emplace(ok.first->second, ok.first->first); // use stored refs
|
||||||
|
if (!ov.second) {
|
||||||
|
forward_.erase(ok.first);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
forward_.erase(ok.first);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
return ok.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace value for existing key (and update reverse map). Returns false if value is already bound elsewhere.
|
||||||
|
bool replace_by_key(const K& k, const V& new_v) {
|
||||||
|
auto it = forward_.find(k);
|
||||||
|
if (it == forward_.end()) return false;
|
||||||
|
if (contains_value(new_v)) return false;
|
||||||
|
// remove old reverse, insert new reverse, then update forward
|
||||||
|
reverse_.erase(it->second);
|
||||||
|
reverse_.emplace(new_v, k);
|
||||||
|
it->second = new_v;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace key for existing value (and update forward map). Returns false if key is already bound elsewhere.
|
||||||
|
bool replace_by_value(const V& v, const K& new_k) {
|
||||||
|
auto it = reverse_.find(v);
|
||||||
|
if (it == reverse_.end()) return false;
|
||||||
|
if (contains_key(new_k)) return false;
|
||||||
|
forward_.erase(it->second);
|
||||||
|
forward_.emplace(new_k, v);
|
||||||
|
it->second = new_k;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase by key/value. Return number erased (0 or 1).
|
||||||
|
size_t erase_key(const K& k) {
|
||||||
|
auto it = forward_.find(k);
|
||||||
|
if (it == forward_.end()) return 0;
|
||||||
|
reverse_.erase(it->second);
|
||||||
|
forward_.erase(it);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t erase_value(const V& v) {
|
||||||
|
auto it = reverse_.find(v);
|
||||||
|
if (it == reverse_.end()) return 0;
|
||||||
|
forward_.erase(it->second);
|
||||||
|
reverse_.erase(it);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup
|
||||||
|
bool contains_key(const K& k) const { return forward_.find(k) != forward_.end(); }
|
||||||
|
bool contains_value(const V& v) const { return reverse_.find(v) != reverse_.end(); }
|
||||||
|
|
||||||
|
const V* find_by_key(const K& k) const {
|
||||||
|
auto it = forward_.find(k);
|
||||||
|
return (it == forward_.end()) ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
const K* find_by_value(const V& v) const {
|
||||||
|
auto it = reverse_.find(v);
|
||||||
|
return (it == reverse_.end()) ? nullptr : &it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// at() variants throw std::out_of_range on missing entries
|
||||||
|
const V& at_key(const K& k) const { return forward_.at(k); }
|
||||||
|
const K& at_value(const V& v) const { return reverse_.at(v); }
|
||||||
|
|
||||||
|
void clear() noexcept {
|
||||||
|
forward_.clear();
|
||||||
|
reverse_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const noexcept { return forward_.empty(); }
|
||||||
|
size_t size() const noexcept { return forward_.size(); }
|
||||||
|
|
||||||
|
// Reserve buckets for performance (optional)
|
||||||
|
void reserve(size_t n) {
|
||||||
|
forward_.reserve(n);
|
||||||
|
reverse_.reserve(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<K, V, HashK, EqK> forward_;
|
||||||
|
std::unordered_map<V, K, HashV, EqV> reverse_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
typedef std::chrono::steady_clock::time_point timepoint_t;
|
||||||
|
typedef std::chrono::steady_clock monoclock_t;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,57 @@
|
||||||
#include "HL_Uuid.h"
|
#include "HL_Uuid.h"
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
|
#include <random>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#if defined(TARGET_LINUX)
|
||||||
|
#define UUID_SYSTEM_GENERATOR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "uuid.h"
|
||||||
|
|
||||||
Uuid::Uuid()
|
Uuid::Uuid()
|
||||||
{
|
{
|
||||||
#if defined(TARGET_WIN) || defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
memset(&mUuid, 0, sizeof mUuid);
|
memset(&mUuid, 0, sizeof mUuid);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uuid Uuid::generateOne()
|
Uuid Uuid::generateOne()
|
||||||
{
|
{
|
||||||
Uuid result;
|
Uuid result;
|
||||||
#if !defined(USE_NULL_UUID)
|
// #if defined(TARGET_LINUX)
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
// auto id = uuids::uuid_system_generator{}();
|
||||||
uuid_generate(result.mUuid);
|
// #else
|
||||||
#endif
|
std::random_device rd;
|
||||||
#if defined(TARGET_WIN)
|
auto seed_data = std::array<int, std::mt19937::state_size> {};
|
||||||
UuidCreate(&result.mUuid);
|
std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd));
|
||||||
#endif
|
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
|
||||||
#endif
|
std::mt19937 generator(seq);
|
||||||
|
uuids::uuid_random_generator gen{generator};
|
||||||
|
|
||||||
|
auto id = gen();
|
||||||
|
// #endif
|
||||||
|
memcpy(result.mUuid, id.as_bytes().data(), id.as_bytes().size_bytes());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uuid Uuid::parse(const std::string &s)
|
Uuid Uuid::parse(const std::string &s)
|
||||||
{
|
{
|
||||||
Uuid result;
|
Uuid result;
|
||||||
#if !defined(USE_NULL_UUID)
|
auto id = uuids::uuid::from_string(s);
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
if (id)
|
||||||
uuid_parse(s.c_str(), result.mUuid);
|
memcpy(result.mUuid, id->as_bytes().data(), id->as_bytes().size_bytes());
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
UuidFromStringA((RPC_CSTR)s.c_str(), &result.mUuid);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Uuid::toString() const
|
std::string Uuid::toString() const
|
||||||
{
|
{
|
||||||
#if defined(USE_NULL_UUID)
|
auto s = std::span<uuids::uuid::value_type, 16>{(uuids::uuid::value_type*)mUuid, 16};
|
||||||
return "UUID_disabled";
|
uuids::uuid id(s);
|
||||||
#else
|
return uuids::to_string(id);
|
||||||
char buf[64];
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
uuid_unparse_lower(mUuid, buf);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
RPC_CSTR s = nullptr;
|
|
||||||
UuidToStringA(&mUuid, &s);
|
|
||||||
if (s)
|
|
||||||
{
|
|
||||||
strcpy(buf, (const char*)s);
|
|
||||||
RpcStringFreeA(&s);
|
|
||||||
s = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Uuid::operator < (const Uuid& right) const
|
bool Uuid::operator < (const Uuid& right) const
|
||||||
{
|
{
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
return memcmp(mUuid, right.mUuid, sizeof(mUuid)) < 0;
|
return memcmp(mUuid, right.mUuid, sizeof(mUuid)) < 0;
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
return memcmp(&mUuid, &right.mUuid, sizeof(mUuid)) < 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,7 @@
|
||||||
#define __HL_UUID_H
|
#define __HL_UUID_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <stdint.h>
|
||||||
#if (defined(TARGET_LINUX) || defined(TARGET_OSX)) && !defined(USE_NULL_UUID)
|
|
||||||
// Please do not forget "sudo apt install uuid-dev" on Ubuntu
|
|
||||||
# include <uuid/uuid.h>
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
# include <rpc.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class Uuid
|
class Uuid
|
||||||
{
|
{
|
||||||
|
|
@ -21,20 +14,7 @@ public:
|
||||||
bool operator < (const Uuid& right) const;
|
bool operator < (const Uuid& right) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
#if defined(USE_NULL_UUID)
|
uint8_t mUuid[16];
|
||||||
unsigned char mUuid[16];
|
|
||||||
#else
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
uuid_t mUuid;
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
UUID mUuid;
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_ANDROID)
|
|
||||||
// Stub only
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
project (media_lib)
|
project (media_lib)
|
||||||
|
|
||||||
# Rely on C++ 11
|
if (NOT DEFINED LIB_PLATFORM)
|
||||||
set (CMAKE_CXX_STANDARD 11)
|
message(FATAL_ERROR media_lib project requires LIB_PLATFORM to be set - it uses libraries from that directory)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Rely on C++ 20
|
||||||
|
set (CMAKE_CXX_STANDARD 20)
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Produce PIC code always
|
# Produce PIC code always
|
||||||
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
option (USE_RESIP_INTEGRATION "Use some resiprocate specific routines" OFF)
|
|
||||||
|
|
||||||
set (SOURCES
|
set (SOURCES
|
||||||
MT_Statistics.cpp
|
MT_Statistics.cpp
|
||||||
|
|
@ -23,6 +26,8 @@ set (SOURCES
|
||||||
MT_AudioReceiver.cpp
|
MT_AudioReceiver.cpp
|
||||||
MT_AudioCodec.cpp
|
MT_AudioCodec.cpp
|
||||||
MT_CngHelper.cpp
|
MT_CngHelper.cpp
|
||||||
|
MT_AmrCodec.cpp
|
||||||
|
MT_EvsCodec.cpp
|
||||||
|
|
||||||
MT_Statistics.h
|
MT_Statistics.h
|
||||||
MT_WebRtc.h
|
MT_WebRtc.h
|
||||||
|
|
@ -38,48 +43,48 @@ set (SOURCES
|
||||||
MT_AudioReceiver.h
|
MT_AudioReceiver.h
|
||||||
MT_AudioCodec.h
|
MT_AudioCodec.h
|
||||||
MT_CngHelper.h
|
MT_CngHelper.h
|
||||||
|
MT_AmrCodec.h
|
||||||
|
MT_EvsCodec.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (USE_AMR_CODEC)
|
add_library(media_lib ${SOURCES})
|
||||||
message("Media: AMR NB and WB codecs will be included.")
|
|
||||||
add_definitions(-DUSE_AMR_CODEC)
|
|
||||||
set (SOURCES ${SOURCES} MT_AmrCodec.cpp MT_AmrCodec.h)
|
|
||||||
set (LIBS_CODEC)
|
set (LIBS_CODEC)
|
||||||
|
|
||||||
|
if (USE_AMR_CODEC)
|
||||||
|
include (${LIB_PLATFORM}/platform_libs.cmake)
|
||||||
|
message("Media: AMR NB and WB codecs will be included.")
|
||||||
|
target_compile_definitions(media_lib PUBLIC USE_AMR_CODEC)
|
||||||
|
list (APPEND LIBS_CODEC ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_EVS_CODEC)
|
if (USE_EVS_CODEC)
|
||||||
message("Media: EVS codec will be included.")
|
message("Media: EVS codec will be included.")
|
||||||
add_definitions (-DUSE_EVS_CODEC)
|
target_compile_definitions (media_lib PUBLIC USE_EVS_CODEC)
|
||||||
set (SOURCES ${SOURCES} MT_EvsCodec.cpp MT_EvsCodec.h)
|
list (APPEND LIBS_CODEC evs_codec)
|
||||||
set (LIBS_CODEC evs_codec)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_OPUS_CODEC)
|
if (USE_OPUS_CODEC)
|
||||||
message("Media: Opus codec will be included.")
|
message("Media: Opus codec will be included.")
|
||||||
add_definitions(-DUSE_OPUS_CODEC)
|
target_compile_definitions(media_lib PUBLIC USE_OPUS_CODEC)
|
||||||
|
list (APPEND LIBS_CODEC opus)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_SYSTEM MATCHES "Linux*" OR CMAKE_SYSTEM MATCHES "Darwin*")
|
||||||
if(CMAKE_SYSTEM MATCHES "Linux*")
|
target_compile_definitions(media_lib PUBLIC HAVE_NETINET_IN_H)
|
||||||
add_definitions(-DHAVE_NETINET_IN_H)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM MATCHES "Darwin*")
|
|
||||||
# OS X Specific flags
|
|
||||||
add_definitions(-DHAVE_NETINET_IN_H)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (CMAKE_SYSTEM MATCHES "Windows*")
|
if (CMAKE_SYSTEM MATCHES "Windows*")
|
||||||
# Windows Specific flags - MSVC expected
|
# Windows Specific flags - MSVC expected
|
||||||
add_definitions(
|
target_compile_definitions(media_lib PRIVATE
|
||||||
-D_CRT_SECURE_NO_WARNINGS
|
_CRT_SECURE_NO_WARNINGS
|
||||||
-DHAVE_WINSOCK2_H
|
HAVE_WINSOCK2_H
|
||||||
-D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
|
_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
|
||||||
-DUNICODE
|
UNICODE
|
||||||
-D_UNICODE )
|
_UNICODE )
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(media_lib ${SOURCES})
|
|
||||||
|
|
||||||
# Dependency on ice_stack - Linux build requires it
|
# Dependency on ice_stack - Linux build requires it
|
||||||
# Codec libraries as well
|
# Codec libraries as well
|
||||||
|
|
@ -94,9 +99,9 @@ target_include_directories(media_lib
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/include
|
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/include
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/crypto/include
|
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/crypto/include
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/webrtc
|
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/webrtc
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/opus/include/
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/resiprocate/
|
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/resiprocate/
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/
|
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs
|
||||||
|
${LIB_PLATFORM}/opus/include
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(media_lib
|
target_include_directories(media_lib
|
||||||
|
|
@ -107,8 +112,4 @@ target_include_directories(media_lib
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/basic_math
|
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/basic_math
|
||||||
)
|
)
|
||||||
|
|
||||||
if (USE_RESIP_INTEGRATION)
|
|
||||||
message("USE_RESIP_INTEGRATION is turned on!")
|
|
||||||
target_compile_definitions(media_lib PUBLIC -DUSE_RESIP_INTEGRATION)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,85 +5,30 @@
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
#include "../helper/HL_ByteBuffer.h"
|
||||||
#include "../helper/HL_Log.h"
|
#include "../helper/HL_Log.h"
|
||||||
#include "../helper/HL_IuUP.h"
|
#include "../helper/HL_IuUP.h"
|
||||||
|
#include "../helper/HL_Exception.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "AmrCodec"
|
#define LOG_SUBSYSTEM "AmrCodec"
|
||||||
using namespace MT;
|
using namespace MT;
|
||||||
|
|
||||||
|
|
||||||
static const uint8_t amr_block_size[16]={ 13, 14, 16, 18, 20, 21, 27, 32,
|
// Constant of AMR-NB frame lengths in bytes.
|
||||||
6 , 0 , 0 , 0 , 0 , 0 , 0 , 1 };
|
const uint8_t amrnb_framelen[9] =
|
||||||
|
{12, 13, 15, 17, 19, 20, 26, 31, 5};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant of AMR-NB frame lengths in bytes.
|
|
||||||
*/
|
|
||||||
const uint8_t amrnb_framelen[16] =
|
|
||||||
{12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant of AMR-NB frame lengths in bits.
|
|
||||||
*/
|
|
||||||
const uint16_t amrnb_framelenbits[9] =
|
const uint16_t amrnb_framelenbits[9] =
|
||||||
{95, 103, 118, 134, 148, 159, 204, 244, 39};
|
{95, 103, 118, 134, 148, 159, 204, 244, 39};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant of AMR-NB bitrates.
|
|
||||||
*/
|
|
||||||
const uint16_t amrnb_bitrates[8] =
|
|
||||||
{4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200};
|
|
||||||
|
|
||||||
/**
|
// Constant of AMR-WB frame lengths in bytes.
|
||||||
* Constant of AMR-WB frame lengths in bytes.
|
const uint8_t amrwb_framelen[10] =
|
||||||
*/
|
{17, 23, 32, 37, 40, 46, 50, 58, 60, 5 /* SID packet */};
|
||||||
const uint8_t amrwb_framelen[16] =
|
|
||||||
{17, 23, 32, 37, 40, 46, 50, 58, 60, 5, 0, 0, 0, 0, 0, 0};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant of AMR-WB frame lengths in bits.
|
|
||||||
*/
|
|
||||||
const uint16_t amrwb_framelenbits[10] =
|
const uint16_t amrwb_framelenbits[10] =
|
||||||
{132, 177, 253, 285, 317, 365, 397, 461, 477, 40};
|
{132, 177, 253, 285, 317, 365, 397, 461, 477, 40 /* SID packet */};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant of AMR-WB bitrates.
|
|
||||||
*/
|
|
||||||
const uint16_t amrwb_bitrates[9] =
|
|
||||||
{6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850};
|
|
||||||
|
|
||||||
|
|
||||||
// Helper routines
|
// Helper routines
|
||||||
|
|
||||||
/*static int8_t bitrateToMode(uint16_t bitrate)
|
|
||||||
{
|
|
||||||
int8_t mode = -1;
|
|
||||||
|
|
||||||
switch (bitrate)
|
|
||||||
{
|
|
||||||
// AMR NB
|
|
||||||
case 4750: mode = 0; break;
|
|
||||||
case 5150: mode = 1; break;
|
|
||||||
case 5900: mode = 2; break;
|
|
||||||
case 6700: mode = 3; break;
|
|
||||||
case 7400: mode = 4; break;
|
|
||||||
case 7950: mode = 5; break;
|
|
||||||
case 10200: mode = 6; break;
|
|
||||||
case 12200: mode = 7; break;
|
|
||||||
|
|
||||||
// AMRWB
|
|
||||||
case 6600: mode = 0; break;
|
|
||||||
case 8850: mode = 1; break;
|
|
||||||
case 12650: mode = 2; break;
|
|
||||||
case 14250: mode = 3; break;
|
|
||||||
case 15850: mode = 4; break;
|
|
||||||
case 18250: mode = 5; break;
|
|
||||||
case 19850: mode = 6; break;
|
|
||||||
case 23050: mode = 7; break;
|
|
||||||
case 23850: mode = 8; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mode;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
struct AmrPayloadInfo
|
struct AmrPayloadInfo
|
||||||
{
|
{
|
||||||
const uint8_t* mPayload;
|
const uint8_t* mPayload;
|
||||||
|
|
@ -119,12 +64,13 @@ struct AmrPayload
|
||||||
static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
||||||
{
|
{
|
||||||
AmrPayload result;
|
AmrPayload result;
|
||||||
|
|
||||||
// Do not skip packet by default; I suppose packet is good enough by default.
|
// Do not skip packet by default; I suppose packet is good enough by default.
|
||||||
result.mDiscardPacket = false;
|
result.mDiscardPacket = false;
|
||||||
|
|
||||||
// Wrap incoming data with ByteArray to make bit dequeuing easy
|
// Wrap incoming data with ByteArray to make bit dequeuing easy
|
||||||
ByteBuffer dataIn(input.mPayload, static_cast<size_t>(input.mPayloadLength));
|
ByteBuffer byte_reader(input.mPayload, static_cast<size_t>(input.mPayloadLength));
|
||||||
BitReader br(input.mPayload, static_cast<size_t>(input.mPayloadLength));
|
BitReader bit_reader (input.mPayload, static_cast<size_t>(input.mPayloadLength));
|
||||||
|
|
||||||
// In bandwidth-efficient mode, the payload header simply consists of a
|
// In bandwidth-efficient mode, the payload header simply consists of a
|
||||||
// 4-bit codec mode request:
|
// 4-bit codec mode request:
|
||||||
|
|
@ -135,16 +81,15 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
||||||
// AMR, as defined in Table 1a in [2], or 0-8 for AMR-WB, as defined
|
// AMR, as defined in Table 1a in [2], or 0-8 for AMR-WB, as defined
|
||||||
// in Table 1a in [4]. CMR value 15 indicates that no mode request
|
// in Table 1a in [4]. CMR value 15 indicates that no mode request
|
||||||
// is present, and other values are for future use.
|
// is present, and other values are for future use.
|
||||||
result.mCodeModeRequest = static_cast<uint8_t>(br.readBits(4));
|
result.mCodeModeRequest = static_cast<uint8_t>(bit_reader.readBits(4));
|
||||||
//ICELogMedia(<< "CMR: " << result.mCodeModeRequest);
|
|
||||||
|
|
||||||
// Consume extra 4 bits for octet aligned profile
|
// Consume extra 4 bits for octet aligned profile
|
||||||
if (input.mOctetAligned)
|
if (input.mOctetAligned)
|
||||||
br.readBits(4);
|
bit_reader.readBits(4);
|
||||||
|
|
||||||
// Skip interleaving flags for now for octet aligned mode
|
// Skip interleaving flags for now for octet aligned mode
|
||||||
if (input.mInterleaving && input.mOctetAligned)
|
if (input.mInterleaving && input.mOctetAligned)
|
||||||
br.readBits(8);
|
bit_reader.readBits(8);
|
||||||
|
|
||||||
// Silence codec mode constant (it differs for wideband and narrowband codecs)
|
// Silence codec mode constant (it differs for wideband and narrowband codecs)
|
||||||
uint8_t SID_FT = input.mWideband ? 9 : 8;
|
uint8_t SID_FT = input.mWideband ? 9 : 8;
|
||||||
|
|
@ -158,105 +103,121 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input)
|
||||||
// F (1 bit): If set to 1, indicates that this frame is followed by
|
// F (1 bit): If set to 1, indicates that this frame is followed by
|
||||||
// another speech frame in this payload; if set to 0, indicates that
|
// another speech frame in this payload; if set to 0, indicates that
|
||||||
// this frame is the last frame in this payload.
|
// this frame is the last frame in this payload.
|
||||||
F = br.readBit();
|
F = bit_reader.readBit();
|
||||||
|
|
||||||
// FT (4 bits): Frame type index, indicating either the AMR or AMR-WB
|
// FT (4 bits): Frame type index, indicating either the AMR or AMR-WB
|
||||||
// speech coding mode or comfort noise (SID) mode of the
|
// speech coding mode or comfort noise (SID) mode of the
|
||||||
// corresponding frame carried in this payload.
|
// corresponding frame carried in this payload.
|
||||||
FT = static_cast<uint8_t>(br.readBits(4));
|
FT = static_cast<uint8_t>(bit_reader.readBits(4));
|
||||||
|
|
||||||
// Q (1 bit): Frame quality indicator. If set to 0, indicates the
|
// Q (1 bit): Frame quality indicator. If set to 0, indicates the
|
||||||
// corresponding frame is severely damaged, and the receiver should
|
// corresponding frame is severely damaged, and the receiver should
|
||||||
// set the RX_TYPE (see [6]) to either SPEECH_BAD or SID_BAD
|
// set the RX_TYPE (see [6]) to either SPEECH_BAD or SID_BAD
|
||||||
// depending on the frame type (FT).
|
// depending on the frame type (FT).
|
||||||
Q = br.readBit();
|
Q = bit_reader.readBit();
|
||||||
|
|
||||||
|
// Handle padding for octet alignment
|
||||||
|
if (input.mOctetAligned)
|
||||||
|
bit_reader.readBits(2);
|
||||||
|
|
||||||
|
AmrFrame frame;
|
||||||
|
frame.mFrameType = FT;
|
||||||
|
frame.mSTI = 0;
|
||||||
|
frame.mMode = FT < SID_FT ? FT : 0xFF;
|
||||||
|
frame.mGoodQuality = Q == 1;
|
||||||
|
frame.mTimestamp = input.mCurrentTimestamp;
|
||||||
|
result.mFrames.push_back(frame);
|
||||||
|
input.mCurrentTimestamp += input.mWideband ? 320 : 160;
|
||||||
|
}
|
||||||
|
while (F != 0);
|
||||||
|
|
||||||
|
for (size_t frameIndex=0; frameIndex < result.mFrames.size() && !result.mDiscardPacket; frameIndex++)
|
||||||
|
{
|
||||||
|
AmrFrame& f = result.mFrames[frameIndex];
|
||||||
|
|
||||||
// If receiving a ToC entry with a FT value in the range 9-14 for AMR or
|
// If receiving a ToC entry with a FT value in the range 9-14 for AMR or
|
||||||
// 10-13 for AMR-WB, the whole packet SHOULD be discarded. This is to
|
// 10-13 for AMR-WB, the whole packet SHOULD be discarded. This is to
|
||||||
// avoid the loss of data synchronization in the depacketization
|
// avoid the loss of data synchronization in the depacketization
|
||||||
// process, which can result in a huge degradation in speech quality.
|
// process, which can result in a huge degradation in speech quality.
|
||||||
if ((input.mWideband && (FT >= 10 && FT <= 13)) ||
|
bool discard = input.mWideband ? (f.mFrameType >= 10 && f.mFrameType <= 13) : (f.mFrameType >= 9 && f.mFrameType <= 14);
|
||||||
(!input.mWideband && (FT >= 9 && FT <= 14)))
|
// discard |= input.mWideband ? f.mFrameType >= 14 : f.mFrameType >= 15;
|
||||||
|
if (discard)
|
||||||
{
|
{
|
||||||
ICELogMedia(<< "Discard corrupted packet");
|
|
||||||
// Discard bad packet
|
|
||||||
result.mDiscardPacket = true;
|
result.mDiscardPacket = true;
|
||||||
return result;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle padding for octet alignment
|
if (input.mWideband && f.mFrameType == 15)
|
||||||
if (input.mOctetAligned)
|
|
||||||
br.readBits(2);
|
|
||||||
|
|
||||||
AmrFrame frame;
|
|
||||||
frame.mFrameType = FT;
|
|
||||||
|
|
||||||
frame.mMode = FT < SID_FT ? FT : 0xFF;
|
|
||||||
frame.mGoodQuality = Q == 1;
|
|
||||||
frame.mTimestamp = input.mCurrentTimestamp;
|
|
||||||
|
|
||||||
result.mFrames.push_back(frame);
|
|
||||||
|
|
||||||
input.mCurrentTimestamp += input.mWideband ? 320 : 160;
|
|
||||||
}
|
|
||||||
while (F != 0);
|
|
||||||
|
|
||||||
for (size_t frameIndex=0; frameIndex < result.mFrames.size(); frameIndex++)
|
|
||||||
{
|
{
|
||||||
AmrFrame& frame = result.mFrames[frameIndex];
|
// DTX, no sense to decode the data
|
||||||
size_t bitsLength = input.mWideband ? amrwb_framelenbits[frame.mFrameType] : amrnb_framelenbits[frame.mFrameType];
|
continue;
|
||||||
size_t byteLength = input.mWideband ? amrwb_framelen[frame.mFrameType] : amrnb_framelen[frame.mFrameType];
|
}
|
||||||
ICELogMedia(<< "New AMR speech frame: frame type = " << FT <<
|
|
||||||
", mode = " << frame.mMode <<
|
if (input.mWideband && f.mFrameType == 14)
|
||||||
", timestamp = " << static_cast<int>(frame.mTimestamp) <<
|
{
|
||||||
", bits length = " << static_cast<int>(bitsLength) <<
|
// Speech lost code only
|
||||||
", byte length =" << static_cast<int>(byteLength) <<
|
continue;
|
||||||
", remaining packet length = " << static_cast<int>(dataIn.size()));
|
}
|
||||||
|
|
||||||
|
if (!f.mGoodQuality)
|
||||||
|
{
|
||||||
|
// Bad quality, frame is damaged
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bitsLength = input.mWideband ? amrwb_framelenbits[f.mFrameType] : amrnb_framelenbits[f.mFrameType];
|
||||||
|
size_t byteLength = input.mWideband ? amrwb_framelen[f.mFrameType] : amrnb_framelen[f.mFrameType];
|
||||||
|
|
||||||
if (bitsLength > 0)
|
if (bitsLength > 0)
|
||||||
{
|
{
|
||||||
if (input.mOctetAligned)
|
if (input.mOctetAligned)
|
||||||
{
|
{
|
||||||
if (dataIn.size() < byteLength)
|
if (byte_reader.size() < byteLength)
|
||||||
frame.mGoodQuality = false;
|
f.mGoodQuality = false;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// It is octet aligned scheme, so we are on byte boundary now
|
// It is octet aligned scheme, so we are on byte boundary now
|
||||||
size_t byteOffset = br.count() / 8;
|
size_t byteOffset = bit_reader.position() / 8;
|
||||||
|
|
||||||
// Copy data of AMR frame
|
// Copy data of AMR frame
|
||||||
frame.mData = std::make_shared<ByteBuffer>(input.mPayload + byteOffset, byteLength);
|
if (byteOffset + byteLength <= input.mPayloadLength)
|
||||||
|
{
|
||||||
|
f.mData = std::make_shared<ByteBuffer>();
|
||||||
|
f.mData->resize(byteLength + 1); // payload + header
|
||||||
|
memcpy(f.mData->mutableData() + 1, input.mPayload + byteOffset, byteLength);
|
||||||
|
|
||||||
|
// Add header for decoder
|
||||||
|
f.mData->mutableData()[0] = (f.mFrameType << 3) | (1 << 2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ICELogError(<< "Problem parsing AMR header: octet-aligned is set, available " << int(input.mPayloadLength - byteOffset)
|
||||||
|
<< " bytes but requested " << (int)byteLength);
|
||||||
|
result.mDiscardPacket = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Allocate place for copying
|
// Allocate place for copying
|
||||||
frame.mData = std::make_shared<ByteBuffer>();
|
f.mData = std::make_shared<ByteBuffer>();
|
||||||
frame.mData->resize(bitsLength / 8 + ((bitsLength % 8) ? 1 : 0) + 1);
|
f.mData->resize(bitsLength / 8 + ((bitsLength % 8) ? 1 : 0) + 1);
|
||||||
|
|
||||||
// Add header for decoder
|
// Add header for decoder
|
||||||
frame.mData->mutableData()[0] = (frame.mFrameType << 3) | (1 << 2);
|
f.mData->mutableData()[0] = (f.mFrameType << 3) | (1 << 2);
|
||||||
|
|
||||||
// Read bits
|
// Read bits
|
||||||
if (br.readBits(frame.mData->mutableData() + 1, bitsLength /*+ bitsLength*/ ) < (size_t)bitsLength)
|
if (bit_reader.readBits(f.mData->mutableData() + 1, bitsLength /*+ bitsLength*/ ) < (size_t)bitsLength)
|
||||||
frame.mGoodQuality = false;
|
f.mGoodQuality = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Padding bits are skipped
|
// Padding bits are skipped
|
||||||
|
|
||||||
/*if (br.count() / 8 != br.position() / 8 &&
|
|
||||||
br.count() / 8 != br.position() / 8 + 1)
|
|
||||||
throw std::runtime_error("Failed to parse AMR frame");*/
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static void predecodeAmrFrame(AmrFrame& frame, ByteBuffer& data)
|
|
||||||
{
|
|
||||||
// Data are already moved into
|
|
||||||
}*/
|
|
||||||
|
|
||||||
AmrNbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
|
AmrNbCodec::CodecFactory::CodecFactory(const AmrCodecConfig& config)
|
||||||
:mConfig(config)
|
:mConfig(config)
|
||||||
{
|
{
|
||||||
|
|
@ -278,12 +239,9 @@ int AmrNbCodec::CodecFactory::payloadType()
|
||||||
return mConfig.mPayloadType;
|
return mConfig.mPayloadType;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_RESIP_INTEGRATION
|
|
||||||
|
|
||||||
void AmrNbCodec::CodecFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
void AmrNbCodec::CodecFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int AmrNbCodec::CodecFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
int AmrNbCodec::CodecFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
|
|
@ -295,7 +253,6 @@ void AmrNbCodec::CodecFactory::create(CodecMap& codecs)
|
||||||
codecs[payloadType()] = std::shared_ptr<Codec>(new AmrNbCodec(mConfig));
|
codecs[payloadType()] = std::shared_ptr<Codec>(new AmrNbCodec(mConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
PCodec AmrNbCodec::CodecFactory::create()
|
PCodec AmrNbCodec::CodecFactory::create()
|
||||||
{
|
{
|
||||||
return PCodec(new AmrNbCodec(mConfig));
|
return PCodec(new AmrNbCodec(mConfig));
|
||||||
|
|
@ -378,6 +335,9 @@ int AmrNbCodec::encode(const void* input, int inputBytes, void* output, int outp
|
||||||
#define AMR_BITRATE_DTX 15
|
#define AMR_BITRATE_DTX 15
|
||||||
int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
|
int AmrNbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
|
||||||
{
|
{
|
||||||
|
if (mConfig.mOctetAligned)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (mConfig.mIuUP)
|
if (mConfig.mIuUP)
|
||||||
{
|
{
|
||||||
// Try to parse IuUP frame
|
// Try to parse IuUP frame
|
||||||
|
|
@ -485,7 +445,7 @@ int AmrNbCodec::plc(int lostFrames, void* output, int outputCapacity)
|
||||||
|
|
||||||
for (int i=0; i < lostFrames; i++)
|
for (int i=0; i < lostFrames; i++)
|
||||||
{
|
{
|
||||||
unsigned char buffer[32];
|
uint8_t buffer[32];
|
||||||
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
buffer[0] = (AMR_BITRATE_DTX << 3)|4;
|
||||||
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
|
Decoder_Interface_Decode(mDecoderCtx, buffer, dataOut, 0); // Handle missing data
|
||||||
dataOut += L_FRAME;
|
dataOut += L_FRAME;
|
||||||
|
|
@ -519,12 +479,8 @@ int AmrWbCodec::CodecFactory::payloadType()
|
||||||
return mConfig.mPayloadType;
|
return mConfig.mPayloadType;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_RESIP_INTEGRATION
|
|
||||||
|
|
||||||
void AmrWbCodec::CodecFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
void AmrWbCodec::CodecFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int AmrWbCodec::CodecFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
int AmrWbCodec::CodecFactory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
|
|
@ -536,19 +492,17 @@ void AmrWbCodec::CodecFactory::create(CodecMap& codecs)
|
||||||
codecs[payloadType()] = std::shared_ptr<Codec>(new AmrWbCodec(mConfig));
|
codecs[payloadType()] = std::shared_ptr<Codec>(new AmrWbCodec(mConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PCodec AmrWbCodec::CodecFactory::create()
|
PCodec AmrWbCodec::CodecFactory::create()
|
||||||
{
|
{
|
||||||
return PCodec(new AmrWbCodec(mConfig));
|
return PCodec(new AmrWbCodec(mConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AmrWbStatistics MT::GAmrWbStatistics;
|
||||||
|
|
||||||
AmrWbCodec::AmrWbCodec(const AmrCodecConfig& config)
|
AmrWbCodec::AmrWbCodec(const AmrCodecConfig& config)
|
||||||
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config),
|
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mConfig(config),
|
||||||
mSwitchCounter(0), mPreviousPacketLength(0)
|
mSwitchCounter(0), mPreviousPacketLength(0)
|
||||||
{
|
{
|
||||||
//mEncoderCtx = E_IF_init();
|
|
||||||
mDecoderCtx = D_IF_init();
|
mDecoderCtx = D_IF_init();
|
||||||
mCurrentDecoderTimestamp = 0;
|
mCurrentDecoderTimestamp = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -595,36 +549,16 @@ int AmrWbCodec::samplerate()
|
||||||
|
|
||||||
int AmrWbCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
|
int AmrWbCodec::encode(const void* input, int inputBytes, void* output, int outputCapacity)
|
||||||
{
|
{
|
||||||
if (inputBytes % pcmLength())
|
throw Exception(ERR_NOT_IMPLEMENTED);
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Declare the data input pointer
|
|
||||||
// const short *dataIn = (const short *)input;
|
|
||||||
|
|
||||||
// Declare the data output pointer
|
|
||||||
// unsigned char *dataOut = (unsigned char *)output;
|
|
||||||
|
|
||||||
// Find how much RTP frames will be generated
|
|
||||||
unsigned int frames = inputBytes / pcmLength();
|
|
||||||
|
|
||||||
// Generate frames
|
|
||||||
for (unsigned int i = 0; i < frames; i++)
|
|
||||||
{
|
|
||||||
// dataOut += Encoder_Interface_Encode(mEncoderCtx, Mode::MRDTX, dataIn, dataOut, 1);
|
|
||||||
// dataIn += pcmLength() / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return frames * rtpLength();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define L_FRAME 160
|
#define L_FRAME 160
|
||||||
#define AMR_BITRATE_DTX 15
|
#define AMR_BITRATE_DTX 15
|
||||||
int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
|
|
||||||
{
|
int AmrWbCodec::decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
if (mConfig.mIuUP)
|
|
||||||
{
|
{
|
||||||
IuUP::Frame frame;
|
IuUP::Frame frame;
|
||||||
if (!IuUP::parse2((const uint8_t*)input, inputBytes, frame))
|
if (!IuUP::parse2(input.data(), input.size(), frame))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
if (!frame.mHeaderCrcOk || !frame.mPayloadCrcOk)
|
||||||
|
|
@ -633,9 +567,6 @@ int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outp
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build first byte to help decoder
|
|
||||||
//ICELogDebug(<< "Decoding AMR frame length = " << frame.mPayloadSize);
|
|
||||||
|
|
||||||
// Reserve space
|
// Reserve space
|
||||||
ByteBuffer dataToDecode;
|
ByteBuffer dataToDecode;
|
||||||
dataToDecode.resize(1 + frame.mPayloadSize);
|
dataToDecode.resize(1 + frame.mPayloadSize);
|
||||||
|
|
@ -652,16 +583,17 @@ int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outp
|
||||||
|
|
||||||
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
|
dataToDecode.mutableData()[0] = (frameType << 3) | (1 << 2);
|
||||||
|
|
||||||
D_IF_decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output, 0);
|
D_IF_decode(mDecoderCtx, (const unsigned char*)dataToDecode.data(), (short*)output.data(), 0);
|
||||||
return pcmLength();
|
return pcmLength();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
int AmrWbCodec::decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output)
|
||||||
{
|
{
|
||||||
AmrPayloadInfo info;
|
AmrPayloadInfo info;
|
||||||
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
|
info.mCurrentTimestamp = mCurrentDecoderTimestamp;
|
||||||
info.mOctetAligned = mConfig.mOctetAligned;
|
info.mOctetAligned = mConfig.mOctetAligned;
|
||||||
info.mPayload = (const uint8_t*)input;
|
info.mPayload = input.data();
|
||||||
info.mPayloadLength = inputBytes;
|
info.mPayloadLength = input.size();
|
||||||
info.mWideband = true;
|
info.mWideband = true;
|
||||||
info.mInterleaving = false;
|
info.mInterleaving = false;
|
||||||
|
|
||||||
|
|
@ -672,6 +604,7 @@ int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outp
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
GAmrWbStatistics.mNonParsed++;
|
||||||
ICELogDebug(<< "Failed to decode AMR payload");
|
ICELogDebug(<< "Failed to decode AMR payload");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -680,13 +613,17 @@ int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outp
|
||||||
|
|
||||||
// Check if packet is corrupted
|
// Check if packet is corrupted
|
||||||
if (ap.mDiscardPacket)
|
if (ap.mDiscardPacket)
|
||||||
|
{
|
||||||
|
GAmrWbStatistics.mDiscarded++;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for output buffer capacity
|
// Check for output buffer capacity
|
||||||
if (outputCapacity < (int)ap.mFrames.size() * pcmLength())
|
if (output.size() < (int)ap.mFrames.size() * pcmLength())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
short* dataOut = (short*)output;
|
short* dataOut = (short*)output.data();
|
||||||
|
size_t dataOutSizeInBytes = 0;
|
||||||
for (AmrFrame& frame: ap.mFrames)
|
for (AmrFrame& frame: ap.mFrames)
|
||||||
{
|
{
|
||||||
memset(dataOut, 0, static_cast<size_t>(pcmLength()));
|
memset(dataOut, 0, static_cast<size_t>(pcmLength()));
|
||||||
|
|
@ -695,11 +632,22 @@ int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outp
|
||||||
{
|
{
|
||||||
D_IF_decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
|
D_IF_decode(mDecoderCtx, (const unsigned char*)frame.mData->data(), (short*)dataOut, 0);
|
||||||
dataOut += pcmLength() / 2;
|
dataOut += pcmLength() / 2;
|
||||||
|
dataOutSizeInBytes += pcmLength();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pcmLength() * ap.mFrames.size();
|
return dataOutSizeInBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int AmrWbCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
|
||||||
|
{
|
||||||
|
auto inputBuffer = std::span<const uint8_t>((uint8_t*)input, (size_t)inputBytes);
|
||||||
|
auto outputBuffer = std::span<uint8_t>((uint8_t*)output, (size_t)outputCapacity);
|
||||||
|
|
||||||
|
if (mConfig.mIuUP)
|
||||||
|
return decodeIuup(inputBuffer, outputBuffer);
|
||||||
|
else
|
||||||
|
return decodePlain(inputBuffer, outputBuffer);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -750,8 +698,6 @@ int GsmEfrCodec::GsmEfrFactory::payloadType()
|
||||||
return mPayloadType;
|
return mPayloadType;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_RESIP_INTEGRATION
|
|
||||||
|
|
||||||
void GsmEfrCodec::GsmEfrFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
void GsmEfrCodec::GsmEfrFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
@ -767,8 +713,6 @@ void GsmEfrCodec::GsmEfrFactory::create(CodecMap& codecs)
|
||||||
codecs[payloadType()] = std::shared_ptr<Codec>(new GsmEfrCodec(mIuUP));
|
codecs[payloadType()] = std::shared_ptr<Codec>(new GsmEfrCodec(mIuUP));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PCodec GsmEfrCodec::GsmEfrFactory::create()
|
PCodec GsmEfrCodec::GsmEfrFactory::create()
|
||||||
{
|
{
|
||||||
return PCodec(new GsmEfrCodec(mIuUP));
|
return PCodec(new GsmEfrCodec(mIuUP));
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
#ifndef MT_AMRCODEC_H
|
#ifndef MT_AMRCODEC_H
|
||||||
#define MT_AMRCODEC_H
|
#define MT_AMRCODEC_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "MT_Codec.h"
|
#include "MT_Codec.h"
|
||||||
#include "../helper/HL_Pointer.h"
|
#include "../helper/HL_Pointer.h"
|
||||||
|
|
||||||
|
|
@ -16,9 +18,9 @@ namespace MT
|
||||||
{
|
{
|
||||||
struct AmrCodecConfig
|
struct AmrCodecConfig
|
||||||
{
|
{
|
||||||
bool mIuUP;
|
bool mIuUP = false;
|
||||||
bool mOctetAligned;
|
bool mOctetAligned = false;
|
||||||
int mPayloadType;
|
int mPayloadType = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AmrNbCodec : public Codec
|
class AmrNbCodec : public Codec
|
||||||
|
|
@ -41,11 +43,10 @@ namespace MT
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int payloadType() override;
|
int payloadType() override;
|
||||||
|
|
||||||
#ifdef USE_RESIP_INTEGRATION
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
void create(CodecMap& codecs) override;
|
void create(CodecMap& codecs) override;
|
||||||
#endif
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -66,6 +67,13 @@ namespace MT
|
||||||
int getSwitchCounter() const;
|
int getSwitchCounter() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AmrWbStatistics
|
||||||
|
{
|
||||||
|
int mDiscarded = 0;
|
||||||
|
int mNonParsed = 0;
|
||||||
|
};
|
||||||
|
extern AmrWbStatistics GAmrWbStatistics;
|
||||||
|
|
||||||
class AmrWbCodec : public Codec
|
class AmrWbCodec : public Codec
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -76,6 +84,9 @@ namespace MT
|
||||||
int mSwitchCounter;
|
int mSwitchCounter;
|
||||||
int mPreviousPacketLength;
|
int mPreviousPacketLength;
|
||||||
|
|
||||||
|
int decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output);
|
||||||
|
int decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
class CodecFactory: public Factory
|
class CodecFactory: public Factory
|
||||||
{
|
{
|
||||||
|
|
@ -86,11 +97,10 @@ namespace MT
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int payloadType() override;
|
int payloadType() override;
|
||||||
|
|
||||||
#ifdef USE_RESIP_INTEGRATION
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
void create(CodecMap& codecs) override;
|
void create(CodecMap& codecs) override;
|
||||||
#endif
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -128,11 +138,10 @@ namespace MT
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int payloadType() override;
|
int payloadType() override;
|
||||||
|
|
||||||
#ifdef USE_RESIP_INTEGRATION
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
void create(CodecMap& codecs) override;
|
void create(CodecMap& codecs) override;
|
||||||
#endif
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
#define NOMINMAX
|
#define NOMINMAX
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "MT_AudioCodec.h"
|
#include "MT_AudioCodec.h"
|
||||||
#include "MT_CodecList.h"
|
#include "MT_CodecList.h"
|
||||||
#include "../helper/HL_Exception.h"
|
#include "../helper/HL_Exception.h"
|
||||||
|
|
@ -58,18 +58,14 @@ int G729Codec::G729Factory::payloadType()
|
||||||
return 18;
|
return 18;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void G729Codec::G729Factory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
void G729Codec::G729Factory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
int G729Codec::G729Factory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
int G729Codec::G729Factory::processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PCodec G729Codec::G729Factory::create()
|
PCodec G729Codec::G729Factory::create()
|
||||||
{
|
{
|
||||||
return std::make_shared<G729Codec>();
|
return std::make_shared<G729Codec>();
|
||||||
|
|
@ -241,7 +237,6 @@ OpusCodec::Params::Params()
|
||||||
mTargetBitrate = OPUS_TARGET_BITRATE;
|
mTargetBitrate = OPUS_TARGET_BITRATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
resip::Data OpusCodec::Params::toString() const
|
resip::Data OpusCodec::Params::toString() const
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
|
|
@ -324,7 +319,7 @@ void OpusCodec::Params::parse(const resip::Data ¶ms)
|
||||||
mPtime = strx::toInt(paramIter->mValue.c_str(), 20);
|
mPtime = strx::toInt(paramIter->mValue.c_str(), 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
OpusCodec::OpusFactory::OpusFactory(int samplerate, int channels, int ptype)
|
OpusCodec::OpusFactory::OpusFactory(int samplerate, int channels, int ptype)
|
||||||
{
|
{
|
||||||
mSamplerate = samplerate;
|
mSamplerate = samplerate;
|
||||||
|
|
@ -352,7 +347,6 @@ int OpusCodec::OpusFactory::payloadType()
|
||||||
return mPType;
|
return mPType;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void OpusCodec::OpusFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
void OpusCodec::OpusFactory::updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction)
|
||||||
{
|
{
|
||||||
// Put opus codec record
|
// Put opus codec record
|
||||||
|
|
@ -394,7 +388,6 @@ int OpusCodec::OpusFactory::processSdp(const resip::SdpContents::Session::Medium
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
PCodec OpusCodec::OpusFactory::create()
|
PCodec OpusCodec::OpusFactory::create()
|
||||||
{
|
{
|
||||||
|
|
@ -407,15 +400,13 @@ PCodec OpusCodec::OpusFactory::create()
|
||||||
}
|
}
|
||||||
|
|
||||||
OpusCodec::OpusCodec(int samplerate, int channels, int ptime)
|
OpusCodec::OpusCodec(int samplerate, int channels, int ptime)
|
||||||
:mEncoderCtx(NULL), mDecoderCtx(NULL), mChannels(channels), mPTime(ptime), mSamplerate(samplerate)
|
:mEncoderCtx(nullptr), mDecoderCtx(nullptr), mChannels(channels), mPTime(ptime), mSamplerate(samplerate), mDecoderChannels(0)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
mEncoderCtx = opus_encoder_create(48000, mChannels, OPUS_APPLICATION_VOIP, &status);
|
mEncoderCtx = opus_encoder_create(mSamplerate, mChannels, OPUS_APPLICATION_VOIP, &status);
|
||||||
if (OPUS_OK != opus_encoder_ctl(mEncoderCtx, OPUS_SET_COMPLEXITY(OPUS_CODEC_COMPLEXITY)))
|
if (OPUS_OK != opus_encoder_ctl(mEncoderCtx, OPUS_SET_COMPLEXITY(OPUS_CODEC_COMPLEXITY)))
|
||||||
ICELogError(<< "Failed to set Opus encoder complexity");
|
ICELogError(<< "Failed to set Opus encoder complexity");
|
||||||
//if (OPUS_OK != opus_encoder_ctl(mEncoderCtx, OPUS_SET_FORCE_CHANNELS(AUDIO_CHANNELS)))
|
// Decoder creation is postponed until first packet arriving (because it may use different channel number
|
||||||
// ICELogCritical(<<"Failed to set channel number in Opus encoder");
|
|
||||||
mDecoderCtx = opus_decoder_create(48000, mChannels, &status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusCodec::applyParams(const Params ¶ms)
|
void OpusCodec::applyParams(const Params ¶ms)
|
||||||
|
|
@ -433,7 +424,7 @@ void OpusCodec::applyParams(const Params ¶ms)
|
||||||
if (OPUS_OK != (error = opus_encoder_ctl(mEncoderCtx, OPUS_SET_PACKET_LOSS_PERC(params.mExpectedPacketLoss))))
|
if (OPUS_OK != (error = opus_encoder_ctl(mEncoderCtx, OPUS_SET_PACKET_LOSS_PERC(params.mExpectedPacketLoss))))
|
||||||
ICELogError(<< "Failed to (un)set expected packet loss. Error " << opus_strerror(error));
|
ICELogError(<< "Failed to (un)set expected packet loss. Error " << opus_strerror(error));
|
||||||
|
|
||||||
mDecodeResampler.start(channels(), 48000, mSamplerate);
|
// mDecodeResampler.start(channels(), 48000, mSamplerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
OpusCodec::~OpusCodec()
|
OpusCodec::~OpusCodec()
|
||||||
|
|
@ -441,13 +432,13 @@ OpusCodec::~OpusCodec()
|
||||||
if (mDecoderCtx)
|
if (mDecoderCtx)
|
||||||
{
|
{
|
||||||
opus_decoder_destroy(mDecoderCtx);
|
opus_decoder_destroy(mDecoderCtx);
|
||||||
mDecoderCtx = NULL;
|
mDecoderCtx = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEncoderCtx)
|
if (mEncoderCtx)
|
||||||
{
|
{
|
||||||
opus_encoder_destroy(mEncoderCtx);
|
opus_encoder_destroy(mEncoderCtx);
|
||||||
mEncoderCtx = NULL;
|
mEncoderCtx = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,42 +484,113 @@ int OpusCodec::encode(const void* input, int inputBytes, void* output, int outpu
|
||||||
|
|
||||||
int OpusCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
|
int OpusCodec::decode(const void* input, int inputBytes, void* output, int outputCapacity)
|
||||||
{
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
// Examine the number of channels available in incoming packet
|
||||||
int nr_of_channels = opus_packet_get_nb_channels((const unsigned char *) input);
|
int nr_of_channels = opus_packet_get_nb_channels((const unsigned char *) input);
|
||||||
assert(nr_of_channels == channels());
|
|
||||||
|
|
||||||
int nr_of_frames = opus_decoder_get_nb_samples(mDecoderCtx, (const unsigned char*)input, inputBytes);
|
// Recreate decoder if needed
|
||||||
if (nr_of_frames > 0)
|
if (mDecoderChannels != nr_of_channels)
|
||||||
{
|
{
|
||||||
// Send number of bytes for input and number of samples for output
|
if (mDecoderCtx)
|
||||||
int capacity = nr_of_frames * sizeof(opus_int16) * channels();
|
{
|
||||||
|
opus_decoder_destroy(mDecoderCtx);
|
||||||
|
mDecoderCtx = nullptr;
|
||||||
|
}
|
||||||
|
mDecoderChannels = nr_of_channels;
|
||||||
|
}
|
||||||
|
|
||||||
// Dangerous !
|
if (!mDecoderCtx)
|
||||||
opus_int16* buffer = (opus_int16*)alloca(capacity);
|
{
|
||||||
int decoded = opus_decode(mDecoderCtx, (const unsigned char*)input, inputBytes, (opus_int16*)buffer, capacity / (sizeof(short) * channels()), 0 /* FEC decoding is for lost packets */);
|
int status = 0;
|
||||||
|
mDecoderCtx = opus_decoder_create(mSamplerate, mDecoderChannels, &status);
|
||||||
|
if (status)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nr_of_frames = opus_decoder_get_nb_samples(mDecoderCtx, (const unsigned char *) input,
|
||||||
|
inputBytes);
|
||||||
|
if (nr_of_frames <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// We support stereo and mono here.
|
||||||
|
int buffer_capacity = nr_of_frames * sizeof(opus_int16) * nr_of_channels;
|
||||||
|
opus_int16 *buffer_decode = (opus_int16 *)alloca(buffer_capacity);
|
||||||
|
int decoded = opus_decode(mDecoderCtx,
|
||||||
|
reinterpret_cast<const unsigned char *>(input), inputBytes,
|
||||||
|
buffer_decode, nr_of_frames, 0);
|
||||||
if (decoded < 0)
|
if (decoded < 0)
|
||||||
return 0;
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Resample 48000 to requested samplerate
|
ICELogCritical(<< "opus_decode() returned " << decoded);
|
||||||
size_t processed = 0;
|
|
||||||
return mDecodeResampler.processBuffer(buffer, decoded * sizeof(short) * channels(), processed, output, outputCapacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int OpusCodec::plc(int lostFrames, void* output, int outputCapacity)
|
opus_int16 *buffer_stereo = nullptr;
|
||||||
|
int buffer_stereo_capacity = buffer_capacity * 2;
|
||||||
|
|
||||||
|
switch (nr_of_channels) {
|
||||||
|
case 1:
|
||||||
|
// Convert to stereo before
|
||||||
|
buffer_stereo = (opus_int16 *) alloca(buffer_stereo_capacity);
|
||||||
|
for (int i = 0; i < nr_of_frames; i++) {
|
||||||
|
buffer_stereo[i * 2 + 1] = buffer_decode[i];
|
||||||
|
buffer_stereo[i * 2] = buffer_decode[i];
|
||||||
|
}
|
||||||
|
assert(buffer_stereo_capacity <= outputCapacity);
|
||||||
|
memcpy(output, buffer_stereo, buffer_stereo_capacity);
|
||||||
|
result = buffer_stereo_capacity;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
assert(buffer_capacity <= outputCapacity);
|
||||||
|
memcpy(output, buffer_decode, buffer_capacity);
|
||||||
|
result = buffer_capacity;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpusCodec::plc(int lostPackets, void* output, int outputCapacity)
|
||||||
{
|
{
|
||||||
int nrOfSamplesInFrame = pcmLength() / (sizeof(short) * channels());
|
// Find how much frames do we need to produce and prefill it with silence
|
||||||
|
int frames_per_packet = (int)pcmLength() / (sizeof(opus_int16) * channels());
|
||||||
memset(output, 0, outputCapacity);
|
memset(output, 0, outputCapacity);
|
||||||
|
|
||||||
int nr_of_decoded_samples = 0;
|
// Use this pointer as output
|
||||||
for (int i=0; i<lostFrames; i++)
|
opus_int16* data_output = reinterpret_cast<opus_int16*>(output);
|
||||||
|
|
||||||
|
int nr_of_decoded_frames = 0;
|
||||||
|
|
||||||
|
// Buffer for single lost frame
|
||||||
|
opus_int16* buffer_plc = (opus_int16*)alloca(frames_per_packet * mDecoderChannels * sizeof(opus_int16));
|
||||||
|
for (int i=0; i<lostPackets; i++)
|
||||||
{
|
{
|
||||||
nr_of_decoded_samples += opus_decode(mDecoderCtx, nullptr, 0, (opus_int16*)output + nrOfSamplesInFrame * i, nrOfSamplesInFrame, 0);
|
nr_of_decoded_frames = opus_decode(mDecoderCtx, nullptr, 0, buffer_plc, frames_per_packet, 0);
|
||||||
|
assert(nr_of_decoded_frames == frames_per_packet);
|
||||||
|
switch (mDecoderChannels)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
// Convert mono to stereo
|
||||||
|
for (int i=0; i < nr_of_decoded_frames; i++)
|
||||||
|
{
|
||||||
|
data_output[i * 2] = buffer_plc[i];
|
||||||
|
data_output[i * 2 + 1] = buffer_plc[i+1];
|
||||||
}
|
}
|
||||||
return nr_of_decoded_samples ? lostFrames * pcmLength() : 0;
|
data_output += frames_per_packet * mChannels;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// Just copy data
|
||||||
|
memcpy(data_output, buffer_plc, frames_per_packet * sizeof(opus_int16) * mDecoderChannels);
|
||||||
|
data_output += frames_per_packet * mChannels;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ((char*)data_output - (char*)output) * sizeof(opus_int16);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -652,7 +714,6 @@ PCodec IlbcCodec::IlbcFactory::create()
|
||||||
return PCodec(new IlbcCodec(mPtime));
|
return PCodec(new IlbcCodec(mPtime));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void IlbcCodec::IlbcFactory::create(CodecMap& codecs)
|
void IlbcCodec::IlbcFactory::create(CodecMap& codecs)
|
||||||
{
|
{
|
||||||
codecs[mPType20ms] = PCodec(create());
|
codecs[mPType20ms] = PCodec(create());
|
||||||
|
|
@ -714,8 +775,6 @@ int IlbcCodec::IlbcFactory::processSdp(const resip::SdpContents::Session::Medium
|
||||||
return pt;
|
return pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// --- IsacCodec(s) ---
|
// --- IsacCodec(s) ---
|
||||||
#define ISAC_CODEC_NAME "ISAC"
|
#define ISAC_CODEC_NAME "ISAC"
|
||||||
|
|
||||||
|
|
@ -1093,8 +1152,8 @@ int GsmCodec::plc(int lostFrames, void* output, int outputCapacity)
|
||||||
|
|
||||||
G722Codec::G722Codec()
|
G722Codec::G722Codec()
|
||||||
{
|
{
|
||||||
mEncoder = g722_encode_init(NULL, 64000, 0);
|
mEncoder = g722_encode_init(nullptr, 64000, 0);
|
||||||
mDecoder = g722_decode_init(NULL, 64000, 0);
|
mDecoder = g722_decode_init(nullptr, 64000, 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef __AUDIO_CODEC_H
|
#ifndef __AUDIO_CODEC_H
|
||||||
#define __AUDIO_CODEC_H
|
#define __AUDIO_CODEC_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "MT_Codec.h"
|
#include "MT_Codec.h"
|
||||||
#include "../audio/Audio_Resampler.h"
|
#include "../audio/Audio_Resampler.h"
|
||||||
|
|
@ -46,10 +46,9 @@ public:
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int payloadType() override;
|
int payloadType() override;
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
#endif
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
};
|
};
|
||||||
G729Codec();
|
G729Codec();
|
||||||
|
|
@ -73,7 +72,8 @@ protected:
|
||||||
OpusEncoder *mEncoderCtx;
|
OpusEncoder *mEncoderCtx;
|
||||||
OpusDecoder *mDecoderCtx;
|
OpusDecoder *mDecoderCtx;
|
||||||
int mPTime, mSamplerate, mChannels;
|
int mPTime, mSamplerate, mChannels;
|
||||||
Audio::SpeexResampler mDecodeResampler;
|
// Audio::SpeexResampler mDecodeResampler;
|
||||||
|
int mDecoderChannels;
|
||||||
public:
|
public:
|
||||||
struct Params
|
struct Params
|
||||||
{
|
{
|
||||||
|
|
@ -81,10 +81,8 @@ public:
|
||||||
int mPtime, mTargetBitrate, mExpectedPacketLoss;
|
int mPtime, mTargetBitrate, mExpectedPacketLoss;
|
||||||
|
|
||||||
Params();
|
Params();
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
resip::Data toString() const;
|
resip::Data toString() const;
|
||||||
void parse(const resip::Data& params);
|
void parse(const resip::Data& params);
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class OpusFactory: public Factory
|
class OpusFactory: public Factory
|
||||||
|
|
@ -102,10 +100,8 @@ public:
|
||||||
int channels() override;
|
int channels() override;
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int payloadType() override;
|
int payloadType() override;
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
|
||||||
#endif
|
|
||||||
PCodec create() override;
|
PCodec create() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -145,11 +141,9 @@ public:
|
||||||
const char* name();
|
const char* name();
|
||||||
int samplerate();
|
int samplerate();
|
||||||
int payloadType();
|
int payloadType();
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
||||||
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
||||||
void create(CodecMap& codecs);
|
void create(CodecMap& codecs);
|
||||||
#endif
|
|
||||||
PCodec create();
|
PCodec create();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#if defined(TARGET_WIN) && !defined(NOMINMAX)
|
||||||
# define NOMINMAX
|
# define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "MT_AudioReceiver.h"
|
#include "MT_AudioReceiver.h"
|
||||||
#include "MT_AudioCodec.h"
|
#include "MT_AudioCodec.h"
|
||||||
#include "MT_CngHelper.h"
|
#include "MT_CngHelper.h"
|
||||||
|
|
@ -14,6 +16,7 @@
|
||||||
#include "../audio/Audio_Interface.h"
|
#include "../audio/Audio_Interface.h"
|
||||||
#include "../audio/Audio_Resampler.h"
|
#include "../audio/Audio_Resampler.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#if !defined(TARGET_ANDROID) && !defined(TARGET_OPENWRT) && !defined(TARGET_WIN) && !defined(TARGET_RPI) && defined(USE_AMR_CODEC)
|
#if !defined(TARGET_ANDROID) && !defined(TARGET_OPENWRT) && !defined(TARGET_WIN) && !defined(TARGET_RPI) && defined(USE_AMR_CODEC)
|
||||||
# include "MT_AmrCodec.h"
|
# include "MT_AmrCodec.h"
|
||||||
|
|
@ -62,6 +65,8 @@ std::vector<short>& RtpBuffer::Packet::pcm()
|
||||||
RtpBuffer::RtpBuffer(Statistics& stat)
|
RtpBuffer::RtpBuffer(Statistics& stat)
|
||||||
:mStat(stat)
|
:mStat(stat)
|
||||||
{
|
{
|
||||||
|
if (mStat.mPacketLoss)
|
||||||
|
std::cout << "Warning: packet loss is not zero" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
RtpBuffer::~RtpBuffer()
|
RtpBuffer::~RtpBuffer()
|
||||||
|
|
@ -117,7 +122,6 @@ std::shared_ptr<RtpBuffer::Packet> RtpBuffer::add(std::shared_ptr<jrtplib::RTPPa
|
||||||
|
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
|
|
||||||
|
|
||||||
// Update statistics
|
// Update statistics
|
||||||
if (mLastAddTime == 0.0)
|
if (mLastAddTime == 0.0)
|
||||||
mLastAddTime = now_ms();
|
mLastAddTime = now_ms();
|
||||||
|
|
@ -197,13 +201,14 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
||||||
// See if there is enough information in buffer
|
// See if there is enough information in buffer
|
||||||
int total = findTimelength();
|
int total = findTimelength();
|
||||||
|
|
||||||
while (total > mHigh && mPacketList.size())
|
while (total > mHigh && mPacketList.size() && 0 != mHigh)
|
||||||
{
|
{
|
||||||
ICELogMedia( << "Dropping RTP packets from jitter buffer");
|
ICELogMedia( << "Dropping RTP packets from jitter buffer");
|
||||||
total -= mPacketList.front()->timelength();
|
total -= mPacketList.front()->timelength();
|
||||||
|
|
||||||
// Save it as last packet however - to not confuse loss packet counter
|
// Save it as last packet however - to not confuse loss packet counter
|
||||||
mFetchedPacket = mPacketList.front();
|
mFetchedPacket = mPacketList.front();
|
||||||
|
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
// Erase from packet list
|
// Erase from packet list
|
||||||
mPacketList.erase(mPacketList.begin());
|
mPacketList.erase(mPacketList.begin());
|
||||||
|
|
@ -213,47 +218,48 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total < mLow)
|
if (total < mLow)
|
||||||
|
{
|
||||||
|
// Still not prebuffered
|
||||||
result = FetchResult::NoPacket;
|
result = FetchResult::NoPacket;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Did we fetch any packet before ?
|
||||||
bool is_fetched_packet = mFetchedPacket.get() != nullptr;
|
bool is_fetched_packet = mFetchedPacket.get() != nullptr;
|
||||||
if (is_fetched_packet)
|
if (is_fetched_packet)
|
||||||
is_fetched_packet &= mFetchedPacket->rtp().get() != nullptr;
|
is_fetched_packet &= mFetchedPacket->rtp().get() != nullptr;
|
||||||
|
|
||||||
if (is_fetched_packet)
|
if (mLastSeqno.has_value())
|
||||||
{
|
{
|
||||||
if (mPacketList.empty())
|
if (mPacketList.empty())
|
||||||
{
|
{
|
||||||
result = FetchResult::NoPacket;
|
result = FetchResult::NoPacket;
|
||||||
mStat.mPacketLoss++;
|
// Don't increase counter of lost packets here; maybe it is DTX
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Current sequence number ?
|
// Current sequence number ?
|
||||||
unsigned seqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
uint32_t seqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
// Gap between new packet and previous on
|
// Gap between new packet and previous on
|
||||||
int gap = (int64_t)seqno - (int64_t)mFetchedPacket->rtp()->GetExtendedSequenceNumber() - 1;
|
int gap = (int64_t)seqno - (int64_t)*mLastSeqno - 1;
|
||||||
gap = std::min(gap, 127);
|
gap = std::min(gap, 127);
|
||||||
if (gap > 0 && mPacketList.empty())
|
if (gap > 0)
|
||||||
{
|
{
|
||||||
|
// std::cout << "Increase the packet loss for SSRC " << std::hex << mSsrc << std::endl;
|
||||||
|
mStat.mPacketLoss++;
|
||||||
|
//mStat.mLoss[gap]++;
|
||||||
|
mLastSeqno = *mLastSeqno + 1;
|
||||||
result = FetchResult::Gap;
|
result = FetchResult::Gap;
|
||||||
mStat.mPacketLoss += gap;
|
|
||||||
mStat.mLoss[gap]++;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (gap > 0)
|
|
||||||
{
|
|
||||||
mStat.mPacketLoss += gap;
|
|
||||||
mStat.mLoss[gap]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = FetchResult::RegularPacket;
|
result = FetchResult::RegularPacket;
|
||||||
rl.push_back(mPacketList.front());
|
rl.push_back(mPacketList.front());
|
||||||
|
|
||||||
// Save last returned normal packet
|
// Save last returned normal packet
|
||||||
mFetchedPacket = mPacketList.front();
|
mFetchedPacket = mPacketList.front();
|
||||||
|
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
// Remove returned packet from the list
|
// Remove returned packet from the list
|
||||||
mPacketList.erase(mPacketList.begin());
|
mPacketList.erase(mPacketList.begin());
|
||||||
|
|
@ -263,7 +269,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// See if prebuffer limit is reached
|
// See if prebuffer limit is reached
|
||||||
if (findTimelength() >= mPrebuffer)
|
if (findTimelength() >= mPrebuffer && !mPacketList.empty())
|
||||||
{
|
{
|
||||||
// Normal packet will be returned
|
// Normal packet will be returned
|
||||||
result = FetchResult::RegularPacket;
|
result = FetchResult::RegularPacket;
|
||||||
|
|
@ -273,6 +279,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch(ResultList& rl)
|
||||||
|
|
||||||
// Remember returned packet
|
// Remember returned packet
|
||||||
mFetchedPacket = mPacketList.front();
|
mFetchedPacket = mPacketList.front();
|
||||||
|
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber();
|
||||||
|
|
||||||
// Remove returned packet from buffer list
|
// Remove returned packet from buffer list
|
||||||
mPacketList.erase(mPacketList.begin());
|
mPacketList.erase(mPacketList.begin());
|
||||||
|
|
@ -331,6 +338,7 @@ AudioReceiver::AudioReceiver(const CodecList::Settings& settings, MT::Statistics
|
||||||
mResampler48.start(AUDIO_CHANNELS, 48000, AUDIO_SAMPLERATE);
|
mResampler48.start(AUDIO_CHANNELS, 48000, AUDIO_SAMPLERATE);
|
||||||
|
|
||||||
// Init codecs
|
// Init codecs
|
||||||
|
mCodecList.setSettings(settings);
|
||||||
mCodecList.fillCodecMap(mCodecMap);
|
mCodecList.fillCodecMap(mCodecMap);
|
||||||
|
|
||||||
#if defined(DUMP_DECODED)
|
#if defined(DUMP_DECODED)
|
||||||
|
|
@ -355,11 +363,17 @@ void AudioReceiver::setCodecSettings(const CodecList::Settings& codecSettings)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mCodecSettings = codecSettings;
|
mCodecSettings = codecSettings;
|
||||||
mCodecMap.clear();
|
mCodecList.setSettings(mCodecSettings); // This builds factory list with proper payload types according to payload types in settings
|
||||||
mCodecList.setSettings(mCodecSettings);
|
|
||||||
|
// Rebuild codec map from factory list
|
||||||
mCodecList.fillCodecMap(mCodecMap);
|
mCodecList.fillCodecMap(mCodecMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CodecList::Settings& AudioReceiver::getCodecSettings()
|
||||||
|
{
|
||||||
|
return mCodecSettings;
|
||||||
|
}
|
||||||
|
|
||||||
size_t decode_packet(Codec& codec, RTPPacket& p, void* output_buffer, size_t output_capacity)
|
size_t decode_packet(Codec& codec, RTPPacket& p, void* output_buffer, size_t output_capacity)
|
||||||
{
|
{
|
||||||
// How much data was produced
|
// How much data was produced
|
||||||
|
|
@ -396,46 +410,56 @@ size_t decode_packet(Codec& codec, RTPPacket& p, void* output_buffer, size_t out
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** codec)
|
bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** detectedCodec)
|
||||||
{
|
{
|
||||||
|
// Estimate time length
|
||||||
|
int time_length = 0,
|
||||||
|
samplerate = 8000,
|
||||||
|
payloadLength = p->GetPayloadLength(),
|
||||||
|
ptype = p->GetPayloadType();
|
||||||
|
|
||||||
// ICELogInfo(<< "Adding packet No " << p->GetSequenceNumber());
|
// ICELogInfo(<< "Adding packet No " << p->GetSequenceNumber());
|
||||||
// Increase codec counter
|
// Increase codec counter
|
||||||
mStat.mCodecCount[p->GetPayloadType()]++;
|
mStat.mCodecCount[ptype]++;
|
||||||
|
|
||||||
// Check if codec can be handled
|
// Check if codec can be handled
|
||||||
CodecMap::iterator codecIter = mCodecMap.find(p->GetPayloadType());
|
Codec* codec = nullptr;
|
||||||
|
CodecMap::iterator codecIter = mCodecMap.find(ptype);
|
||||||
if (codecIter == mCodecMap.end())
|
if (codecIter == mCodecMap.end())
|
||||||
{
|
{
|
||||||
ICELogMedia(<< "Cannot find codec in available codecs");
|
time_length = 10;
|
||||||
return false; // Reject packet with unknown payload type
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// Check if codec is created actually
|
{
|
||||||
|
// Check if codec is creating lazily
|
||||||
if (!codecIter->second)
|
if (!codecIter->second)
|
||||||
{
|
{
|
||||||
// Look for ptype
|
codecIter->second = mCodecList.createCodecByPayloadType(ptype);
|
||||||
for (int codecIndex = 0; codecIndex < mCodecList.count(); codecIndex++)
|
|
||||||
if (mCodecList.codecAt(codecIndex).payloadType() == p->GetPayloadType())
|
|
||||||
codecIter->second = mCodecList.codecAt(codecIndex).create();
|
|
||||||
}
|
}
|
||||||
|
codec = codecIter->second.get();
|
||||||
|
|
||||||
// Return pointer to codec if needed.get()
|
// Return pointer to codec if needed.get()
|
||||||
if (codec)
|
if (detectedCodec)
|
||||||
*codec = codecIter->second.get();
|
*detectedCodec = codec;
|
||||||
|
|
||||||
if (mStat.mCodecName.empty())
|
if (mStat.mCodecName.empty() && codec)
|
||||||
mStat.mCodecName = codecIter->second->name();
|
mStat.mCodecName = codec->name();
|
||||||
|
|
||||||
// Estimate time length
|
|
||||||
int time_length = 0, payloadLength = p->GetPayloadLength(), ptype = p->GetPayloadType();
|
|
||||||
|
|
||||||
if (!codecIter->second->rtpLength())
|
if (!codec)
|
||||||
time_length = codecIter->second->frameTime();
|
time_length = 10;
|
||||||
else
|
else
|
||||||
time_length = lround(double(payloadLength) / codecIter->second->rtpLength() * codecIter->second->frameTime());
|
if (!codec->rtpLength())
|
||||||
|
time_length = codec->frameTime();
|
||||||
|
else
|
||||||
|
time_length = lround(double(payloadLength) / codec->rtpLength() * codec->frameTime());
|
||||||
|
|
||||||
|
if (codec)
|
||||||
|
samplerate = codec->samplerate();
|
||||||
|
}
|
||||||
|
|
||||||
// Process jitter
|
// Process jitter
|
||||||
mJitterStats.process(p.get(), codecIter->second->samplerate());
|
mJitterStats.process(p.get(), samplerate);
|
||||||
mStat.mJitter = static_cast<float>(mJitterStats.get());
|
mStat.mJitter = static_cast<float>(mJitterStats.get());
|
||||||
|
|
||||||
// Check if packet is CNG
|
// Check if packet is CNG
|
||||||
|
|
@ -447,23 +471,26 @@ bool AudioReceiver::add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** co
|
||||||
{
|
{
|
||||||
// It will cause statistics to report about bad RTP packet
|
// It will cause statistics to report about bad RTP packet
|
||||||
// I have to replay last packet payload here to avoid report about lost packet
|
// I have to replay last packet payload here to avoid report about lost packet
|
||||||
mBuffer.add(p, time_length, codecIter->second->samplerate());
|
mBuffer.add(p, time_length, samplerate);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Queue packet to buffer
|
// Queue packet to buffer
|
||||||
auto packet = mBuffer.add(p, time_length, codecIter->second->samplerate()).get();
|
auto packet = mBuffer.add(p, time_length, samplerate).get();
|
||||||
|
|
||||||
if (packet)
|
if (packet)
|
||||||
{
|
{
|
||||||
// Check if early decoding configured
|
// Check if early decoding configured
|
||||||
if (mEarlyDecode && *codec)
|
if (mEarlyDecode && codec)
|
||||||
{
|
{
|
||||||
// Move data to packet buffer
|
// Move data to packet buffer
|
||||||
size_t available = decode_packet(**codec, *p, mDecodedFrame, sizeof mDecodedFrame);
|
size_t available = decode_packet(*codec, *p, mDecodedFrame, sizeof mDecodedFrame);
|
||||||
|
if (available > 0)
|
||||||
|
{
|
||||||
packet->pcm().resize(available / 2);
|
packet->pcm().resize(available / 2);
|
||||||
memcpy(packet->pcm().data(), mDecodedFrame, available / 2);
|
memcpy(packet->pcm().data(), mDecodedFrame, available / 2);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -513,7 +540,17 @@ AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, i
|
||||||
if (options & DecodeOptions_SkipDecode)
|
if (options & DecodeOptions_SkipDecode)
|
||||||
mDecodedLength = 0;
|
mDecodedLength = 0;
|
||||||
else
|
else
|
||||||
|
{
|
||||||
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
|
mDecodedLength = mCodec->plc(mFrameCount, mDecodedFrame, sizeof mDecodedFrame);
|
||||||
|
if (!mDecodedLength)
|
||||||
|
{
|
||||||
|
// PLC is not support or failed
|
||||||
|
// So substitute the silence
|
||||||
|
size_t nr_of_samples = mCodec->frameTime() * mCodec->samplerate() / 1000 * sizeof(short);
|
||||||
|
mDecodedLength = nr_of_samples * sizeof(short);
|
||||||
|
memset(mDecodedFrame, 0, mDecodedLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDecodedLength)
|
if (mDecodedLength)
|
||||||
|
|
@ -530,7 +567,6 @@ AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, i
|
||||||
|
|
||||||
case RtpBuffer::FetchResult::RegularPacket:
|
case RtpBuffer::FetchResult::RegularPacket:
|
||||||
mFailedCount = 0;
|
mFailedCount = 0;
|
||||||
|
|
||||||
for (std::shared_ptr<RtpBuffer::Packet>& p: rl)
|
for (std::shared_ptr<RtpBuffer::Packet>& p: rl)
|
||||||
{
|
{
|
||||||
assert(p);
|
assert(p);
|
||||||
|
|
@ -637,8 +673,7 @@ AudioReceiver::DecodeResult AudioReceiver::getAudio(Audio::DataWindow& output, i
|
||||||
// Decode frame by frame
|
// Decode frame by frame
|
||||||
mDecodedLength = mCodec->decode(p->rtp()->GetPayloadData() + i * mCodec->rtpLength(),
|
mDecodedLength = mCodec->decode(p->rtp()->GetPayloadData() + i * mCodec->rtpLength(),
|
||||||
frameLength, mDecodedFrame, sizeof mDecodedFrame);
|
frameLength, mDecodedFrame, sizeof mDecodedFrame);
|
||||||
// mDecodedLength = 3840; // Opus 20 ms stereo
|
if (mDecodedLength > 0)
|
||||||
if (mDecodedLength)
|
|
||||||
processDecoded(output, options);
|
processDecoded(output, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
@ -8,22 +8,16 @@
|
||||||
|
|
||||||
#include "MT_Stream.h"
|
#include "MT_Stream.h"
|
||||||
#include "MT_CodecList.h"
|
#include "MT_CodecList.h"
|
||||||
#include "MT_AudioCodec.h"
|
|
||||||
#include "MT_CngHelper.h"
|
#include "MT_CngHelper.h"
|
||||||
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
#include "../helper/HL_Sync.h"
|
||||||
#include "../helper/HL_Optional.hpp"
|
|
||||||
|
|
||||||
#include "jrtplib/src/rtppacket.h"
|
#include "jrtplib/src/rtppacket.h"
|
||||||
#include "jrtplib/src/rtcppacket.h"
|
|
||||||
#include "jrtplib/src/rtpsourcedata.h"
|
#include "jrtplib/src/rtpsourcedata.h"
|
||||||
#include "../audio/Audio_DataWindow.h"
|
#include "../audio/Audio_DataWindow.h"
|
||||||
#include "../audio/Audio_Resampler.h"
|
#include "../audio/Audio_Resampler.h"
|
||||||
|
|
||||||
#include <map>
|
#include <optional>
|
||||||
|
|
||||||
// #define DUMP_DECODED
|
|
||||||
|
|
||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
|
|
@ -101,6 +95,7 @@ namespace MT
|
||||||
bool mFirstPacketWillGo = true;
|
bool mFirstPacketWillGo = true;
|
||||||
jrtplib::RTPSourceStats mRtpStats;
|
jrtplib::RTPSourceStats mRtpStats;
|
||||||
std::shared_ptr<Packet> mFetchedPacket;
|
std::shared_ptr<Packet> mFetchedPacket;
|
||||||
|
std::optional<uint32_t> mLastSeqno;
|
||||||
|
|
||||||
// To calculate average interval between packet add. It is close to jitter but more useful in debugging.
|
// To calculate average interval between packet add. It is close to jitter but more useful in debugging.
|
||||||
float mLastAddTime = 0.0;
|
float mLastAddTime = 0.0;
|
||||||
|
|
@ -124,7 +119,7 @@ namespace MT
|
||||||
|
|
||||||
// Update codec settings
|
// Update codec settings
|
||||||
void setCodecSettings(const CodecList::Settings& codecSettings);
|
void setCodecSettings(const CodecList::Settings& codecSettings);
|
||||||
|
CodecList::Settings& getCodecSettings();
|
||||||
// Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding.
|
// Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding.
|
||||||
// Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container).
|
// Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container).
|
||||||
bool add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** codec = nullptr);
|
bool add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** codec = nullptr);
|
||||||
|
|
|
||||||
|
|
@ -306,18 +306,22 @@ void AudioStream::dataArrived(PDatagramSocket s, const void* buffer, int length,
|
||||||
int receiveLength = length;
|
int receiveLength = length;
|
||||||
memcpy(mReceiveBuffer, buffer, length);
|
memcpy(mReceiveBuffer, buffer, length);
|
||||||
|
|
||||||
bool srtpResult;
|
|
||||||
if (mSrtpSession.active())
|
if (mSrtpSession.active())
|
||||||
{
|
{
|
||||||
|
bool srtpResult;
|
||||||
|
size_t srcLength = length; size_t dstLength = sizeof mSrtpDecodeBuffer;
|
||||||
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
|
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
|
||||||
srtpResult = mSrtpSession.unprotectRtp(mReceiveBuffer, &receiveLength);
|
srtpResult = mSrtpSession.unprotectRtp(mReceiveBuffer, srcLength, mSrtpDecodeBuffer, &dstLength);
|
||||||
else
|
else
|
||||||
srtpResult = mSrtpSession.unprotectRtcp(mReceiveBuffer, &receiveLength);
|
srtpResult = mSrtpSession.unprotectRtcp(mReceiveBuffer, srcLength, mSrtpDecodeBuffer, &dstLength);
|
||||||
if (!srtpResult)
|
if (!srtpResult)
|
||||||
{
|
{
|
||||||
ICELogError(<<"Cannot decrypt SRTP packet.");
|
ICELogError(<<"Cannot decrypt SRTP packet.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memcpy(mReceiveBuffer, mSrtpDecodeBuffer, dstLength);
|
||||||
|
receiveLength = dstLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (source.family())
|
switch (source.family())
|
||||||
|
|
@ -343,8 +347,8 @@ void AudioStream::dataArrived(PDatagramSocket s, const void* buffer, int length,
|
||||||
mStat.mReceived += length;
|
mStat.mReceived += length;
|
||||||
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
|
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
|
||||||
{
|
{
|
||||||
if (!mStat.mFirstRtpTime.is_initialized())
|
if (!mStat.mFirstRtpTime)
|
||||||
mStat.mFirstRtpTime = std::chrono::system_clock::now();
|
mStat.mFirstRtpTime = std::chrono::steady_clock::now();
|
||||||
mStat.mReceivedRtp++;
|
mStat.mReceivedRtp++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef __MT_AUDIOSTREAM_H
|
#ifndef __MT_AUDIOSTREAM_H
|
||||||
#define __MT_AUDIOSTREAM_H
|
#define __MT_AUDIOSTREAM_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "MT_Stream.h"
|
#include "MT_Stream.h"
|
||||||
#include "MT_NativeRtpSender.h"
|
#include "MT_NativeRtpSender.h"
|
||||||
#include "MT_SingleAudioStream.h"
|
#include "MT_SingleAudioStream.h"
|
||||||
|
|
@ -87,7 +87,8 @@ protected:
|
||||||
mCaptureResampler32,
|
mCaptureResampler32,
|
||||||
mCaptureResampler48;
|
mCaptureResampler48;
|
||||||
DtmfContext mDtmfContext;
|
DtmfContext mDtmfContext;
|
||||||
char mReceiveBuffer[MAX_VALID_UDPPACKET_SIZE];
|
char mReceiveBuffer[MAX_VALID_UDPPACKET_SIZE],
|
||||||
|
mSrtpDecodeBuffer[MAX_VALID_UDPPACKET_SIZE];
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
|
|
||||||
#include "MT_CngHelper.h"
|
#include "MT_CngHelper.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ int Codec::Factory::channels()
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
void Codec::Factory::create(CodecMap& codecs)
|
void Codec::Factory::create(CodecMap& codecs)
|
||||||
{
|
{
|
||||||
codecs[payloadType()] = std::shared_ptr<Codec>(create());
|
codecs[payloadType()] = std::shared_ptr<Codec>(create());
|
||||||
|
|
@ -38,4 +36,4 @@ int Codec::Factory::processSdp(const resip::SdpContents::Session::Medium::CodecC
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,7 @@
|
||||||
#ifndef __MT_CODEC_H
|
#ifndef __MT_CODEC_H
|
||||||
#define __MT_CODEC_H
|
#define __MT_CODEC_H
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
#include "resiprocate/resip/stack/SdpContents.hxx"
|
#include "resiprocate/resip/stack/SdpContents.hxx"
|
||||||
#endif
|
|
||||||
#include "../helper/HL_Types.h"
|
#include "../helper/HL_Types.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include "../helper/HL_Pointer.h"
|
#include "../helper/HL_Pointer.h"
|
||||||
|
|
@ -36,14 +34,12 @@ public:
|
||||||
virtual PCodec create() = 0;
|
virtual PCodec create() = 0;
|
||||||
|
|
||||||
virtual int channels();
|
virtual int channels();
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
typedef std::map<int, PCodec > CodecMap;
|
typedef std::map<int, PCodec > CodecMap;
|
||||||
virtual void create(CodecMap& codecs);
|
virtual void create(CodecMap& codecs);
|
||||||
virtual void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
virtual void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
||||||
// Returns payload type from chosen codec if success. -1 is returned for negative result.
|
// Returns payload type from chosen codec if success. -1 is returned for negative result.
|
||||||
virtual int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
virtual int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
|
||||||
resip::Codec resipCodec();
|
resip::Codec resipCodec();
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
virtual ~Codec() {}
|
virtual ~Codec() {}
|
||||||
virtual const char* name() = 0;
|
virtual const char* name() = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
/* Copyright(C) 2007-2023 VoIPobjects (voipobjects.com)
|
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "MT_CodecList.h"
|
#include "MT_CodecList.h"
|
||||||
#include "MT_AudioCodec.h"
|
#include "MT_AudioCodec.h"
|
||||||
|
|
||||||
|
|
@ -25,33 +25,95 @@ using namespace MT;
|
||||||
|
|
||||||
using strx = strx;
|
using strx = strx;
|
||||||
|
|
||||||
// ---------------- EvsSpec ---------------
|
|
||||||
|
bool CodecList::Settings::contains(int ptype) const
|
||||||
|
{
|
||||||
|
if (ptype >= 0 && ptype < 96)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (mAmrNbOctetPayloadType.contains(ptype))
|
||||||
|
return true;
|
||||||
|
if (mAmrNbPayloadType.contains(ptype))
|
||||||
|
return true;
|
||||||
|
if (mAmrWbOctetPayloadType.contains(ptype))
|
||||||
|
return true;
|
||||||
|
if (mAmrWbPayloadType.contains(ptype))
|
||||||
|
return true;
|
||||||
|
for (const auto& s: mOpusSpec)
|
||||||
|
if (s.mPayloadType == ptype)
|
||||||
|
return true;
|
||||||
|
for (const auto& s: mEvsSpec)
|
||||||
|
if (s.mPayloadType == ptype)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (mIsac16KPayloadType == ptype || mIsac32KPayloadType == ptype)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (mIlbc20PayloadType == ptype || mIlbc30PayloadType == ptype)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (mGsmEfrPayloadType == ptype || mGsmFrPayloadType == ptype || mGsmHrPayloadType == ptype)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::string CodecList::Settings::toString() const
|
std::string CodecList::Settings::toString() const
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "wrap IuUP: " << mWrapIuUP << std::endl
|
// oss << "wrap IuUP: " << mWrapIuUP << std::endl
|
||||||
<< "skip decode: " << mSkipDecode << std::endl;
|
// << "skip decode: " << mSkipDecode << std::endl;
|
||||||
for (int ptype: mAmrWbPayloadType)
|
|
||||||
oss << "AMR WB ptype: " << ptype << std::endl;
|
|
||||||
for (int ptype: mAmrWbOctetPayloadType)
|
|
||||||
oss << "AMR WB octet-aligned ptype: " << ptype << std::endl;
|
|
||||||
for (int ptype: mAmrNbPayloadType)
|
|
||||||
oss << "AMR NB ptype: " << ptype << std::endl;
|
|
||||||
for (int ptype: mAmrNbOctetPayloadType)
|
|
||||||
oss << "AMR NB octet-aligned ptype:" << ptype << std::endl;
|
|
||||||
|
|
||||||
oss << "ISAC 16Khz ptype: " << mIsac16KPayloadType << std::endl
|
if (!mAmrWbPayloadType.empty())
|
||||||
<< "ISAC 32Khz ptype: " << mIsac32KPayloadType << std::endl
|
{
|
||||||
<< "iLBC 20ms ptype: " << mIlbc20PayloadType << std::endl
|
oss << "AMR WB ptype: ";
|
||||||
<< "iLBC 30ms ptype: " << mIlbc30PayloadType << std::endl
|
for (int ptype: mAmrWbPayloadType)
|
||||||
<< "GSM FR ptype: " << mGsmFrPayloadType << ", GSM FR plength: " << mGsmFrPayloadLength << std::endl
|
oss << ptype << " ";
|
||||||
<< "GSM HR ptype: " << mGsmHrPayloadType << std::endl
|
}
|
||||||
<< "GSM EFR ptype: " << mGsmEfrPayloadType << std::endl;
|
if (!mAmrWbOctetPayloadType.empty())
|
||||||
|
{
|
||||||
|
oss << "AMR WB octet ptype: ";
|
||||||
|
for (int ptype: mAmrWbOctetPayloadType)
|
||||||
|
oss << ptype << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mAmrNbPayloadType.empty())
|
||||||
|
{
|
||||||
|
oss << "AMR ptype: ";
|
||||||
|
for (int ptype: mAmrNbPayloadType)
|
||||||
|
oss << ptype << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mAmrNbOctetPayloadType.empty())
|
||||||
|
{
|
||||||
|
oss << "AMR octet ptype: ";
|
||||||
|
for (int ptype: mAmrNbOctetPayloadType)
|
||||||
|
oss << ptype << " ";
|
||||||
|
}
|
||||||
|
if (mIsac16KPayloadType != -1)
|
||||||
|
oss << "ISAC 16Khz ptype: " << mIsac16KPayloadType << " ";
|
||||||
|
|
||||||
|
if (mIsac32KPayloadType != -1)
|
||||||
|
oss << "ISAC 32Khz ptype: " << mIsac32KPayloadType << " ";
|
||||||
|
|
||||||
|
if (mIlbc20PayloadType != -1)
|
||||||
|
oss << "iLBC 20ms ptype: " << mIlbc20PayloadType << " ";
|
||||||
|
|
||||||
|
if (mIlbc30PayloadType != -1)
|
||||||
|
oss << "iLBC 30ms ptype: " << mIlbc30PayloadType << " ";
|
||||||
|
|
||||||
|
if (mGsmFrPayloadType != -1)
|
||||||
|
oss << "GSM FR ptype: " << mGsmFrPayloadType << ", GSM FR plength: " << mGsmFrPayloadLength << " ";
|
||||||
|
|
||||||
|
if (mGsmHrPayloadType != -1)
|
||||||
|
oss << "GSM HR ptype: " << mGsmHrPayloadType << " ";
|
||||||
|
|
||||||
|
if (mGsmEfrPayloadType != -1)
|
||||||
|
oss << "GSM EFR ptype: " << mGsmEfrPayloadType << " ";
|
||||||
|
|
||||||
for (auto& spec: mEvsSpec)
|
for (auto& spec: mEvsSpec)
|
||||||
{
|
{
|
||||||
oss << "EVS ptype: " << spec.mPayloadType << ", bw: " << spec.mBandwidth << ", enc: " << (spec.mEncodingType == EvsSpec::Encoding_MIME ? "mime" : "g192") << std::endl;
|
oss << "EVS ptype: " << spec.mPayloadType << ", bw: " << spec.mBandwidth << ", enc: " << (spec.mEncodingType == EvsSpec::Encoding_MIME ? "mime" : "g192") << " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& spec: mOpusSpec)
|
for (auto& spec: mOpusSpec)
|
||||||
|
|
@ -62,6 +124,24 @@ std::string CodecList::Settings::toString() const
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CodecList::Settings::clear()
|
||||||
|
{
|
||||||
|
// Remove all dynamic payload type assignments
|
||||||
|
mEvsSpec.clear();
|
||||||
|
mOpusSpec.clear();
|
||||||
|
mAmrNbOctetPayloadType.clear();
|
||||||
|
mAmrNbPayloadType.clear();
|
||||||
|
mAmrWbOctetPayloadType.clear();
|
||||||
|
mAmrWbPayloadType.clear();
|
||||||
|
mIsac16KPayloadType = -1;
|
||||||
|
mIsac32KPayloadType = -1;
|
||||||
|
mIlbc20PayloadType = -1;
|
||||||
|
mIlbc30PayloadType = -1;
|
||||||
|
mGsmEfrPayloadType = -1;
|
||||||
|
mGsmFrPayloadType = -1;
|
||||||
|
mGsmHrPayloadType = -1;
|
||||||
|
}
|
||||||
|
|
||||||
bool CodecList::Settings::EvsSpec::isValid() const
|
bool CodecList::Settings::EvsSpec::isValid() const
|
||||||
{
|
{
|
||||||
return mPayloadType >= 96 && mPayloadType <= 127;
|
return mPayloadType >= 96 && mPayloadType <= 127;
|
||||||
|
|
@ -71,7 +151,7 @@ CodecList::Settings::EvsSpec CodecList::Settings::EvsSpec::parse(const std::stri
|
||||||
{
|
{
|
||||||
EvsSpec result;
|
EvsSpec result;
|
||||||
|
|
||||||
auto parts = strx::split(spec, "-/");
|
auto parts = strx::split(spec, "-/ ,:");
|
||||||
if (parts.size() == 3)
|
if (parts.size() == 3)
|
||||||
{
|
{
|
||||||
// Payload type number
|
// Payload type number
|
||||||
|
|
@ -116,7 +196,7 @@ CodecList::Settings::OpusSpec CodecList::Settings::OpusSpec::parse(const std::st
|
||||||
{
|
{
|
||||||
OpusSpec result;
|
OpusSpec result;
|
||||||
|
|
||||||
auto parts = strx::split(spec, "-");
|
auto parts = strx::split(spec, "-/ ,:");
|
||||||
if (parts.size() == 3)
|
if (parts.size() == 3)
|
||||||
{
|
{
|
||||||
result.mPayloadType = strx::toInt(strx::trim(parts.front()).c_str(), -1);
|
result.mPayloadType = strx::toInt(strx::trim(parts.front()).c_str(), -1);
|
||||||
|
|
@ -126,7 +206,21 @@ CodecList::Settings::OpusSpec CodecList::Settings::OpusSpec::parse(const std::st
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
static int findOctetMode(const char* line)
|
||||||
|
{
|
||||||
|
const char* param_name = "octet-align=";
|
||||||
|
auto p = strstr(line, param_name);
|
||||||
|
if (!p)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
p += strlen(param_name);
|
||||||
|
char int_buf[8] = {0};
|
||||||
|
size_t int_buf_offset = 0;
|
||||||
|
while (*p && isdigit(*p) && int_buf_offset < sizeof(int_buf))
|
||||||
|
int_buf[int_buf_offset++] = *p++;
|
||||||
|
return atoi(int_buf);
|
||||||
|
}
|
||||||
|
|
||||||
CodecList::Settings CodecList::Settings::parseSdp(const std::list<resip::Codec>& codeclist)
|
CodecList::Settings CodecList::Settings::parseSdp(const std::list<resip::Codec>& codeclist)
|
||||||
{
|
{
|
||||||
CodecList::Settings r{DefaultSettings};
|
CodecList::Settings r{DefaultSettings};
|
||||||
|
|
@ -137,20 +231,50 @@ CodecList::Settings CodecList::Settings::parseSdp(const std::list<resip::Codec>&
|
||||||
int samplerate = c.getRate();
|
int samplerate = c.getRate();
|
||||||
int ptype = c.payloadType();
|
int ptype = c.payloadType();
|
||||||
|
|
||||||
|
auto enc_params = c.encodingParameters(); // This must channels number for Opus codec
|
||||||
|
auto params = c.parameters();
|
||||||
|
|
||||||
// Dynamic payload type codecs only - ISAC / iLBC / Speex / etc.
|
// Dynamic payload type codecs only - ISAC / iLBC / Speex / etc.
|
||||||
if (codec_name == "OPUS")
|
if (codec_name == "OPUS")
|
||||||
{
|
{
|
||||||
// Check the parameters
|
// Check the parameters
|
||||||
auto enc_params = c.encodingParameters(); // This must channels number for Opus codec
|
|
||||||
auto params = c.parameters();
|
|
||||||
int channels = strx::toInt(enc_params.c_str(), 1);
|
int channels = strx::toInt(enc_params.c_str(), 1);
|
||||||
r.mOpusSpec.push_back({ptype, samplerate, channels});
|
r.mOpusSpec.push_back({ptype, samplerate, channels});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
if (codec_name == "AMR-WB")
|
||||||
|
{
|
||||||
|
int octet_mode = findOctetMode(params.c_str());
|
||||||
|
if (octet_mode != -1)
|
||||||
|
{
|
||||||
|
if (octet_mode == 0)
|
||||||
|
r.mAmrWbPayloadType.insert(ptype);
|
||||||
|
else
|
||||||
|
if (octet_mode == 1)
|
||||||
|
r.mAmrWbOctetPayloadType.insert(ptype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (codec_name == "AMR" || codec_name == "AMR-NB")
|
||||||
|
{
|
||||||
|
int octet_mode = findOctetMode(params.c_str());
|
||||||
|
if (octet_mode != -1)
|
||||||
|
{
|
||||||
|
if (octet_mode == 0)
|
||||||
|
r.mAmrNbPayloadType.insert(ptype);
|
||||||
|
else
|
||||||
|
if (octet_mode == 1)
|
||||||
|
r.mAmrNbOctetPayloadType.insert(ptype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (codec_name == "EVS")
|
||||||
|
{
|
||||||
|
r.mEvsSpec.push_back({ptype});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
bool CodecList::Settings::operator == (const Settings& rhs) const
|
bool CodecList::Settings::operator == (const Settings& rhs) const
|
||||||
{
|
{
|
||||||
|
|
@ -200,21 +324,12 @@ CodecList::CodecList(const Settings& settings)
|
||||||
|
|
||||||
void CodecList::init(const Settings& settings)
|
void CodecList::init(const Settings& settings)
|
||||||
{
|
{
|
||||||
for (auto f: mFactoryList)
|
|
||||||
delete f;
|
|
||||||
mFactoryList.clear();
|
mFactoryList.clear();
|
||||||
|
mSettings = settings;
|
||||||
#if defined(USE_OPUS_CODEC)
|
#if defined(USE_OPUS_CODEC)
|
||||||
if (settings.mOpusSpec.empty())
|
|
||||||
{
|
|
||||||
mFactoryList.push_back(new OpusCodec::OpusFactory(48000, 2, MT_OPUS_CODEC_PT));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (auto spec: settings.mOpusSpec)
|
for (auto spec: settings.mOpusSpec)
|
||||||
{
|
{
|
||||||
mFactoryList.push_back(new OpusCodec::OpusFactory(spec.mRate, spec.mChannels, spec.mPayloadType));
|
mFactoryList.push_back(std::make_shared<OpusCodec::OpusFactory>(spec.mRate, spec.mChannels, spec.mPayloadType));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -222,28 +337,29 @@ void CodecList::init(const Settings& settings)
|
||||||
#if !defined(TARGET_ANDROID) && !defined(TARGET_OPENWRT) && !defined(TARGET_RPI)
|
#if !defined(TARGET_ANDROID) && !defined(TARGET_OPENWRT) && !defined(TARGET_RPI)
|
||||||
#if defined(USE_AMR_CODEC)
|
#if defined(USE_AMR_CODEC)
|
||||||
for (int pt: mSettings.mAmrWbPayloadType)
|
for (int pt: mSettings.mAmrWbPayloadType)
|
||||||
mFactoryList.push_back(new AmrWbCodec::CodecFactory({mSettings.mWrapIuUP, false, pt}));
|
mFactoryList.push_back(std::make_shared<AmrWbCodec::CodecFactory>(AmrCodecConfig{mSettings.mWrapIuUP, false, pt}));
|
||||||
for (int pt: mSettings.mAmrWbOctetPayloadType)
|
for (int pt: mSettings.mAmrWbOctetPayloadType)
|
||||||
mFactoryList.push_back(new AmrWbCodec::CodecFactory({mSettings.mWrapIuUP, true, pt}));
|
mFactoryList.push_back(std::make_shared<AmrWbCodec::CodecFactory>(AmrCodecConfig{mSettings.mWrapIuUP, true, pt}));
|
||||||
|
|
||||||
for (int pt: mSettings.mAmrNbPayloadType)
|
for (int pt: mSettings.mAmrNbPayloadType)
|
||||||
mFactoryList.push_back(new AmrNbCodec::CodecFactory({mSettings.mWrapIuUP, false, pt}));
|
mFactoryList.push_back(std::make_shared<AmrNbCodec::CodecFactory>(AmrCodecConfig{mSettings.mWrapIuUP, false, pt}));
|
||||||
for (int pt: mSettings.mAmrNbOctetPayloadType)
|
for (int pt: mSettings.mAmrNbOctetPayloadType)
|
||||||
mFactoryList.push_back(new AmrNbCodec::CodecFactory({mSettings.mWrapIuUP, true, pt}));
|
mFactoryList.push_back(std::make_shared<AmrNbCodec::CodecFactory>(AmrCodecConfig{mSettings.mWrapIuUP, true, pt}));
|
||||||
|
|
||||||
mFactoryList.push_back(new GsmEfrCodec::GsmEfrFactory(mSettings.mWrapIuUP, mSettings.mGsmEfrPayloadType));
|
if (mSettings.mGsmEfrPayloadType != -1)
|
||||||
|
mFactoryList.push_back(std::make_shared<GsmEfrCodec::GsmEfrFactory>(mSettings.mWrapIuUP, mSettings.mGsmEfrPayloadType));
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
// mFactoryList.push_back(new IsacCodec::IsacFactory16K(mSettings.mIsac16KPayloadType));
|
mFactoryList.push_back(std::make_shared<G711Codec::AlawFactory>());
|
||||||
// mFactoryList.push_back(new IlbcCodec::IlbcFactory(mSettings.mIlbc20PayloadType, mSettings.mIlbc30PayloadType));
|
mFactoryList.push_back(std::make_shared<G711Codec::UlawFactory>());
|
||||||
mFactoryList.push_back(new G711Codec::AlawFactory());
|
|
||||||
mFactoryList.push_back(new G711Codec::UlawFactory());
|
|
||||||
|
|
||||||
mFactoryList.push_back(new GsmCodec::GsmFactory(mSettings.mGsmFrPayloadLength == 32 ? GsmCodec::Type::Bytes_32 : GsmCodec::Type::Bytes_33, mSettings.mGsmFrPayloadType));
|
if (mSettings.mGsmFrPayloadType != -1)
|
||||||
// mFactoryList.push_back(new G722Codec::G722Factory());
|
mFactoryList.push_back(std::make_shared<GsmCodec::GsmFactory>(mSettings.mGsmFrPayloadLength == 32 ? GsmCodec::Type::Bytes_32 : GsmCodec::Type::Bytes_33, mSettings.mGsmFrPayloadType));
|
||||||
// mFactoryList.push_back(new G729Codec::G729Factory());
|
mFactoryList.push_back(std::make_shared<G722Codec::G722Factory>());
|
||||||
|
mFactoryList.push_back(std::make_shared<G729Codec::G729Factory>());
|
||||||
#ifndef TARGET_ANDROID
|
#ifndef TARGET_ANDROID
|
||||||
mFactoryList.push_back(new GsmHrCodec::GsmHrFactory(mSettings.mGsmHrPayloadType));
|
if (mSettings.mGsmHrPayloadType != -1)
|
||||||
|
mFactoryList.push_back(std::make_shared<GsmHrCodec::GsmHrFactory>(mSettings.mGsmHrPayloadType));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(TARGET_ANDROID) && defined(USE_EVS_CODEC)
|
#if !defined(TARGET_ANDROID) && defined(USE_EVS_CODEC)
|
||||||
|
|
@ -255,15 +371,14 @@ void CodecList::init(const Settings& settings)
|
||||||
evs_params.ptime = 20;
|
evs_params.ptime = 20;
|
||||||
evs_params.ptype = spec.mPayloadType;
|
evs_params.ptype = spec.mPayloadType;
|
||||||
|
|
||||||
mFactoryList.push_back(new EVSCodec::EVSFactory(evs_params));
|
mFactoryList.push_back(std::make_shared<EVSCodec::EVSFactory>(evs_params));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
CodecList::~CodecList()
|
CodecList::~CodecList()
|
||||||
{
|
{
|
||||||
for (auto f: mFactoryList)
|
mFactoryList.clear();
|
||||||
delete f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int CodecList::count() const
|
int CodecList::count() const
|
||||||
|
|
@ -292,21 +407,26 @@ void CodecList::fillCodecMap(CodecMap& cm)
|
||||||
for (auto& factory: mFactoryList)
|
for (auto& factory: mFactoryList)
|
||||||
{
|
{
|
||||||
// Create codec here. Although they are not needed right now - they can be needed to find codec's info.
|
// Create codec here. Although they are not needed right now - they can be needed to find codec's info.
|
||||||
PCodec c = factory->create();
|
// PCodec c = factory->create();
|
||||||
cm.insert({factory->payloadType(), c});
|
cm.insert({factory->payloadType(), PCodec()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PCodec CodecList::createCodecByPayloadType(int payloadType)
|
||||||
|
{
|
||||||
|
for (auto& factory: mFactoryList)
|
||||||
|
{
|
||||||
|
if (factory->payloadType() == payloadType)
|
||||||
|
return factory->create();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
CodecListPriority::CodecListPriority()
|
CodecListPriority::CodecListPriority()
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CodecListPriority::~CodecListPriority()
|
CodecListPriority::~CodecListPriority()
|
||||||
{
|
{}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CodecListPriority::isNegativePriority(const CodecListPriority::Item& item)
|
bool CodecListPriority::isNegativePriority(const CodecListPriority::Item& item)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,14 @@
|
||||||
#ifndef __MT_CODEC_LIST_H
|
#ifndef __MT_CODEC_LIST_H
|
||||||
#define __MT_CODEC_LIST_H
|
#define __MT_CODEC_LIST_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
#include "resiprocate/resip/stack/SdpContents.hxx"
|
#include "resiprocate/resip/stack/SdpContents.hxx"
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "MT_Codec.h"
|
#include "MT_Codec.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <list>
|
||||||
#include "../helper/HL_VariantMap.h"
|
#include "../helper/HL_VariantMap.h"
|
||||||
|
|
||||||
#define ALL_CODECS_STRING "OPUS,ISAC,ILBC,PCMU,PCMA,G722,GSM"
|
#define ALL_CODECS_STRING "OPUS,ISAC,ILBC,PCMU,PCMA,G722,GSM"
|
||||||
|
|
@ -30,22 +29,22 @@ public:
|
||||||
bool mSkipDecode = false;
|
bool mSkipDecode = false;
|
||||||
|
|
||||||
// AMR payload types
|
// AMR payload types
|
||||||
std::set<int> mAmrWbPayloadType = { MT_AMRWB_PAYLOADTYPE };
|
std::set<int64_t> mAmrWbPayloadType = { };
|
||||||
std::set<int> mAmrNbPayloadType = { MT_AMRNB_PAYLOADTYPE };
|
std::set<int64_t> mAmrNbPayloadType = { };
|
||||||
std::set<int> mAmrWbOctetPayloadType = { MT_AMRWB_OCTET_PAYLOADTYPE };
|
std::set<int64_t> mAmrWbOctetPayloadType = { };
|
||||||
std::set<int> mAmrNbOctetPayloadType = { MT_AMRNB_OCTET_PAYLOADTYPE };
|
std::set<int64_t> mAmrNbOctetPayloadType = { };
|
||||||
|
|
||||||
bool isAmrWb(int ptype) const { return mAmrWbOctetPayloadType.count(ptype) > 0 || mAmrWbPayloadType.count(ptype) > 0; }
|
bool isAmrWb(int ptype) const { return mAmrWbOctetPayloadType.count(ptype) > 0 || mAmrWbPayloadType.count(ptype) > 0; }
|
||||||
bool isAmrNb(int ptype) const { return mAmrNbOctetPayloadType.count(ptype) > 0 || mAmrNbPayloadType.count(ptype) > 0; }
|
bool isAmrNb(int ptype) const { return mAmrNbOctetPayloadType.count(ptype) > 0 || mAmrNbPayloadType.count(ptype) > 0; }
|
||||||
|
|
||||||
int mIsac16KPayloadType = MT_ISAC16K_PAYLOADTYPE;
|
int mIsac16KPayloadType = -1;
|
||||||
int mIsac32KPayloadType = MT_ISAC32K_PAYLOADTYPE;
|
int mIsac32KPayloadType = -1;
|
||||||
int mIlbc20PayloadType = MT_ILBC20_PAYLOADTYPE;
|
int mIlbc20PayloadType = -1;
|
||||||
int mIlbc30PayloadType = MT_ILBC30_PAYLOADTYPE;
|
int mIlbc30PayloadType = -1;
|
||||||
int mGsmFrPayloadType = 3; // GSM is codec with fixed payload type. But sometimes it has to be overwritten.
|
int mGsmFrPayloadType = -1; // GSM is codec with fixed payload type. But sometimes it has to be overwritten.
|
||||||
int mGsmFrPayloadLength = 33; // Expected GSM payload length
|
int mGsmFrPayloadLength = 33; // Expected GSM payload length
|
||||||
int mGsmHrPayloadType = MT_GSMHR_PAYLOADTYPE;
|
int mGsmHrPayloadType = -1;
|
||||||
int mGsmEfrPayloadType = MT_GSMEFR_PAYLOADTYPE;
|
int mGsmEfrPayloadType = -1;
|
||||||
|
|
||||||
struct EvsSpec
|
struct EvsSpec
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +57,7 @@ public:
|
||||||
Bandwidth_FB
|
Bandwidth_FB
|
||||||
};
|
};
|
||||||
|
|
||||||
Bandwidth mBandwidth = Bandwidth_NB;
|
Bandwidth mBandwidth = Bandwidth_FB;
|
||||||
|
|
||||||
enum Encoding
|
enum Encoding
|
||||||
{
|
{
|
||||||
|
|
@ -71,7 +70,7 @@ public:
|
||||||
static EvsSpec parse(const std::string& spec);
|
static EvsSpec parse(const std::string& spec);
|
||||||
|
|
||||||
bool operator == (const EvsSpec& rhs) const { return std::tie(mPayloadType, mBandwidth, mEncodingType) == std::tie(rhs.mPayloadType, rhs.mBandwidth, rhs.mEncodingType);}
|
bool operator == (const EvsSpec& rhs) const { return std::tie(mPayloadType, mBandwidth, mEncodingType) == std::tie(rhs.mPayloadType, rhs.mBandwidth, rhs.mEncodingType);}
|
||||||
bool operator != (const EvsSpec& rhs) const { return ! (operator ==) (rhs);};
|
bool operator != (const EvsSpec& rhs) const { return ! (operator ==) (rhs);}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -89,35 +88,42 @@ public:
|
||||||
|
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
bool operator == (const OpusSpec& rhs) const { return std::tie(mPayloadType, mRate, mChannels) == std::tie(rhs.mPayloadType, rhs.mRate, rhs.mChannels);}
|
bool operator == (const OpusSpec& rhs) const { return std::tie(mPayloadType, mRate, mChannels) == std::tie(rhs.mPayloadType, rhs.mRate, rhs.mChannels);}
|
||||||
bool operator != (const OpusSpec& rhs) const { return ! (operator ==) (rhs);};
|
bool operator != (const OpusSpec& rhs) const { return ! (operator ==) (rhs);}
|
||||||
|
|
||||||
static OpusSpec parse(const std::string& spec);
|
static OpusSpec parse(const std::string& spec);
|
||||||
};
|
};
|
||||||
std::vector<OpusSpec> mOpusSpec;
|
std::vector<OpusSpec> mOpusSpec;
|
||||||
|
|
||||||
|
// Payload type
|
||||||
|
bool contains(int ptype) const;
|
||||||
|
|
||||||
// Textual representation - used in logging
|
// Textual representation - used in logging
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
void clear();
|
||||||
|
|
||||||
static Settings DefaultSettings;
|
static Settings DefaultSettings;
|
||||||
|
|
||||||
#if defined(USE_RESIP_INTEGRATION)
|
|
||||||
static Settings parseSdp(const std::list<resip::Codec>& codeclist);
|
static Settings parseSdp(const std::list<resip::Codec>& codeclist);
|
||||||
#endif
|
|
||||||
bool operator == (const Settings& rhs) const;
|
bool operator == (const Settings& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
CodecList(const Settings& settings);
|
CodecList(const Settings& settings);
|
||||||
~CodecList();
|
~CodecList();
|
||||||
void setSettings(const Settings& settings) { init(settings); }
|
void setSettings(const Settings& settings)
|
||||||
|
{
|
||||||
|
init(settings);
|
||||||
|
}
|
||||||
|
|
||||||
int count() const;
|
int count() const;
|
||||||
Codec::Factory& codecAt(int index) const;
|
Codec::Factory& codecAt(int index) const;
|
||||||
int findCodec(const std::string& name) const;
|
int findCodec(const std::string& name) const;
|
||||||
void fillCodecMap(CodecMap& cm);
|
void fillCodecMap(CodecMap& cm);
|
||||||
|
PCodec createCodecByPayloadType(int payloadType);
|
||||||
|
void clear();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
typedef std::vector<Codec::Factory*> FactoryList;
|
typedef std::vector<std::shared_ptr<Codec::Factory>> FactoryList;
|
||||||
FactoryList mFactoryList;
|
FactoryList mFactoryList;
|
||||||
Settings mSettings;
|
Settings mSettings;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "MT_Dtmf.h"
|
#include "MT_Dtmf.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#ifndef MT_DTMF
|
#ifndef MT_DTMF
|
||||||
#define MT_DTMF
|
#define MT_DTMF
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
#include "../helper/HL_ByteBuffer.h"
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,7 @@ PCodec EVSCodec::EVSFactory::create()
|
||||||
}
|
}
|
||||||
|
|
||||||
EVSCodec::EVSCodec(): EVSCodec(StreamParameters())
|
EVSCodec::EVSCodec(): EVSCodec(StreamParameters())
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
EVSCodec::EVSCodec(const StreamParameters &sp)
|
EVSCodec::EVSCodec(const StreamParameters &sp)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#ifndef __MT_EVS_CODEC_H
|
||||||
|
#define __MT_EVS_CODEC_H
|
||||||
|
|
||||||
|
#include "../engine_config.h"
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -10,7 +12,6 @@
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
#include "MT_Codec.h"
|
#include "MT_Codec.h"
|
||||||
|
|
||||||
#include "libevs/lib_com/prot.h"
|
#include "libevs/lib_com/prot.h"
|
||||||
|
|
@ -40,7 +41,7 @@ public:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EVSFactory(StreamParameters& sp);
|
EVSFactory(StreamParameters& sp);
|
||||||
const char* name() { return MT_EVS_CODECNAME; };
|
const char* name() { return MT_EVS_CODECNAME; }
|
||||||
int samplerate();
|
int samplerate();
|
||||||
int payloadType();
|
int payloadType();
|
||||||
PCodec create();
|
PCodec create();
|
||||||
|
|
@ -51,7 +52,7 @@ public:
|
||||||
EVSCodec(const StreamParameters& sp);
|
EVSCodec(const StreamParameters& sp);
|
||||||
~EVSCodec() override;
|
~EVSCodec() override;
|
||||||
|
|
||||||
const char* name() override { return MT_EVS_CODECNAME; } ;
|
const char* name() override { return MT_EVS_CODECNAME; }
|
||||||
int samplerate() override;
|
int samplerate() override;
|
||||||
int pcmLength() override;
|
int pcmLength() override;
|
||||||
int frameTime() override;
|
int frameTime() override;
|
||||||
|
|
@ -68,3 +69,5 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End of namespace
|
} // End of namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
#ifndef __MT_NATIVE_RTP_SENDER_H
|
#ifndef __MT_NATIVE_RTP_SENDER_H
|
||||||
#define __MT_NATIVE_RTP_SENDER_H
|
#define __MT_NATIVE_RTP_SENDER_H
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "jrtplib/src/rtpexternaltransmitter.h"
|
#include "jrtplib/src/rtpexternaltransmitter.h"
|
||||||
#include "srtp/include/srtp.h"
|
#include "libsrtp/include/srtp.h"
|
||||||
#include "../helper/HL_NetworkSocket.h"
|
#include "../helper/HL_NetworkSocket.h"
|
||||||
#include "../helper/HL_InternetAddress.h"
|
#include "../helper/HL_InternetAddress.h"
|
||||||
#include "../helper/HL_Rtp.h"
|
#include "../helper/HL_Rtp.h"
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,90 @@
|
||||||
/* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
|
/* Copyright(C) 2007-2025 VoIP objects (voipobjects.com)
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#include "../engine_config.h"
|
||||||
#include "MT_SrtpHelper.h"
|
#include "MT_SrtpHelper.h"
|
||||||
#include "../helper/HL_Log.h"
|
#include "../helper/HL_Log.h"
|
||||||
#include "../helper/HL_Exception.h"
|
#include "../helper/HL_Exception.h"
|
||||||
#include "../helper/HL_Rtp.h"
|
#include "../helper/HL_Rtp.h"
|
||||||
|
#include <openssl/rand.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
struct SrtpSuiteAndName
|
||||||
|
{
|
||||||
|
SrtpSuite mSuite;
|
||||||
|
std::string mName, mAltName;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<SrtpSuiteAndName> SrtpSuiteList {
|
||||||
|
{ SRTP_AES_256_AUTH_80, "AES_CM_256_HMAC_SHA1_80", "AES_256_CM_HMAC_SHA1_80" },
|
||||||
|
{ SRTP_AES_128_AUTH_80, "AES_CM_128_HMAC_SHA1_80", "AES_128_CM_HMAC_SHA1_80" },
|
||||||
|
{ SRTP_AES_192_AUTH_80, "AES_CM_192_HMAC_SHA1_80", "AES_192_CM_HMAC_SHA1_80" },
|
||||||
|
{ SRTP_AES_256_AUTH_32, "AES_CM_256_HMAC_SHA1_32", "AES_256_CM_HMAC_SHA1_32" },
|
||||||
|
{ SRTP_AES_192_AUTH_32, "AES_CM_192_HMAC_SHA1_32", "AES_192_CM_HMAC_SHA1_32" },
|
||||||
|
{ SRTP_AES_128_AUTH_32, "AES_CM_128_HMAC_SHA1_32", "AES_128_CM_HMAC_SHA1_32" },
|
||||||
|
{ SRTP_AES_128_AUTH_NULL, "AES_CM_128_NULL_AUTH", "AES_128_CM_NULL_AUTH"},
|
||||||
|
{ SRTP_AED_AES_256_GCM, "AEAD_AES_256_GCM"},
|
||||||
|
{ SRTP_AED_AES_128_GCM, "AEAD_AES_128_GCM"}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SrtpSuite toSrtpSuite(const std::string_view& s)
|
||||||
|
{
|
||||||
|
for (const auto& suite: SrtpSuiteList)
|
||||||
|
if (s == suite.mName || s == suite.mAltName)
|
||||||
|
return suite.mSuite;
|
||||||
|
|
||||||
|
return SRTP_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern std::string_view toString(SrtpSuite suite)
|
||||||
|
{
|
||||||
|
for (const auto& item: SrtpSuiteList)
|
||||||
|
if (item.mSuite == suite)
|
||||||
|
return item.mName;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void (*set_srtp_policy_function) (srtp_crypto_policy_t*);
|
||||||
|
|
||||||
|
set_srtp_policy_function findPolicyFunction(SrtpSuite suite)
|
||||||
|
{
|
||||||
|
switch (suite)
|
||||||
|
{
|
||||||
|
case SRTP_AES_128_AUTH_80: return &srtp_crypto_policy_set_rtp_default; break;
|
||||||
|
case SRTP_AES_192_AUTH_80: return &srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80; break;
|
||||||
|
case SRTP_AES_256_AUTH_80: return &srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80; break;
|
||||||
|
case SRTP_AES_128_AUTH_32: return &srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32; break;
|
||||||
|
case SRTP_AES_192_AUTH_32: return &srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32; break;
|
||||||
|
case SRTP_AES_256_AUTH_32: return &srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32; break;
|
||||||
|
case SRTP_AES_128_AUTH_NULL: return &srtp_crypto_policy_set_aes_cm_128_null_auth; break;
|
||||||
|
case SRTP_AED_AES_256_GCM: return &srtp_crypto_policy_set_aes_gcm_256_16_auth; break;
|
||||||
|
case SRTP_AED_AES_128_GCM: return &srtp_crypto_policy_set_aes_gcm_128_16_auth; break;
|
||||||
|
case SRTP_NONE: return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// --- SrtpStream ---
|
// --- SrtpStream ---
|
||||||
void initSrtpStream(SrtpStream& s, unsigned ssrc, SrtpSuite suite)
|
static void configureSrtpStream(SrtpStream& s, uint16_t ssrc, SrtpSuite suite)
|
||||||
{
|
{
|
||||||
s.second.ssrc.type = ssrc_specific;
|
s.second.ssrc.type = ssrc_specific;
|
||||||
s.second.ssrc.value = ntohl(ssrc);
|
s.second.ssrc.value = ntohl(ssrc);
|
||||||
s.second.next = NULL;
|
s.second.next = nullptr;
|
||||||
switch (suite)
|
set_srtp_policy_function func = findPolicyFunction(suite);
|
||||||
{
|
if (!func)
|
||||||
case SRTP_AES_128_AUTH_80:
|
throw std::runtime_error(std::format("SRTP suite {} is not supported", toString(suite)));
|
||||||
crypto_policy_set_aes_cm_128_hmac_sha1_80(&s.second.rtp);
|
func(&s.second.rtp);
|
||||||
crypto_policy_set_aes_cm_128_hmac_sha1_80(&s.second.rtcp);
|
func(&s.second.rtcp);
|
||||||
break;
|
|
||||||
|
|
||||||
case SRTP_AES_256_AUTH_80:
|
|
||||||
crypto_policy_set_aes_cm_256_hmac_sha1_80(&s.second.rtp);
|
|
||||||
crypto_policy_set_aes_cm_256_hmac_sha1_80(&s.second.rtcp);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SrtpSession::SrtpSession()
|
SrtpSession::SrtpSession()
|
||||||
:mInboundSession(NULL), mOutboundSession(NULL)
|
:mInboundSession(nullptr), mOutboundSession(nullptr)
|
||||||
{
|
{
|
||||||
mInboundSession = NULL;
|
|
||||||
mOutboundSession = NULL;
|
|
||||||
mSuite = SRTP_NONE;
|
mSuite = SRTP_NONE;
|
||||||
|
|
||||||
memset(&mInboundPolicy, 0, sizeof mInboundPolicy);
|
memset(&mInboundPolicy, 0, sizeof mInboundPolicy);
|
||||||
|
|
@ -45,15 +93,18 @@ SrtpSession::SrtpSession()
|
||||||
memset(&mOutboundPolicy, 0, sizeof mOutboundPolicy);
|
memset(&mOutboundPolicy, 0, sizeof mOutboundPolicy);
|
||||||
mOutboundPolicy.ssrc.type = ssrc_specific;
|
mOutboundPolicy.ssrc.type = ssrc_specific;
|
||||||
|
|
||||||
// Generate outgoing keys
|
// Generate outgoing keys for all ciphers
|
||||||
|
auto putKey = [this](SrtpSuite suite, size_t length){
|
||||||
mOutgoingKey[SRTP_AES_128_AUTH_80-1].first = PByteBuffer(new ByteBuffer());
|
auto key = std::make_shared<ByteBuffer>();
|
||||||
mOutgoingKey[SRTP_AES_128_AUTH_80-1].first->resize(30);
|
key->resize(length);
|
||||||
crypto_get_random((unsigned char*)mOutgoingKey[SRTP_AES_128_AUTH_80-1].first->mutableData(), 30);
|
RAND_bytes(key->mutableData(), key->size());
|
||||||
|
mOutgoingKey[suite].first = key;
|
||||||
mOutgoingKey[SRTP_AES_256_AUTH_80-1].first = PByteBuffer(new ByteBuffer());
|
};
|
||||||
mOutgoingKey[SRTP_AES_256_AUTH_80-1].first->resize(46);
|
putKey(SRTP_AES_128_AUTH_80, 30); putKey(SRTP_AES_128_AUTH_32, 30);
|
||||||
crypto_get_random((unsigned char*)mOutgoingKey[SRTP_AES_256_AUTH_80-1].first->mutableData(), 46);
|
putKey(SRTP_AES_192_AUTH_80, 38); putKey(SRTP_AES_192_AUTH_32, 38);
|
||||||
|
putKey(SRTP_AES_256_AUTH_80, 46); putKey(SRTP_AES_256_AUTH_32, 46);
|
||||||
|
putKey(SRTP_AED_AES_128_GCM, 28);
|
||||||
|
putKey(SRTP_AED_AES_256_GCM, 44);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +112,7 @@ SrtpSession::~SrtpSession()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SrtpSession::addSsrc(unsigned ssrc, SsrcDirection d)
|
/*void SrtpSession::addSsrc(unsigned ssrc, SsrcDirection d)
|
||||||
{
|
{
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
assert(mSuite != SRTP_NONE);
|
assert(mSuite != SRTP_NONE);
|
||||||
|
|
@ -76,7 +127,7 @@ void SrtpSession::addSsrc(unsigned ssrc, SsrcDirection d)
|
||||||
if (streamIter != mIncomingMap.end())
|
if (streamIter != mIncomingMap.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
initSrtpStream(s, ssrc, mSuite);
|
configureSrtpStream(s, ssrc, mSuite);
|
||||||
s.second.key = (unsigned char*)mIncomingKey.first->mutableData();
|
s.second.key = (unsigned char*)mIncomingKey.first->mutableData();
|
||||||
mIncomingMap[ssrc] = s;
|
mIncomingMap[ssrc] = s;
|
||||||
srtp_add_stream(mInboundSession, &s.second);
|
srtp_add_stream(mInboundSession, &s.second);
|
||||||
|
|
@ -86,13 +137,13 @@ void SrtpSession::addSsrc(unsigned ssrc, SsrcDirection d)
|
||||||
streamIter = mOutgoingMap.find(ssrc);
|
streamIter = mOutgoingMap.find(ssrc);
|
||||||
if (streamIter != mOutgoingMap.end())
|
if (streamIter != mOutgoingMap.end())
|
||||||
return;
|
return;
|
||||||
initSrtpStream(s, ssrc, mSuite);
|
configureSrtpStream(s, ssrc, mSuite);
|
||||||
s.second.key = (unsigned char*)mOutgoingKey[int(mSuite)-1].first->mutableData();
|
s.second.key = (unsigned char*)mOutgoingKey[int(mSuite)-1].first->mutableData();
|
||||||
mOutgoingMap[ssrc] = s;
|
mOutgoingMap[ssrc] = s;
|
||||||
srtp_add_stream(mOutboundSession, &s.second);
|
srtp_add_stream(mOutboundSession, &s.second);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
void SrtpSession::open(ByteBuffer& incomingKey, SrtpSuite suite)
|
void SrtpSession::open(ByteBuffer& incomingKey, SrtpSuite suite)
|
||||||
{
|
{
|
||||||
|
|
@ -106,33 +157,26 @@ void SrtpSession::open(ByteBuffer& incomingKey, SrtpSuite suite)
|
||||||
mSuite = suite;
|
mSuite = suite;
|
||||||
|
|
||||||
// Save key
|
// Save key
|
||||||
mIncomingKey.first = PByteBuffer(new ByteBuffer(incomingKey));
|
mIncomingKey.first = std::make_shared<ByteBuffer>(incomingKey);
|
||||||
|
|
||||||
// Update policy
|
auto policyFunction = findPolicyFunction(suite);
|
||||||
switch (suite)
|
if (!policyFunction)
|
||||||
{
|
throw std::runtime_error(std::format("SRTP suite {} not found", toString(suite)));
|
||||||
case SRTP_AES_128_AUTH_80:
|
|
||||||
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mInboundPolicy.rtp);
|
|
||||||
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mInboundPolicy.rtcp);
|
|
||||||
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mOutboundPolicy.rtp);
|
|
||||||
crypto_policy_set_aes_cm_128_hmac_sha1_80(&mOutboundPolicy.rtcp);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SRTP_AES_256_AUTH_80:
|
// Configure policies
|
||||||
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mInboundPolicy.rtp);
|
policyFunction(&mInboundPolicy.rtp);
|
||||||
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mInboundPolicy.rtcp);
|
policyFunction(&mInboundPolicy.rtcp);
|
||||||
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mOutboundPolicy.rtp);
|
policyFunction(&mOutboundPolicy.rtp);
|
||||||
crypto_policy_set_aes_cm_256_hmac_sha1_80(&mOutboundPolicy.rtcp);
|
policyFunction(&mOutboundPolicy.rtcp);
|
||||||
break;
|
|
||||||
|
mOutboundPolicy.key = (unsigned char*)mOutgoingKey[int(suite)].first->mutableData();
|
||||||
|
mOutboundPolicy.ssrc.type = ssrc_any_outbound;
|
||||||
|
|
||||||
case SRTP_NONE:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mOutboundPolicy.key = (unsigned char*)mOutgoingKey[int(suite)-1].first->mutableData();
|
|
||||||
mInboundPolicy.key = (unsigned char*)mIncomingKey.first->mutableData();
|
mInboundPolicy.key = (unsigned char*)mIncomingKey.first->mutableData();
|
||||||
|
mInboundPolicy.ssrc.type = ssrc_any_inbound;
|
||||||
|
|
||||||
// Create SRTP session
|
// Create SRTP session
|
||||||
err_status_t err;
|
srtp_err_status_t err;
|
||||||
|
|
||||||
err = srtp_create(&mOutboundSession, &mOutboundPolicy);
|
err = srtp_create(&mOutboundSession, &mOutboundPolicy);
|
||||||
if (err)
|
if (err)
|
||||||
|
|
@ -156,13 +200,13 @@ void SrtpSession::close()
|
||||||
if (mOutboundSession)
|
if (mOutboundSession)
|
||||||
{
|
{
|
||||||
srtp_dealloc(mOutboundSession);
|
srtp_dealloc(mOutboundSession);
|
||||||
mOutboundSession = NULL;
|
mOutboundSession = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mInboundSession)
|
if (mInboundSession)
|
||||||
{
|
{
|
||||||
srtp_dealloc(mInboundSession);
|
srtp_dealloc(mInboundSession);
|
||||||
mInboundSession = NULL;
|
mInboundSession = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,44 +219,70 @@ SrtpKeySalt& SrtpSession::outgoingKey(SrtpSuite suite)
|
||||||
|
|
||||||
bool SrtpSession::protectRtp(void* buffer, int* length)
|
bool SrtpSession::protectRtp(void* buffer, int* length)
|
||||||
{
|
{
|
||||||
addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
|
// addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
|
||||||
|
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (mOutboundSession)
|
if (mOutboundSession)
|
||||||
return srtp_protect(mOutboundSession, buffer, length) == 0;
|
{
|
||||||
|
size_t srtp_len = MAX_VALID_UDPPACKET_SIZE;
|
||||||
|
auto code = srtp_protect(mOutboundSession,
|
||||||
|
(const uint8_t*)buffer, (size_t)*length,
|
||||||
|
(uint8_t*)buffer, &srtp_len,
|
||||||
|
0 /* mki_index, non-used */);
|
||||||
|
*length = srtp_len;
|
||||||
|
return code == 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SrtpSession::protectRtcp(void* buffer, int* length)
|
bool SrtpSession::protectRtcp(void* buffer, int* length)
|
||||||
{
|
{
|
||||||
addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
|
//addSsrc(RtpHelper::findSsrc(buffer, *length), sdOutgoing);
|
||||||
|
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (mOutboundSession)
|
if (mOutboundSession)
|
||||||
return srtp_protect_rtcp(mOutboundSession, buffer, length) == 0;
|
{
|
||||||
|
size_t srtp_len = MAX_VALID_UDPPACKET_SIZE;
|
||||||
|
auto code = srtp_protect_rtcp(mOutboundSession,
|
||||||
|
(const uint8_t*)buffer, (size_t)*length,
|
||||||
|
(uint8_t*)buffer, &srtp_len,
|
||||||
|
0 /* mki_index, non-used */);
|
||||||
|
*length = srtp_len;
|
||||||
|
return code == 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SrtpSession::unprotectRtp(void* buffer, int* length)
|
bool SrtpSession::unprotectRtp(const void* src, size_t srcLength, void* dst, size_t* dstLength)
|
||||||
{
|
{
|
||||||
addSsrc(RtpHelper::findSsrc(buffer, *length), sdIncoming);
|
//addSsrc(RtpHelper::findSsrc(buffer, *length), sdIncoming);
|
||||||
|
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (mInboundSession)
|
if (mInboundSession)
|
||||||
return srtp_unprotect(mInboundSession, buffer, length) == 0;
|
{
|
||||||
|
auto code = srtp_unprotect(mInboundSession,
|
||||||
|
(const uint8_t*)src, srcLength,
|
||||||
|
(uint8_t*)dst, dstLength);
|
||||||
|
return code == 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SrtpSession::unprotectRtcp(void* buffer, int* length)
|
bool SrtpSession::unprotectRtcp(const void* src, size_t srcLength, void* dst, size_t* dstLength)
|
||||||
{
|
{
|
||||||
addSsrc(RtpHelper::findSsrc(buffer, *length), sdIncoming);
|
//addSsrc(RtpHelper::findSsrc(buffer, *length), sdIncoming);
|
||||||
|
|
||||||
Lock l(mGuard);
|
Lock l(mGuard);
|
||||||
if (mInboundSession)
|
if (mInboundSession)
|
||||||
return srtp_unprotect_rtcp(mInboundSession, buffer, (int*)length) == 0;
|
{
|
||||||
|
auto code = srtp_unprotect_rtcp(mInboundSession,
|
||||||
|
(const uint8_t*)src, srcLength,
|
||||||
|
(uint8_t*)dst, dstLength);
|
||||||
|
return code == 0;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -223,9 +293,9 @@ void SrtpSession::initSrtp()
|
||||||
if (GSrtpInitialized)
|
if (GSrtpInitialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
err_status_t err = srtp_init();
|
auto err = srtp_init();
|
||||||
|
|
||||||
if (err != err_status_ok)
|
if (err != srtp_err_status_ok)
|
||||||
throw Exception(ERR_SRTP, err);
|
throw Exception(ERR_SRTP, err);
|
||||||
GSrtpInitialized = true;
|
GSrtpInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,35 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
#include "srtp/include/srtp.h"
|
#include "libsrtp/include/srtp.h"
|
||||||
#include "../helper/HL_Sync.h"
|
#include "HL_Sync.h"
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
#include "HL_ByteBuffer.h"
|
||||||
|
#include "HL_Types.h"
|
||||||
|
|
||||||
#define SRTP_SUITE_NAME_2 "AES_CM_256_HMAC_SHA1_80"
|
#define NAME_SRTP_AES_256_AUTH_80 "AES_CM_256_HMAC_SHA1_80"
|
||||||
#define SRTP_SUITE_NAME_1 "AES_CM_128_HMAC_SHA1_80"
|
#define NAME_SRTP_AES_128_AUTH_80 "AES_CM_128_HMAC_SHA1_80"
|
||||||
|
|
||||||
enum SrtpSuite
|
enum SrtpSuite
|
||||||
{
|
{
|
||||||
SRTP_NONE,
|
SRTP_NONE,
|
||||||
SRTP_AES_128_AUTH_80,
|
SRTP_AES_128_AUTH_80,
|
||||||
SRTP_AES_256_AUTH_80,
|
SRTP_AES_256_AUTH_80,
|
||||||
SRTP_LAST = SRTP_AES_256_AUTH_80
|
SRTP_AES_192_AUTH_80,
|
||||||
|
SRTP_AES_128_AUTH_32,
|
||||||
|
SRTP_AES_256_AUTH_32,
|
||||||
|
SRTP_AES_192_AUTH_32,
|
||||||
|
SRTP_AES_128_AUTH_NULL,
|
||||||
|
SRTP_AED_AES_256_GCM,
|
||||||
|
SRTP_AED_AES_128_GCM,
|
||||||
|
SRTP_LAST = SRTP_AED_AES_128_GCM
|
||||||
|
// ToDo:
|
||||||
|
// a=crypto:1 AEAD_AES_256_GCM_8 inline:tN2A0vRjFBimpQsW2GasuJuPe7hKE26gki30APC8DVuySqCOYTs8lYBPR5I=
|
||||||
|
// a=crypto:3 AEAD_AES_128_GCM_8 inline:Ok7VL8SmBHSbZLw4dK6iQgpliYKGdY9BHLJcRw==
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern SrtpSuite toSrtpSuite(const std::string_view& s);
|
||||||
|
extern std::string_view toString(SrtpSuite suite);
|
||||||
|
|
||||||
typedef std::pair<PByteBuffer, PByteBuffer> SrtpKeySalt;
|
typedef std::pair<PByteBuffer, PByteBuffer> SrtpKeySalt;
|
||||||
typedef std::pair<unsigned, srtp_policy_t> SrtpStream;
|
typedef std::pair<unsigned, srtp_policy_t> SrtpStream;
|
||||||
|
|
||||||
|
|
@ -48,8 +62,8 @@ public:
|
||||||
/* bufferPtr is RTP packet data i.e. header + payload. Buffer must be big enough to hold encrypted data. */
|
/* bufferPtr is RTP packet data i.e. header + payload. Buffer must be big enough to hold encrypted data. */
|
||||||
bool protectRtp(void* buffer, int* length);
|
bool protectRtp(void* buffer, int* length);
|
||||||
bool protectRtcp(void* buffer, int* length);
|
bool protectRtcp(void* buffer, int* length);
|
||||||
bool unprotectRtp(void* buffer, int* length);
|
bool unprotectRtp(const void* src, size_t srcLength, void* dst, size_t* dstLength);
|
||||||
bool unprotectRtcp(void* buffer, int* length);
|
bool unprotectRtcp(const void* src, size_t srcLength, void* dst, size_t* dstLength);
|
||||||
|
|
||||||
|
|
||||||
static void initSrtp();
|
static void initSrtp();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "MT_Statistics.h"
|
#include "MT_Statistics.h"
|
||||||
#include "audio/Audio_Interface.h"
|
|
||||||
#include "helper/HL_Log.h"
|
|
||||||
#define LOG_SUBSYSTEM "Statistics"
|
#define LOG_SUBSYSTEM "Statistics"
|
||||||
|
|
||||||
using namespace MT;
|
using namespace MT;
|
||||||
|
|
@ -13,7 +12,7 @@ void JitterStatistics::process(jrtplib::RTPPacket* packet, int rate)
|
||||||
uint32_t timestamp = packet->GetTimestamp();
|
uint32_t timestamp = packet->GetTimestamp();
|
||||||
jrtplib::RTPTime receiveTime = packet->GetReceiveTime();
|
jrtplib::RTPTime receiveTime = packet->GetReceiveTime();
|
||||||
|
|
||||||
if (!mLastJitter.is_initialized())
|
if (!mLastJitter)
|
||||||
{
|
{
|
||||||
// First packet
|
// First packet
|
||||||
mReceiveTime = receiveTime;
|
mReceiveTime = receiveTime;
|
||||||
|
|
@ -55,8 +54,11 @@ void JitterStatistics::process(jrtplib::RTPPacket* packet, int rate)
|
||||||
mReceiveTime = receiveTime;
|
mReceiveTime = receiveTime;
|
||||||
mReceiveTimestamp = timestamp;
|
mReceiveTimestamp = timestamp;
|
||||||
|
|
||||||
// And mJitter are in seconds again
|
// And mJitter are in milliseconds again
|
||||||
mJitter.process(mLastJitter.value() / float(rate));
|
float jitter_s = mLastJitter.value() / (float(rate));
|
||||||
|
// std::cout << "Jitter (in seconds): " << std::dec << jitter_s << std::endl;
|
||||||
|
|
||||||
|
mJitter.process(jitter_s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,40 +67,10 @@ void JitterStatistics::process(jrtplib::RTPPacket* packet, int rate)
|
||||||
|
|
||||||
|
|
||||||
Statistics::Statistics()
|
Statistics::Statistics()
|
||||||
:mReceived(0), mSent(0), mReceivedRtp(0), mSentRtp(0),
|
{}
|
||||||
mReceivedRtcp(0), mSentRtcp(0), mDuplicatedRtp(0), mOldRtp(0), mIllegalRtp(0),
|
|
||||||
mPacketLoss(0), mJitter(0.0), mAudioTime(0), mDecodedSize(0), mSsrc(0), mPacketDropped(0)
|
|
||||||
{
|
|
||||||
mBitrateSwitchCounter = 0;
|
|
||||||
memset(mLoss, 0, sizeof mLoss);
|
|
||||||
|
|
||||||
// It is to keep track of statistics instance via grep | wc -l
|
|
||||||
//ICELogDebug(<< "Create statistics instance.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Statistics::~Statistics()
|
Statistics::~Statistics()
|
||||||
{
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
void Statistics::reset()
|
|
||||||
{
|
|
||||||
mReceived = 0;
|
|
||||||
mSent = 0;
|
|
||||||
mReceivedRtp = 0;
|
|
||||||
mSentRtp = 0;
|
|
||||||
mReceivedRtcp = 0;
|
|
||||||
mSentRtcp = 0;
|
|
||||||
mDuplicatedRtp = 0;
|
|
||||||
mOldRtp = 0;
|
|
||||||
mPacketLoss = 0;
|
|
||||||
mIllegalRtp = 0;
|
|
||||||
mJitter = 0.0;
|
|
||||||
mAudioTime = 0;
|
|
||||||
mPacketDropped = 0;
|
|
||||||
mDecodedSize = 0;
|
|
||||||
|
|
||||||
memset(mLoss, 0, sizeof mLoss);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Statistics::calculateBurstr(double* burstr, double* lossr) const
|
void Statistics::calculateBurstr(double* burstr, double* lossr) const
|
||||||
{
|
{
|
||||||
|
|
@ -127,7 +99,7 @@ void Statistics::calculateBurstr(double* burstr, double* lossr) const
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
*burstr = 0;
|
*burstr = 0;
|
||||||
//printf("total loss: %d\n", lost);
|
|
||||||
if (mReceivedRtp > 0)
|
if (mReceivedRtp > 0)
|
||||||
*lossr = (double)((double)lost / (double)mReceivedRtp);
|
*lossr = (double)((double)lost / (double)mReceivedRtp);
|
||||||
else
|
else
|
||||||
|
|
@ -199,16 +171,16 @@ Statistics& Statistics::operator += (const Statistics& src)
|
||||||
mCodecName = src.mCodecName;
|
mCodecName = src.mCodecName;
|
||||||
|
|
||||||
// Find minimal
|
// Find minimal
|
||||||
if (mFirstRtpTime.is_initialized())
|
if (mFirstRtpTime)
|
||||||
{
|
{
|
||||||
if (src.mFirstRtpTime.is_initialized())
|
if (src.mFirstRtpTime)
|
||||||
{
|
{
|
||||||
if (mFirstRtpTime.value() > src.mFirstRtpTime.value())
|
if (mFirstRtpTime.value() > src.mFirstRtpTime.value())
|
||||||
mFirstRtpTime = src.mFirstRtpTime;
|
mFirstRtpTime = src.mFirstRtpTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (src.mFirstRtpTime.is_initialized())
|
if (src.mFirstRtpTime)
|
||||||
mFirstRtpTime = src.mFirstRtpTime;
|
mFirstRtpTime = src.mFirstRtpTime;
|
||||||
|
|
||||||
mBitrateSwitchCounter += src.mBitrateSwitchCounter;
|
mBitrateSwitchCounter += src.mBitrateSwitchCounter;
|
||||||
|
|
@ -231,8 +203,8 @@ Statistics& Statistics::operator -= (const Statistics& src)
|
||||||
mOldRtp -= src.mOldRtp;
|
mOldRtp -= src.mOldRtp;
|
||||||
mPacketLoss -= src.mPacketLoss;
|
mPacketLoss -= src.mPacketLoss;
|
||||||
mPacketDropped -= src.mPacketDropped;
|
mPacketDropped -= src.mPacketDropped;
|
||||||
|
|
||||||
mAudioTime -= src.mAudioTime;
|
mAudioTime -= src.mAudioTime;
|
||||||
|
|
||||||
for (auto codecStat: src.mCodecCount)
|
for (auto codecStat: src.mCodecCount)
|
||||||
{
|
{
|
||||||
if (mCodecCount.find(codecStat.first) != mCodecCount.end())
|
if (mCodecCount.find(codecStat.first) != mCodecCount.end())
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,17 @@
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include "audio/Audio_DataWindow.h"
|
#include "audio/Audio_DataWindow.h"
|
||||||
#include "helper/HL_Optional.hpp"
|
#include "helper/HL_Optional.hpp"
|
||||||
#include "helper/HL_Statistics.h"
|
#include "helper/HL_Statistics.h"
|
||||||
|
#include "helper/HL_Types.h"
|
||||||
|
|
||||||
#include "jrtplib/src/rtptimeutilities.h"
|
#include "jrtplib/src/rtptimeutilities.h"
|
||||||
#include "jrtplib/src/rtppacket.h"
|
#include "jrtplib/src/rtppacket.h"
|
||||||
|
|
||||||
using std::experimental::optional;
|
|
||||||
|
|
||||||
namespace MT
|
namespace MT
|
||||||
{
|
{
|
||||||
|
|
@ -39,7 +41,7 @@ protected:
|
||||||
uint32_t mReceiveTimestamp = 0;
|
uint32_t mReceiveTimestamp = 0;
|
||||||
|
|
||||||
// It is classic jitter value in units
|
// It is classic jitter value in units
|
||||||
optional<float> mLastJitter;
|
std::optional<float> mLastJitter;
|
||||||
|
|
||||||
// Some statistics for jitter value in seconds
|
// Some statistics for jitter value in seconds
|
||||||
TestResult<float> mJitter;
|
TestResult<float> mJitter;
|
||||||
|
|
@ -51,39 +53,36 @@ protected:
|
||||||
class Statistics
|
class Statistics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
size_t mReceived, // Received traffic in bytes
|
size_t mReceived = 0, // Received traffic in bytes
|
||||||
mSent, // Sent traffic in bytes
|
mSent = 0, // Sent traffic in bytes
|
||||||
mReceivedRtp, // Number of received rtp packets
|
mReceivedRtp = 0, // Number of received rtp packets
|
||||||
mSentRtp, // Number of sent rtp packets
|
mSentRtp = 0, // Number of sent rtp packets
|
||||||
mReceivedRtcp, // Number of received rtcp packets
|
mReceivedRtcp = 0, // Number of received rtcp packets
|
||||||
mSentRtcp, // Number of sent rtcp packets
|
mSentRtcp = 0, // Number of sent rtcp packets
|
||||||
mDuplicatedRtp, // Number of received duplicated rtp packets
|
mDuplicatedRtp = 0, // Number of received duplicated rtp packets
|
||||||
mOldRtp, // Number of late rtp packets
|
mOldRtp = 0, // Number of late rtp packets
|
||||||
mPacketLoss, // Number of lost packets
|
mPacketLoss = 0, // Number of lost packets
|
||||||
mPacketDropped, // Number of dropped packets (due to time unsync when playing)б
|
mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б
|
||||||
mIllegalRtp; // Number of rtp packets with bad payload type
|
mIllegalRtp = 0; // Number of rtp packets with bad payload type
|
||||||
|
|
||||||
TestResult<float> mDecodingInterval, // Average interval on call to packet decode
|
TestResult<float> mDecodingInterval, // Average interval on call to packet decode
|
||||||
mDecodeRequested, // Average amount of requested audio frames to play
|
mDecodeRequested, // Average amount of requested audio frames to play
|
||||||
mPacketInterval; // Average interval between packet adding to jitter buffer
|
mPacketInterval; // Average interval between packet adding to jitter buffer
|
||||||
|
|
||||||
int mLoss[128]; // Every item is number of loss of corresping length
|
std::array<float, 128> mLoss = {0}; // Every item is number of loss of corresping length
|
||||||
size_t mAudioTime; // Decoded/found time in milliseconds
|
size_t mAudioTime = 0; // Decoded/found time in milliseconds
|
||||||
size_t mDecodedSize; // Number of decoded bytes
|
size_t mDecodedSize = 0; // Number of decoded bytes
|
||||||
uint16_t mSsrc; // Last known SSRC ID in a RTP stream
|
uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream
|
||||||
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
|
ice::NetworkAddress mRemotePeer; // Last known remote RTP address
|
||||||
|
|
||||||
// AMR codec bitrate switch counter
|
// AMR codec bitrate switch counter
|
||||||
int mBitrateSwitchCounter;
|
int mBitrateSwitchCounter = 0;
|
||||||
|
|
||||||
std::string mCodecName;
|
std::string mCodecName;
|
||||||
|
float mJitter = 0.0f; // Jitter
|
||||||
float mJitter; // Jitter
|
|
||||||
|
|
||||||
TestResult<float> mRttDelay; // RTT delay
|
TestResult<float> mRttDelay; // RTT delay
|
||||||
|
|
||||||
// Timestamp when first RTP packet has arrived
|
// Timestamp when first RTP packet has arrived
|
||||||
optional<std::chrono::system_clock::time_point> mFirstRtpTime;
|
std::optional<timepoint_t> mFirstRtpTime;
|
||||||
|
|
||||||
std::map<int, int> mCodecCount; // Stats on used codecs
|
std::map<int, int> mCodecCount; // Stats on used codecs
|
||||||
|
|
||||||
|
|
@ -98,11 +97,7 @@ public:
|
||||||
Statistics& operator += (const Statistics& src);
|
Statistics& operator += (const Statistics& src);
|
||||||
Statistics& operator -= (const Statistics& src);
|
Statistics& operator -= (const Statistics& src);
|
||||||
|
|
||||||
float mNetworkMos = 0.0;
|
float mNetworkMos = 0.0f;
|
||||||
#if defined(USE_PVQA_LIBRARY) && !defined(PVQA_SERVER)
|
|
||||||
float mPvqaMos = 0.0;
|
|
||||||
std::string mPvqaReport;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "../config.h"
|
#include "../engine_config.h"
|
||||||
#include "MT_WebRtc.h"
|
#include "MT_WebRtc.h"
|
||||||
#include "../helper/HL_Exception.h"
|
#include "../helper/HL_Exception.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Run manually to reformat a file:
|
||||||
|
# clang-format -i --style=file <file>
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: Google
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
|
IndentCaseLabels: false
|
||||||
|
AlwaysBreakTemplateDeclarations: false
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!--
|
||||||
|
Please make sure that the problem reproduces on the current master before
|
||||||
|
submitting an issue.
|
||||||
|
If possible please provide a repro on Compiler Explorer:
|
||||||
|
https://godbolt.org/z/fxccbh53W.
|
||||||
|
-->
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!--
|
||||||
|
Please read the contribution guidelines before submitting a pull request:
|
||||||
|
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
|
||||||
|
By submitting this pull request, you agree that your contributions are licensed
|
||||||
|
under the {fmt} license, and agree to future changes to the licensing.
|
||||||
|
-->
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: doc
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install doxygen python3-virtualenv
|
||||||
|
sudo npm install -g less clean-css
|
||||||
|
cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
env:
|
||||||
|
KEY: ${{secrets.KEY}}
|
||||||
|
run: $GITHUB_WORKSPACE/support/build-docs.py
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
name: linux
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
cxx: [g++-4.8, g++-10, clang++-9]
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
std: [11]
|
||||||
|
os: [ubuntu-18.04]
|
||||||
|
include:
|
||||||
|
- cxx: g++-4.8
|
||||||
|
install: sudo apt install g++-4.8
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-8
|
||||||
|
build_type: Debug
|
||||||
|
std: 14
|
||||||
|
install: sudo apt install g++-8
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-8
|
||||||
|
build_type: Debug
|
||||||
|
std: 17
|
||||||
|
install: sudo apt install g++-8
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-10
|
||||||
|
build_type: Debug
|
||||||
|
std: 17
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: g++-11
|
||||||
|
build_type: Debug
|
||||||
|
std: 20
|
||||||
|
os: ubuntu-20.04
|
||||||
|
install: sudo apt install g++-11
|
||||||
|
- cxx: clang++-8
|
||||||
|
build_type: Debug
|
||||||
|
std: 17
|
||||||
|
cxxflags: -stdlib=libc++
|
||||||
|
os: ubuntu-18.04
|
||||||
|
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
|
||||||
|
- cxx: clang++-9
|
||||||
|
build_type: Debug
|
||||||
|
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
|
||||||
|
std: 17
|
||||||
|
os: ubuntu-18.04
|
||||||
|
- cxx: clang++-11
|
||||||
|
build_type: Debug
|
||||||
|
std: 20
|
||||||
|
os: ubuntu-20.04
|
||||||
|
- cxx: clang++-11
|
||||||
|
build_type: Debug
|
||||||
|
std: 20
|
||||||
|
cxxflags: -stdlib=libc++
|
||||||
|
os: ubuntu-20.04
|
||||||
|
install: sudo apt install libc++-11-dev libc++abi-11-dev
|
||||||
|
- shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: |
|
||||||
|
${{matrix.install}}
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install locales-all
|
||||||
|
cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
env:
|
||||||
|
CXX: ${{matrix.cxx}}
|
||||||
|
CXXFLAGS: ${{matrix.cxxflags}}
|
||||||
|
run: |
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
|
||||||
|
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
|
||||||
|
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||||
|
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
threads=`nproc`
|
||||||
|
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: ctest -C ${{matrix.build_type}}
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: macos
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: macos-10.15
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
include:
|
||||||
|
- shared: -DBUILD_SHARED_LIBS=ON
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
|
||||||
|
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
|
||||||
|
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
threads=`sysctl -n hw.logicalcpu`
|
||||||
|
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: ctest -C ${{matrix.build_type}}
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
name: windows
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
# windows-2019 has MSVC 2019 installed;
|
||||||
|
# windows-2022 has MSVC 2022 installed:
|
||||||
|
# https://github.com/actions/virtual-environments.
|
||||||
|
os: [windows-2019]
|
||||||
|
platform: [Win32, x64]
|
||||||
|
toolset: [v140, v141, v142]
|
||||||
|
standard: [14, 17, 20]
|
||||||
|
shared: ["", -DBUILD_SHARED_LIBS=ON]
|
||||||
|
build_type: [Debug, Release]
|
||||||
|
exclude:
|
||||||
|
- { toolset: v140, standard: 17 }
|
||||||
|
- { toolset: v140, standard: 20 }
|
||||||
|
- { toolset: v141, standard: 14 }
|
||||||
|
- { toolset: v141, standard: 20 }
|
||||||
|
- { toolset: v142, standard: 14 }
|
||||||
|
- { platform: Win32, toolset: v140 }
|
||||||
|
- { platform: Win32, toolset: v141 }
|
||||||
|
- { platform: Win32, standard: 14 }
|
||||||
|
- { platform: Win32, standard: 20 }
|
||||||
|
- { platform: x64, toolset: v140, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
|
||||||
|
include:
|
||||||
|
- os: windows-2022
|
||||||
|
platform: x64
|
||||||
|
toolset: v143
|
||||||
|
build_type: Debug
|
||||||
|
standard: 20
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create Build Environment
|
||||||
|
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||||
|
|
||||||
|
- name: Configure
|
||||||
|
# Use a bash shell for $GITHUB_WORKSPACE.
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
cmake -A ${{matrix.platform}} -T ${{matrix.toolset}} \
|
||||||
|
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
|
||||||
|
${{matrix.shared}} -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||||
|
$GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: |
|
||||||
|
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
|
||||||
|
cmake --build . --config ${{matrix.build_type}} --parallel $threads
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
working-directory: ${{runner.workspace}}/build
|
||||||
|
run: ctest -C ${{matrix.build_type}} -V
|
||||||
|
env:
|
||||||
|
CTEST_OUTPUT_ON_FAILURE: True
|
||||||
|
|
||||||
|
mingw:
|
||||||
|
runs-on: windows-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: msys2 {0}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
sys: [ mingw64, mingw32, ucrt64 ]
|
||||||
|
steps:
|
||||||
|
- uses: msys2/setup-msys2@v2
|
||||||
|
with:
|
||||||
|
release: false
|
||||||
|
msystem: ${{matrix.sys}}
|
||||||
|
pacboy: cc:p cmake:p ninja:p lld:p
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Configure
|
||||||
|
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
env: { LDFLAGS: -fuse-ld=lld }
|
||||||
|
- name: Build
|
||||||
|
run: cmake --build ../build
|
||||||
|
- name: Test
|
||||||
|
run: ctest -j `nproc` --test-dir ../build
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
.vscode/
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
.externalNativeBuild/
|
||||||
|
.gradle/
|
||||||
|
gradle/
|
||||||
|
gradlew*
|
||||||
|
local.properties
|
||||||
|
build/
|
||||||
|
support/.cxx
|
||||||
|
|
||||||
|
bin/
|
||||||
|
/_CPack_Packages
|
||||||
|
/CMakeScripts
|
||||||
|
/doc/doxyxml
|
||||||
|
/doc/html
|
||||||
|
/doc/node_modules
|
||||||
|
virtualenv
|
||||||
|
/Testing
|
||||||
|
/install_manifest.txt
|
||||||
|
*~
|
||||||
|
*.a
|
||||||
|
*.so*
|
||||||
|
*.xcodeproj
|
||||||
|
*.zip
|
||||||
|
cmake_install.cmake
|
||||||
|
CPack*.cmake
|
||||||
|
fmt-*.cmake
|
||||||
|
CTestTestfile.cmake
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
FMT.build
|
||||||
|
Makefile
|
||||||
|
run-msbuild.bat
|
||||||
|
fmt.pc
|
||||||
|
|
@ -0,0 +1,387 @@
|
||||||
|
cmake_minimum_required(VERSION 3.1...3.18)
|
||||||
|
|
||||||
|
# Fallback for using newer policies on CMake <3.12.
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
|
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Determine if fmt is built as a subproject (using add_subdirectory)
|
||||||
|
# or if it is the master project.
|
||||||
|
if (NOT DEFINED FMT_MASTER_PROJECT)
|
||||||
|
set(FMT_MASTER_PROJECT OFF)
|
||||||
|
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||||
|
set(FMT_MASTER_PROJECT ON)
|
||||||
|
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Joins arguments and places the results in ${result_var}.
|
||||||
|
function(join result_var)
|
||||||
|
set(result "")
|
||||||
|
foreach (arg ${ARGN})
|
||||||
|
set(result "${result}${arg}")
|
||||||
|
endforeach ()
|
||||||
|
set(${result_var} "${result}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(enable_module target)
|
||||||
|
if (MSVC)
|
||||||
|
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||||
|
target_compile_options(${target}
|
||||||
|
PRIVATE /interface /ifcOutput ${BMI}
|
||||||
|
INTERFACE /reference fmt=${BMI})
|
||||||
|
endif ()
|
||||||
|
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||||
|
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
|
# Sets a cache variable with a docstring joined from multiple arguments:
|
||||||
|
# set(<variable> <value>... CACHE <type> <docstring>...)
|
||||||
|
# This allows splitting a long docstring for readability.
|
||||||
|
function(set_verbose)
|
||||||
|
# cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use
|
||||||
|
# list instead.
|
||||||
|
list(GET ARGN 0 var)
|
||||||
|
list(REMOVE_AT ARGN 0)
|
||||||
|
list(GET ARGN 0 val)
|
||||||
|
list(REMOVE_AT ARGN 0)
|
||||||
|
list(REMOVE_AT ARGN 0)
|
||||||
|
list(GET ARGN 0 type)
|
||||||
|
list(REMOVE_AT ARGN 0)
|
||||||
|
join(doc ${ARGN})
|
||||||
|
set(${var} ${val} CACHE ${type} ${doc})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Set the default CMAKE_BUILD_TYPE to Release.
|
||||||
|
# This should be done before the project command since the latter can set
|
||||||
|
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
||||||
|
if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||||
|
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
|
||||||
|
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||||
|
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
project(FMT CXX)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
|
||||||
|
"Installation directory for include files, a relative path that "
|
||||||
|
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||||
|
|
||||||
|
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||||
|
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||||
|
OFF)
|
||||||
|
|
||||||
|
# Options that control generation of various targets.
|
||||||
|
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||||
|
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||||
|
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||||
|
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||||
|
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||||
|
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||||
|
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||||
|
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
||||||
|
|
||||||
|
set(FMT_CAN_MODULE OFF)
|
||||||
|
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
||||||
|
# msvc 16.10-pre4
|
||||||
|
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
||||||
|
set(FMT_CAN_MODULE OFF)
|
||||||
|
endif ()
|
||||||
|
if (NOT FMT_CAN_MODULE)
|
||||||
|
set(FMT_MODULE OFF)
|
||||||
|
message(STATUS "Module support is disabled.")
|
||||||
|
endif ()
|
||||||
|
if (FMT_TEST AND FMT_MODULE)
|
||||||
|
# The tests require {fmt} to be compiled as traditional library
|
||||||
|
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||||
|
endif ()
|
||||||
|
set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
|
||||||
|
if (FMT_SYSTEM_HEADERS)
|
||||||
|
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Get version from core.h
|
||||||
|
file(READ include/fmt/core.h core_h)
|
||||||
|
if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
|
||||||
|
message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.")
|
||||||
|
endif ()
|
||||||
|
# Use math to skip leading zeros if any.
|
||||||
|
math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||||
|
math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||||
|
math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||||
|
join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
|
||||||
|
${CPACK_PACKAGE_VERSION_PATCH})
|
||||||
|
message(STATUS "Version: ${FMT_VERSION}")
|
||||||
|
|
||||||
|
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
|
|
||||||
|
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||||
|
|
||||||
|
include(cxx14)
|
||||||
|
include(JoinPaths)
|
||||||
|
|
||||||
|
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||||
|
if (${index} GREATER -1)
|
||||||
|
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
||||||
|
# compatibility with older CMake versions.
|
||||||
|
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
||||||
|
endif ()
|
||||||
|
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||||
|
|
||||||
|
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||||
|
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||||
|
"Preset for the export of private symbols")
|
||||||
|
set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS
|
||||||
|
hidden default)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN)
|
||||||
|
set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL
|
||||||
|
"Whether to add a compile flag to hide symbols of inline functions")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
||||||
|
-Wold-style-cast -Wundef
|
||||||
|
-Wredundant-decls -Wwrite-strings -Wpointer-arith
|
||||||
|
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
|
||||||
|
-Wcast-align
|
||||||
|
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||||
|
-Winvalid-pch -Woverloaded-virtual
|
||||||
|
-Wconversion -Wundef
|
||||||
|
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
|
||||||
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||||
|
-Wno-dangling-else -Wno-unused-local-typedefs)
|
||||||
|
endif ()
|
||||||
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
|
||||||
|
-Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
|
||||||
|
-Wvector-operation-performance -Wsized-deallocation -Wshadow)
|
||||||
|
endif ()
|
||||||
|
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||||
|
-Wnull-dereference -Wduplicated-cond)
|
||||||
|
endif ()
|
||||||
|
set(WERROR_FLAG -Werror)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
|
||||||
|
-Wdeprecated -Wweak-vtables -Wshadow
|
||||||
|
-Wno-gnu-zero-variadic-macro-arguments)
|
||||||
|
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||||
|
if (HAS_NULLPTR_WARNING)
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||||
|
-Wzero-as-null-pointer-constant)
|
||||||
|
endif ()
|
||||||
|
set(WERROR_FLAG -Werror)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
set(PEDANTIC_COMPILE_FLAGS /W3)
|
||||||
|
set(WERROR_FLAG /WX)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||||
|
# If Microsoft SDK is installed create script run-msbuild.bat that
|
||||||
|
# calls SetEnv.cmd to set up build environment and runs msbuild.
|
||||||
|
# It is useful when building Visual Studio projects with the SDK
|
||||||
|
# toolchain rather than Visual Studio.
|
||||||
|
include(FindSetEnv)
|
||||||
|
if (WINSDK_SETENV)
|
||||||
|
set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"")
|
||||||
|
endif ()
|
||||||
|
# Set FrameworkPathOverride to get rid of MSB3644 warnings.
|
||||||
|
join(netfxpath
|
||||||
|
"C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\"
|
||||||
|
".NETFramework\\v4.0")
|
||||||
|
file(WRITE run-msbuild.bat "
|
||||||
|
${MSBUILD_SETUP}
|
||||||
|
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
function(add_headers VAR)
|
||||||
|
set(headers ${${VAR}})
|
||||||
|
foreach (header ${ARGN})
|
||||||
|
set(headers ${headers} include/fmt/${header})
|
||||||
|
endforeach()
|
||||||
|
set(${VAR} ${headers} PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Define the fmt library, its includes and the needed defines.
|
||||||
|
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||||
|
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
||||||
|
xchar.h)
|
||||||
|
if (FMT_MODULE)
|
||||||
|
set(FMT_SOURCES src/fmt.cc)
|
||||||
|
elseif (FMT_OS)
|
||||||
|
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||||
|
else()
|
||||||
|
set(FMT_SOURCES src/format.cc)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||||
|
add_library(fmt::fmt ALIAS fmt)
|
||||||
|
|
||||||
|
if (FMT_WERROR)
|
||||||
|
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
||||||
|
endif ()
|
||||||
|
if (FMT_PEDANTIC)
|
||||||
|
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||||
|
endif ()
|
||||||
|
if (FMT_MODULE)
|
||||||
|
enable_module(fmt)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||||
|
|
||||||
|
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||||
|
|
||||||
|
set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
|
||||||
|
|
||||||
|
set_target_properties(fmt PROPERTIES
|
||||||
|
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||||
|
PUBLIC_HEADER "${FMT_HEADERS}"
|
||||||
|
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
|
||||||
|
|
||||||
|
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
|
||||||
|
# property because it's not set by default.
|
||||||
|
set(FMT_LIB_NAME fmt)
|
||||||
|
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (BUILD_SHARED_LIBS)
|
||||||
|
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||||
|
endif ()
|
||||||
|
if (FMT_SAFE_DURATION_CAST)
|
||||||
|
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(fmt-header-only INTERFACE)
|
||||||
|
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||||
|
|
||||||
|
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||||
|
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||||
|
|
||||||
|
target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
|
||||||
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||||
|
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||||
|
|
||||||
|
# Install targets.
|
||||||
|
if (FMT_INSTALL)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||||
|
"Installation directory for cmake files, a relative path that "
|
||||||
|
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
|
||||||
|
"path.")
|
||||||
|
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||||
|
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||||
|
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
||||||
|
set(targets_export_name fmt-targets)
|
||||||
|
|
||||||
|
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||||
|
"Installation directory for libraries, a relative path that "
|
||||||
|
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||||
|
|
||||||
|
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||||
|
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||||
|
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||||
|
"absolute path.")
|
||||||
|
|
||||||
|
# Generate the version, config and target files into the build directory.
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${version_config}
|
||||||
|
VERSION ${FMT_VERSION}
|
||||||
|
COMPATIBILITY AnyNewerVersion)
|
||||||
|
|
||||||
|
join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}")
|
||||||
|
join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}")
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
"${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
|
||||||
|
"${pkgconfig}"
|
||||||
|
@ONLY)
|
||||||
|
configure_package_config_file(
|
||||||
|
${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
|
||||||
|
${project_config}
|
||||||
|
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||||
|
|
||||||
|
set(INSTALL_TARGETS fmt fmt-header-only)
|
||||||
|
|
||||||
|
# Install the library and headers.
|
||||||
|
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||||
|
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||||
|
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||||
|
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
||||||
|
FRAMEWORK DESTINATION "."
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
|
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||||
|
# imported targets.
|
||||||
|
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
||||||
|
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||||
|
|
||||||
|
# Install version, config and target files.
|
||||||
|
install(
|
||||||
|
FILES ${project_config} ${version_config}
|
||||||
|
DESTINATION ${FMT_CMAKE_DIR})
|
||||||
|
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||||
|
NAMESPACE fmt::)
|
||||||
|
|
||||||
|
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||||
|
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||||
|
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (FMT_DOC)
|
||||||
|
add_subdirectory(doc)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (FMT_TEST)
|
||||||
|
enable_testing()
|
||||||
|
add_subdirectory(test)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Control fuzzing independent of the unit tests.
|
||||||
|
if (FMT_FUZZ)
|
||||||
|
add_subdirectory(test/fuzzing)
|
||||||
|
|
||||||
|
# The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
|
||||||
|
# mode and make fuzzing practically possible. It is similar to
|
||||||
|
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
|
||||||
|
# avoid interfering with fuzzing of projects that use {fmt}.
|
||||||
|
# See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
|
||||||
|
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||||
|
if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
|
||||||
|
# Get the list of ignored files from .gitignore.
|
||||||
|
file (STRINGS ${gitignore} lines)
|
||||||
|
list(REMOVE_ITEM lines /doc/html)
|
||||||
|
foreach (line ${lines})
|
||||||
|
string(REPLACE "." "[.]" line "${line}")
|
||||||
|
string(REPLACE "*" ".*" line "${line}")
|
||||||
|
set(ignored_files ${ignored_files} "${line}$" "${line}/")
|
||||||
|
endforeach ()
|
||||||
|
set(ignored_files ${ignored_files}
|
||||||
|
/.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)
|
||||||
|
|
||||||
|
set(CPACK_SOURCE_GENERATOR ZIP)
|
||||||
|
set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
|
||||||
|
set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
|
||||||
|
set(CPACK_PACKAGE_NAME fmt)
|
||||||
|
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
|
||||||
|
include(CPack)
|
||||||
|
endif ()
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
Contributing to {fmt}
|
||||||
|
=====================
|
||||||
|
|
||||||
|
By submitting a pull request or a patch, you represent that you have the right
|
||||||
|
to license your contribution to the {fmt} project owners and the community,
|
||||||
|
agree that your contributions are licensed under the {fmt} license, and agree
|
||||||
|
to future changes to the licensing.
|
||||||
|
|
||||||
|
All C++ code must adhere to [Google C++ Style Guide](
|
||||||
|
https://google.github.io/styleguide/cppguide.html) with the following
|
||||||
|
exceptions:
|
||||||
|
|
||||||
|
* Exceptions are permitted
|
||||||
|
* snake_case should be used instead of UpperCamelCase for function and type
|
||||||
|
names
|
||||||
|
|
||||||
|
All documentation must adhere to the [Google Developer Documentation Style
|
||||||
|
Guide](https://developers.google.com/style).
|
||||||
|
|
||||||
|
Thanks for contributing!
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
--- Optional exception to the license ---
|
||||||
|
|
||||||
|
As an exception, if, as a result of your compiling your source code, portions
|
||||||
|
of this Software are embedded into a machine-executable object form of such
|
||||||
|
source code, you may redistribute such embedded portions in such object form
|
||||||
|
without including the above copyright and permission notices.
|
||||||
|
|
@ -0,0 +1,531 @@
|
||||||
|
.. image:: https://user-images.githubusercontent.com/
|
||||||
|
576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png
|
||||||
|
:width: 25%
|
||||||
|
:alt: {fmt}
|
||||||
|
|
||||||
|
.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
|
||||||
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
|
||||||
|
|
||||||
|
.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
|
||||||
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
|
||||||
|
|
||||||
|
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
||||||
|
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
||||||
|
|
||||||
|
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
||||||
|
:alt: fmt is continuously fuzzed at oss-fuzz
|
||||||
|
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
|
||||||
|
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
|
||||||
|
Summary&q=proj%3Dfmt&can=1
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
||||||
|
:alt: Ask questions at StackOverflow with the tag fmt
|
||||||
|
:target: https://stackoverflow.com/questions/tagged/fmt
|
||||||
|
|
||||||
|
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||||
|
alternative to C stdio and C++ iostreams.
|
||||||
|
|
||||||
|
If you like this project, please consider donating to one of the funds that
|
||||||
|
help victims of the war in Ukraine: https://www.stopputin.net/.
|
||||||
|
|
||||||
|
`Documentation <https://fmt.dev>`__
|
||||||
|
|
||||||
|
`Cheat Sheets <https://hackingcpp.com/cpp/libs/fmt.html>`__
|
||||||
|
|
||||||
|
Q&A: ask questions on `StackOverflow with the tag fmt
|
||||||
|
<https://stackoverflow.com/questions/tagged/fmt>`_.
|
||||||
|
|
||||||
|
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
|
||||||
|
for localization
|
||||||
|
* Implementation of `C++20 std::format
|
||||||
|
<https://en.cppreference.com/w/cpp/utility/format>`__
|
||||||
|
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
||||||
|
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||||
|
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
|
||||||
|
round-trip guarantees
|
||||||
|
* Safe `printf implementation
|
||||||
|
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
||||||
|
extension for positional arguments
|
||||||
|
* Extensibility: `support for user-defined types
|
||||||
|
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
|
||||||
|
* High performance: faster than common standard library implementations of
|
||||||
|
``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
|
||||||
|
and `Converting a hundred million integers to strings per second
|
||||||
|
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
||||||
|
* Small code size both in terms of source code with the minimum configuration
|
||||||
|
consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
|
||||||
|
and compiled code; see `Compile time and code bloat`_
|
||||||
|
* Reliability: the library has an extensive set of `tests
|
||||||
|
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
|
||||||
|
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
|
||||||
|
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
|
||||||
|
* Safety: the library is fully type safe, errors in format strings can be
|
||||||
|
reported at compile time, automatic memory management prevents buffer overflow
|
||||||
|
errors
|
||||||
|
* Ease of use: small self-contained code base, no external dependencies,
|
||||||
|
permissive MIT `license
|
||||||
|
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||||
|
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||||
|
consistent output across platforms and support for older compilers
|
||||||
|
* Clean warning-free codebase even on high warning levels such as
|
||||||
|
``-Wall -Wextra -pedantic``
|
||||||
|
* Locale-independence by default
|
||||||
|
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
|
||||||
|
|
||||||
|
See the `documentation <https://fmt.dev>`_ for more details.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fmt::print("Hello, world!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
std::string s = fmt::format("The answer is {}.", 42);
|
||||||
|
// s == "The answer is 42."
|
||||||
|
|
||||||
|
**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||||
|
// s == "I'd rather be happy than right."
|
||||||
|
|
||||||
|
**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||||
|
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||||
|
}
|
||||||
|
|
||||||
|
Output::
|
||||||
|
|
||||||
|
Default format: 42s 100ms
|
||||||
|
strftime-like format: 03:15:30
|
||||||
|
|
||||||
|
**Print a container** (`run <https://godbolt.org/z/MxM1YqjE7>`_)
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <fmt/ranges.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::vector<int> v = {1, 2, 3};
|
||||||
|
fmt::print("{}\n", v);
|
||||||
|
}
|
||||||
|
|
||||||
|
Output::
|
||||||
|
|
||||||
|
[1, 2, 3]
|
||||||
|
|
||||||
|
**Check a format string at compile time**
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
std::string s = fmt::format("{:d}", "I am not a number");
|
||||||
|
|
||||||
|
This gives a compile-time error in C++20 because ``d`` is an invalid format
|
||||||
|
specifier for a string.
|
||||||
|
|
||||||
|
**Write a file from a single thread**
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/os.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto out = fmt::output_file("guide.txt");
|
||||||
|
out.print("Don't {}", "Panic");
|
||||||
|
}
|
||||||
|
|
||||||
|
This can be `5 to 9 times faster than fprintf
|
||||||
|
<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
|
||||||
|
|
||||||
|
**Print with colors and text styles**
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/color.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
|
||||||
|
"Hello, {}!\n", "world");
|
||||||
|
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
|
||||||
|
fmt::emphasis::underline, "Hello, {}!\n", "мир");
|
||||||
|
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
|
||||||
|
"Hello, {}!\n", "世界");
|
||||||
|
}
|
||||||
|
|
||||||
|
Output on a modern terminal:
|
||||||
|
|
||||||
|
.. image:: https://user-images.githubusercontent.com/
|
||||||
|
576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
|
||||||
|
|
||||||
|
Benchmarks
|
||||||
|
----------
|
||||||
|
|
||||||
|
Speed tests
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
================= ============= ===========
|
||||||
|
Library Method Run Time, s
|
||||||
|
================= ============= ===========
|
||||||
|
libc printf 1.04
|
||||||
|
libc++ std::ostream 3.05
|
||||||
|
{fmt} 6.1.1 fmt::print 0.75
|
||||||
|
Boost Format 1.67 boost::format 7.24
|
||||||
|
Folly Format folly::format 2.23
|
||||||
|
================= ============= ===========
|
||||||
|
|
||||||
|
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
||||||
|
|
||||||
|
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||||
|
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
||||||
|
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||||
|
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
||||||
|
further details refer to the `source
|
||||||
|
<https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc>`_.
|
||||||
|
|
||||||
|
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
|
||||||
|
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||||
|
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
||||||
|
`ryu <https://github.com/ulfjack/ryu>`_:
|
||||||
|
|
||||||
|
.. image:: https://user-images.githubusercontent.com/576385/
|
||||||
|
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
|
||||||
|
:target: https://fmt.dev/unknown_mac64_clang12.0.html
|
||||||
|
|
||||||
|
Compile time and code bloat
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The script `bloat-test.py
|
||||||
|
<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
|
||||||
|
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
||||||
|
tests compile time and code bloat for nontrivial projects.
|
||||||
|
It generates 100 translation units and uses ``printf()`` or its alternative
|
||||||
|
five times in each to simulate a medium sized project. The resulting
|
||||||
|
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
||||||
|
macOS Sierra, best of three) is shown in the following tables.
|
||||||
|
|
||||||
|
**Optimized build (-O3)**
|
||||||
|
|
||||||
|
============= =============== ==================== ==================
|
||||||
|
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||||
|
============= =============== ==================== ==================
|
||||||
|
printf 2.6 29 26
|
||||||
|
printf+string 16.4 29 26
|
||||||
|
iostreams 31.1 59 55
|
||||||
|
{fmt} 19.0 37 34
|
||||||
|
Boost Format 91.9 226 203
|
||||||
|
Folly Format 115.7 101 88
|
||||||
|
============= =============== ==================== ==================
|
||||||
|
|
||||||
|
As you can see, {fmt} has 60% less overhead in terms of resulting binary code
|
||||||
|
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
||||||
|
and Folly Format have the largest overheads.
|
||||||
|
|
||||||
|
``printf+string`` is the same as ``printf`` but with extra ``<string>``
|
||||||
|
include to measure the overhead of the latter.
|
||||||
|
|
||||||
|
**Non-optimized build**
|
||||||
|
|
||||||
|
============= =============== ==================== ==================
|
||||||
|
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||||
|
============= =============== ==================== ==================
|
||||||
|
printf 2.2 33 30
|
||||||
|
printf+string 16.0 33 30
|
||||||
|
iostreams 28.3 56 52
|
||||||
|
{fmt} 18.2 59 50
|
||||||
|
Boost Format 54.1 365 303
|
||||||
|
Folly Format 79.9 445 430
|
||||||
|
============= =============== ==================== ==================
|
||||||
|
|
||||||
|
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
||||||
|
compare formatting function overhead only. Boost Format is a
|
||||||
|
header-only library so it doesn't provide any linkage options.
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Please refer to `Building the library`__ for the instructions on how to build
|
||||||
|
the library and run the unit tests.
|
||||||
|
|
||||||
|
__ https://fmt.dev/latest/usage.html#building-the-library
|
||||||
|
|
||||||
|
Benchmarks reside in a separate repository,
|
||||||
|
`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
|
||||||
|
so to run the benchmarks you first need to clone this repository and
|
||||||
|
generate Makefiles with CMake::
|
||||||
|
|
||||||
|
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
|
||||||
|
$ cd format-benchmark
|
||||||
|
$ cmake .
|
||||||
|
|
||||||
|
Then you can run the speed test::
|
||||||
|
|
||||||
|
$ make speed-test
|
||||||
|
|
||||||
|
or the bloat test::
|
||||||
|
|
||||||
|
$ make bloat-test
|
||||||
|
|
||||||
|
Migrating code
|
||||||
|
--------------
|
||||||
|
|
||||||
|
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
|
||||||
|
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
|
||||||
|
``fmt::print``.
|
||||||
|
|
||||||
|
Projects using this library
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
|
||||||
|
real-time strategy game
|
||||||
|
|
||||||
|
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
|
||||||
|
|
||||||
|
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||||
|
an open-source library for mathematical programming
|
||||||
|
|
||||||
|
* `Aseprite <https://github.com/aseprite/aseprite>`_:
|
||||||
|
animated sprite editor & pixel art tool
|
||||||
|
|
||||||
|
* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
|
||||||
|
operations suite
|
||||||
|
|
||||||
|
* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
|
||||||
|
|
||||||
|
* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
|
||||||
|
|
||||||
|
* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
|
||||||
|
|
||||||
|
* `ccache <https://ccache.dev/>`_: a compiler cache
|
||||||
|
|
||||||
|
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
|
||||||
|
management system
|
||||||
|
|
||||||
|
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||||
|
vehicle
|
||||||
|
|
||||||
|
* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
|
||||||
|
for nonlinear dynamical systems (MIT)
|
||||||
|
|
||||||
|
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||||
|
(Lyft)
|
||||||
|
|
||||||
|
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||||
|
|
||||||
|
* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
|
||||||
|
logging library with latency in nanoseconds
|
||||||
|
|
||||||
|
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
||||||
|
|
||||||
|
* `GemRB <https://gemrb.org/>`_: a portable open-source implementation of
|
||||||
|
Bioware’s Infinity Engine
|
||||||
|
|
||||||
|
* `Grand Mountain Adventure
|
||||||
|
<https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/>`_:
|
||||||
|
a beautiful open-world ski & snowboarding game
|
||||||
|
|
||||||
|
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||||
|
Player vs Player Gaming Network with tweaks
|
||||||
|
|
||||||
|
* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
|
||||||
|
engine
|
||||||
|
|
||||||
|
* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
|
||||||
|
|
||||||
|
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
|
||||||
|
|
||||||
|
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
||||||
|
|
||||||
|
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
||||||
|
research programming language for concurrent ownership
|
||||||
|
|
||||||
|
* `MongoDB <https://mongodb.com/>`_: distributed document database
|
||||||
|
|
||||||
|
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
|
||||||
|
generate randomized datasets
|
||||||
|
|
||||||
|
* `OpenSpace <https://openspaceproject.com/>`_: an open-source
|
||||||
|
astrovisualization framework
|
||||||
|
|
||||||
|
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
||||||
|
an MMO server, compatible with most Ultima Online clients
|
||||||
|
|
||||||
|
* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
|
||||||
|
learning library
|
||||||
|
|
||||||
|
* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
|
||||||
|
associative database
|
||||||
|
|
||||||
|
* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
|
||||||
|
|
||||||
|
* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
|
||||||
|
navigation, and executing complex multi-line terminal command sequences
|
||||||
|
|
||||||
|
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
|
||||||
|
proxy
|
||||||
|
|
||||||
|
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
|
||||||
|
for mission critical systems written in C++
|
||||||
|
|
||||||
|
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
|
||||||
|
library
|
||||||
|
|
||||||
|
* `Salesforce Analytics Cloud
|
||||||
|
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||||
|
business intelligence software
|
||||||
|
|
||||||
|
* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
|
||||||
|
that can handle 1 million transactions per second on a single server
|
||||||
|
|
||||||
|
* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
|
||||||
|
framework for high-performance server applications on modern hardware
|
||||||
|
|
||||||
|
* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
|
||||||
|
|
||||||
|
* `Stellar <https://www.stellar.org/>`_: financial platform
|
||||||
|
|
||||||
|
* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
|
||||||
|
|
||||||
|
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
||||||
|
MMORPG framework
|
||||||
|
|
||||||
|
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
||||||
|
terminal
|
||||||
|
|
||||||
|
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||||
|
|
||||||
|
If you are aware of other projects using this library, please let me know
|
||||||
|
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||||
|
`issue <https://github.com/fmtlib/fmt/issues>`_.
|
||||||
|
|
||||||
|
Motivation
|
||||||
|
----------
|
||||||
|
|
||||||
|
So why yet another formatting library?
|
||||||
|
|
||||||
|
There are plenty of methods for doing this task, from standard ones like
|
||||||
|
the printf family of function and iostreams to Boost Format and FastFormat
|
||||||
|
libraries. The reason for creating a new library is that every existing
|
||||||
|
solution that I found either had serious issues or didn't provide
|
||||||
|
all the features I needed.
|
||||||
|
|
||||||
|
printf
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
The good thing about ``printf`` is that it is pretty fast and readily available
|
||||||
|
being a part of the C standard library. The main drawback is that it
|
||||||
|
doesn't support user-defined types. ``printf`` also has safety issues although
|
||||||
|
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
|
||||||
|
<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||||
|
There is a POSIX extension that adds positional arguments required for
|
||||||
|
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
|
||||||
|
to ``printf`` but it is not a part of C99 and may not be available on some
|
||||||
|
platforms.
|
||||||
|
|
||||||
|
iostreams
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
The main issue with iostreams is best illustrated with an example:
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
|
||||||
|
|
||||||
|
which is a lot of typing compared to printf:
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
printf("%.2f\n", 1.23456);
|
||||||
|
|
||||||
|
Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
|
||||||
|
don't support positional arguments by design.
|
||||||
|
|
||||||
|
The good part is that iostreams support user-defined types and are safe although
|
||||||
|
error handling is awkward.
|
||||||
|
|
||||||
|
Boost Format
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a very powerful library which supports both ``printf``-like format
|
||||||
|
strings and positional arguments. Its main drawback is performance. According to
|
||||||
|
various benchmarks, it is much slower than other methods considered here. Boost
|
||||||
|
Format also has excessive build times and severe code bloat issues (see
|
||||||
|
`Benchmarks`_).
|
||||||
|
|
||||||
|
FastFormat
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
This is an interesting library which is fast, safe and has positional arguments.
|
||||||
|
However, it has significant limitations, citing its author:
|
||||||
|
|
||||||
|
Three features that have no hope of being accommodated within the
|
||||||
|
current design are:
|
||||||
|
|
||||||
|
* Leading zeros (or any other non-space padding)
|
||||||
|
* Octal/hexadecimal encoding
|
||||||
|
* Runtime width/alignment specification
|
||||||
|
|
||||||
|
It is also quite big and has a heavy dependency, STLSoft, which might be too
|
||||||
|
restrictive for using it in some projects.
|
||||||
|
|
||||||
|
Boost Spirit.Karma
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is not really a formatting library but I decided to include it here for
|
||||||
|
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
||||||
|
with arguments. The library is pretty fast, but slower on integer formatting
|
||||||
|
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
|
||||||
|
see `Converting a hundred million integers to strings per second
|
||||||
|
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
{fmt} is distributed under the MIT `license
|
||||||
|
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||||
|
|
||||||
|
Documentation License
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
|
||||||
|
section in the documentation is based on the one from Python `string module
|
||||||
|
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
|
||||||
|
For this reason the documentation is distributed under the Python Software
|
||||||
|
Foundation license available in `doc/python-license.txt
|
||||||
|
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||||
|
It only applies if you distribute the documentation of {fmt}.
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
||||||
|
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
|
||||||
|
<https://github.com/foonathan>`_) with contributions from many other people.
|
||||||
|
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
||||||
|
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
|
||||||
|
Let us know if your contribution is not listed or mentioned incorrectly and
|
||||||
|
we'll make it right.
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
find_program(DOXYGEN doxygen
|
||||||
|
PATHS "$ENV{ProgramFiles}/doxygen/bin"
|
||||||
|
"$ENV{ProgramFiles\(x86\)}/doxygen/bin")
|
||||||
|
if (NOT DOXYGEN)
|
||||||
|
message(STATUS "Target 'doc' disabled (requires doxygen)")
|
||||||
|
return ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Find the Python interpreter and set the PYTHON_EXECUTABLE variable.
|
||||||
|
if (CMAKE_VERSION VERSION_LESS 3.12)
|
||||||
|
# This logic is deprecated in CMake after 3.12.
|
||||||
|
find_package(PythonInterp QUIET REQUIRED)
|
||||||
|
else ()
|
||||||
|
find_package(Python QUIET REQUIRED)
|
||||||
|
set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
add_custom_target(doc
|
||||||
|
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
|
||||||
|
${FMT_VERSION}
|
||||||
|
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL
|
||||||
|
PATTERN ".doctrees" EXCLUDE)
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
/* -- breathe specific styles ----------------------------------------------- */
|
||||||
|
|
||||||
|
/* So enum value descriptions are displayed inline to the item */
|
||||||
|
.breatheenumvalues li tt + p {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* So parameter descriptions are displayed inline to the item */
|
||||||
|
.breatheparameterlist li tt + p {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container .breathe-sectiondef {
|
||||||
|
width: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-btn {
|
||||||
|
border: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jumbotron {
|
||||||
|
background-size: 100% 4px;
|
||||||
|
background-repeat: repeat-y;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,229 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata></metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
|
||||||
|
<font-face units-per-em="1200" ascent="960" descent="-240" />
|
||||||
|
<missing-glyph horiz-adv-x="500" />
|
||||||
|
<glyph />
|
||||||
|
<glyph />
|
||||||
|
<glyph unicode="
" />
|
||||||
|
<glyph unicode=" " />
|
||||||
|
<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
|
||||||
|
<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
|
||||||
|
<glyph unicode=" " />
|
||||||
|
<glyph unicode=" " horiz-adv-x="652" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="1304" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="652" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="1304" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="434" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="326" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="217" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="217" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="163" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="260" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="72" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="260" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="326" />
|
||||||
|
<glyph unicode="€" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
|
||||||
|
<glyph unicode="−" d="M200 400h900v300h-900v-300z" />
|
||||||
|
<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
|
||||||
|
<glyph unicode="☁" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
|
||||||
|
<glyph unicode="✉" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
|
||||||
|
<glyph unicode="✏" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
|
||||||
|
<glyph unicode="" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
|
||||||
|
<glyph unicode="" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q18 -55 86 -75.5t147 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
|
||||||
|
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
|
||||||
|
<glyph unicode="" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
|
||||||
|
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
|
||||||
|
<glyph unicode="" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
|
||||||
|
<glyph unicode="" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
|
||||||
|
<glyph unicode="" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
|
||||||
|
<glyph unicode="" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||||
|
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||||
|
<glyph unicode="" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||||
|
<glyph unicode="" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
|
||||||
|
<glyph unicode="" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
|
||||||
|
<glyph unicode="" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
|
||||||
|
<glyph unicode="" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
|
||||||
|
<glyph unicode="" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
|
||||||
|
<glyph unicode="" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
|
||||||
|
<glyph unicode="" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
|
||||||
|
<glyph unicode="" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
|
||||||
|
<glyph unicode="" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
|
||||||
|
<glyph unicode="" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
|
||||||
|
<glyph unicode="" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
|
||||||
|
<glyph unicode="" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
|
||||||
|
<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
|
||||||
|
<glyph unicode="" d="M0 25v475l200 700h800l199 -700l1 -475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
|
||||||
|
<glyph unicode="" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
|
||||||
|
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
|
||||||
|
<glyph unicode="" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
|
||||||
|
<glyph unicode="" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
|
||||||
|
<glyph unicode="" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
|
||||||
|
<glyph unicode="" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
|
||||||
|
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
|
||||||
|
<glyph unicode="" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
|
||||||
|
<glyph unicode="" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
|
||||||
|
<glyph unicode="" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
|
||||||
|
<glyph unicode="" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
|
||||||
|
<glyph unicode="" d="M0 700l1 475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
|
||||||
|
<glyph unicode="" d="M1 700l1 475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
|
||||||
|
<glyph unicode="" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
|
||||||
|
<glyph unicode="" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
|
||||||
|
<glyph unicode="" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
|
||||||
|
<glyph unicode="" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
|
||||||
|
<glyph unicode="" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
|
||||||
|
<glyph unicode="" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v71l471 -1q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
|
||||||
|
<glyph unicode="" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
|
||||||
|
<glyph unicode="" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
|
||||||
|
<glyph unicode="" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
|
||||||
|
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||||
|
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
|
||||||
|
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
|
||||||
|
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
|
||||||
|
<glyph unicode="" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
|
||||||
|
<glyph unicode="" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
|
||||||
|
<glyph unicode="" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
|
||||||
|
<glyph unicode="" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
|
||||||
|
<glyph unicode="" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
|
||||||
|
<glyph unicode="" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
|
||||||
|
<glyph unicode="" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
|
||||||
|
<glyph unicode="" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 139t-64 210zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q61 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l567 567l-137 137l-430 -431l-146 147z" />
|
||||||
|
<glyph unicode="" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
|
||||||
|
<glyph unicode="" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||||
|
<glyph unicode="" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||||
|
<glyph unicode="" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
|
||||||
|
<glyph unicode="" d="M200 0l900 550l-900 550v-1100z" />
|
||||||
|
<glyph unicode="" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||||
|
<glyph unicode="" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
|
||||||
|
<glyph unicode="" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
|
||||||
|
<glyph unicode="" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
|
||||||
|
<glyph unicode="" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
|
||||||
|
<glyph unicode="" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
|
||||||
|
<glyph unicode="" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
|
||||||
|
<glyph unicode="" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
|
||||||
|
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
|
||||||
|
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM300 500h600v200h-600v-200z" />
|
||||||
|
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141 z" />
|
||||||
|
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
|
||||||
|
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM364 700h143q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5 q19 0 30 -10t11 -26q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-50 0 -90.5 -12t-75 -38.5t-53.5 -74.5t-19 -114zM500 300h200v100h-200 v-100z" />
|
||||||
|
<glyph unicode="" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M0 500v200h195q31 125 98.5 199.5t206.5 100.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200v-206 q149 48 201 206h-201v200h200q-25 74 -75.5 127t-124.5 77v-204h-200v203q-75 -23 -130 -77t-79 -126h209v-200h-210z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
|
||||||
|
<glyph unicode="" d="M0 547l600 453v-300h600v-300h-600v-301z" />
|
||||||
|
<glyph unicode="" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
|
||||||
|
<glyph unicode="" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
|
||||||
|
<glyph unicode="" d="M104 600h296v600h300v-600h298l-449 -600z" />
|
||||||
|
<glyph unicode="" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
|
||||||
|
<glyph unicode="" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
|
||||||
|
<glyph unicode="" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
|
||||||
|
<glyph unicode="" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5h-207q-21 0 -33 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111q1 1 1 6.5t-1.5 15t-3.5 17.5l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6 h-111v-100zM100 0h400v400h-400v-400zM200 900q-3 0 14 48t36 96l18 47l213 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
|
||||||
|
<glyph unicode="" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
|
||||||
|
<glyph unicode="" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
|
||||||
|
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
|
||||||
|
<glyph unicode="" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
|
||||||
|
<glyph unicode="" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 34 -48 36.5t-48 -29.5l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -20 -13 -28.5t-32 0.5l-94 78h-222l-94 -78q-19 -9 -32 -0.5t-13 28.5 v64q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
|
||||||
|
<glyph unicode="" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
|
||||||
|
<glyph unicode="" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
|
||||||
|
<glyph unicode="" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
|
||||||
|
<glyph unicode="" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
|
||||||
|
<glyph unicode="" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
|
||||||
|
<glyph unicode="" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
|
||||||
|
<glyph unicode="" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
|
||||||
|
<glyph unicode="" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
|
||||||
|
<glyph unicode="" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
|
||||||
|
<glyph unicode="" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
|
||||||
|
<glyph unicode="" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
|
||||||
|
<glyph unicode="" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
|
||||||
|
<glyph unicode="" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
|
||||||
|
<glyph unicode="" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM100 500v250v8v8v7t0.5 7t1.5 5.5t2 5t3 4t4.5 3.5t6 1.5t7.5 0.5h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35 q-55 337 -55 351zM1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
|
||||||
|
<glyph unicode="" d="M74 350q0 21 13.5 35.5t33.5 14.5h18l117 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5q-18 -36 -18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-8 -3 -23 -8.5 t-65 -20t-103 -25t-132.5 -19.5t-158.5 -9q-125 0 -245.5 20.5t-178.5 40.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
|
||||||
|
<glyph unicode="" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
|
||||||
|
<glyph unicode="" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q124 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 213l100 212h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
|
||||||
|
<glyph unicode="" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q124 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
|
||||||
|
<glyph unicode="" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
|
||||||
|
<glyph unicode="" d="M-101 651q0 72 54 110t139 38l302 -1l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 17 -10.5t26.5 -26t16.5 -36.5v-526q0 -13 -86 -93.5t-94 -80.5h-341q-16 0 -29.5 20t-19.5 41l-130 339h-107q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l107 89v502l-343 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM1000 201v600h200v-600h-200z" />
|
||||||
|
<glyph unicode="" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6.5v7.5v6.5v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
|
||||||
|
<glyph unicode="" d="M2 585q-16 -31 6 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85q0 -51 -0.5 -153.5t-0.5 -148.5q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM77 565l236 339h503 l89 -100v-294l-340 -130q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
|
||||||
|
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM298 701l2 -201h300l-2 -194l402 294l-402 298v-197h-300z" />
|
||||||
|
<glyph unicode="" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l402 -294l-2 194h300l2 201h-300v197z" />
|
||||||
|
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
|
||||||
|
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
|
||||||
|
<glyph unicode="" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -33 5.5 -92.5t7.5 -87.5q0 -9 17 -44t16 -60 q12 0 23 -5.5t23 -15t20 -13.5q24 -12 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55t-20 -57q42 -71 87 -80q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q104 -3 221 112q30 29 47 47t34.5 49t20.5 62q-14 9 -37 9.5t-36 7.5q-14 7 -49 15t-52 19q-9 0 -39.5 -0.5 t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5t5.5 57.5 q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 39 2 44q31 -13 58 -14.5t39 3.5l11 4q7 36 -16.5 53.5t-64.5 28.5t-56 23q-19 -3 -37 0 q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -45.5 0.5t-45.5 -2.5q-21 -7 -52 -26.5t-34 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -90.5t-29.5 -79.5zM518 916q3 12 16 30t16 25q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -24 17 -66.5t17 -43.5 q-9 2 -31 5t-36 5t-32 8t-30 14zM692 1003h1h-1z" />
|
||||||
|
<glyph unicode="" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
|
||||||
|
<glyph unicode="" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
|
||||||
|
<glyph unicode="" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
|
||||||
|
<glyph unicode="" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM514 609q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-14 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
|
||||||
|
<glyph unicode="" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -78.5 -16.5t-67.5 -51.5l-389 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23 q38 0 53 -36q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60 l517 511q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
|
||||||
|
<glyph unicode="" d="M80 784q0 131 98.5 229.5t230.5 98.5q143 0 241 -129q103 129 246 129q129 0 226 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100q-71 70 -104.5 105.5t-77 89.5t-61 99 t-17.5 91zM250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-105 48.5q-74 0 -132 -83l-118 -171l-114 174q-51 80 -123 80q-60 0 -109.5 -49.5t-49.5 -118.5z" />
|
||||||
|
<glyph unicode="" d="M57 353q0 -95 66 -159l141 -142q68 -66 159 -66q93 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-8 9 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141q7 -7 19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -17q47 -49 77 -100l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
|
||||||
|
<glyph unicode="" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
|
||||||
|
<glyph unicode="" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
|
||||||
|
<glyph unicode="" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335q-6 1 -15.5 4t-11.5 3q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5 v-307l64 -14q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5 zM700 237q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
|
||||||
|
<glyph unicode="" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -28 16.5 -69.5t28 -62.5t41.5 -72h241v-100h-197q8 -50 -2.5 -115 t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q33 1 103 -16t103 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
|
||||||
|
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
|
||||||
|
<glyph unicode="" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
|
||||||
|
<glyph unicode="" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
|
||||||
|
<glyph unicode="" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
|
||||||
|
<glyph unicode="" d="M217 519q8 -19 31 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8h9q14 0 26 15q11 13 274.5 321.5t264.5 308.5q14 19 5 36q-8 17 -31 17l-301 -1q1 4 78 219.5t79 227.5q2 15 -5 27l-9 9h-9q-15 0 -25 -16q-4 -6 -98 -111.5t-228.5 -257t-209.5 -237.5q-16 -19 -6 -41 z" />
|
||||||
|
<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
|
||||||
|
<glyph unicode="" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
|
||||||
|
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
|
||||||
|
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
|
||||||
|
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
|
||||||
|
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 400l697 1l3 699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
|
||||||
|
<glyph unicode="" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l249 -237l-1 697zM900 150h100v50h-100v-50z" />
|
||||||
|
<glyph unicode="" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
|
||||||
|
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
|
||||||
|
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
|
||||||
|
<glyph unicode="" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
|
||||||
|
<glyph unicode="" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
|
||||||
|
<glyph unicode="" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
|
||||||
|
<glyph unicode="" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -116q-25 -17 -43.5 -51.5t-18.5 -65.5v-359z" />
|
||||||
|
<glyph unicode="" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
|
||||||
|
<glyph unicode="" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
|
||||||
|
<glyph unicode="" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q17 18 13.5 41t-22.5 37l-192 136q-19 14 -45 12t-42 -19l-118 -118q-142 101 -268 227t-227 268l118 118q17 17 20 41.5t-11 44.5 l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
|
||||||
|
<glyph unicode="" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-20 0 -35 14.5t-15 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
|
||||||
|
<glyph unicode="" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
|
||||||
|
<glyph unicode="" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
|
||||||
|
<glyph unicode="" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
|
||||||
|
<glyph unicode="" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
|
||||||
|
<glyph unicode="" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
|
||||||
|
<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86.5t85 207.5t-85 207t-205 86q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300h200 l-300 -300z" />
|
||||||
|
<glyph unicode="" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104.5t60.5 178.5q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
|
||||||
|
<glyph unicode="" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
|
||||||
|
<glyph unicode="" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -11.5t1 -11.5q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
|
||||||
|
</font>
|
||||||
|
</defs></svg>
|
||||||
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,148 @@
|
||||||
|
{% extends "!layout.html" %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
<meta name="description" content="Small, safe and fast formatting library">
|
||||||
|
<meta name="keywords" content="C++, formatting, printf, string, library">
|
||||||
|
<meta name="author" content="Victor Zverovich">
|
||||||
|
<link rel="stylesheet" href="_static/fmt.css">
|
||||||
|
{# Google Analytics #}
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20116650-4"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'UA-20116650-4');
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{%- macro searchform(classes, button) %}
|
||||||
|
<form class="{{classes}}" role="search" action="{{ pathto('search') }}"
|
||||||
|
method="get">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" name="q" class="form-control"
|
||||||
|
{{ 'placeholder="Search"' if not button }} >
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="check_keywords" value="yes" />
|
||||||
|
<input type="hidden" name="area" value="default" />
|
||||||
|
{% if button %}
|
||||||
|
<input type="submit" class="btn btn-default" value="search">
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<nav class="navbar navbar-inverse">
|
||||||
|
<div class="tb-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="navbar-content">
|
||||||
|
{# Brand and toggle get grouped for better mobile display #}
|
||||||
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle collapsed"
|
||||||
|
data-toggle="collapse" data-target=".navbar-collapse">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</button>
|
||||||
|
<a class="navbar-brand" href="index.html">{fmt}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Collect the nav links, forms, and other content for toggling #}
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
|
||||||
|
role="button" aria-expanded="false">{{ version }}
|
||||||
|
<span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu" role="menu">
|
||||||
|
{% for v in versions.split(',') %}
|
||||||
|
<li><a href="https://fmt.dev/{{v}}">{{v}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% for name in ['Contents', 'Usage', 'API', 'Syntax'] %}
|
||||||
|
{% if pagename == name.lower() %}
|
||||||
|
<li class="active"><a href="{{name.lower()}}.html">{{name}}
|
||||||
|
<span class="sr-only">(current)</span></a></li>
|
||||||
|
{%else%}
|
||||||
|
<li><a href="{{name.lower()}}.html">{{name}}</a></li>
|
||||||
|
{%endif%}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% if pagename != 'search' %}
|
||||||
|
{{ searchform('navbar-form navbar-right', False) }}
|
||||||
|
{%endif%}
|
||||||
|
</div> {# /.navbar-collapse #}
|
||||||
|
</div> {# /.col-md-offset-2 #}
|
||||||
|
</div> {# /.row #}
|
||||||
|
</div> {# /.tb-container #}
|
||||||
|
</nav>
|
||||||
|
{% if pagename == "index" %}
|
||||||
|
{% set download_url = 'https://github.com/fmtlib/fmt/releases/download' %}
|
||||||
|
<div class="jumbotron">
|
||||||
|
<div class="tb-container">
|
||||||
|
<h1>{fmt}</h1>
|
||||||
|
<p class="lead">A modern formatting library</p>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
{% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||||
|
<a class="btn btn-success"
|
||||||
|
href="{{download_url}}/{{version}}/{{name}}-{{version}}.zip">
|
||||||
|
<span class="glyphicon glyphicon-download"></span> Download
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-success dropdown-toggle"
|
||||||
|
data-toggle="dropdown"><span class="caret"></span></button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
{% for v in versions.split(',') %}
|
||||||
|
{% set name = 'fmt' if v.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||||
|
<li><a href="{{download_url}}/{{v}}/{{name}}-{{v}}.zip">Version {{v}}
|
||||||
|
</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{# Disable relbars. #}
|
||||||
|
{% block relbar1 %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block relbar2 %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="tb-container">
|
||||||
|
<div class="row">
|
||||||
|
{# Sidebar is currently disabled.
|
||||||
|
<div class="bs-sidebar">
|
||||||
|
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="sphinxsidebarwrapper">
|
||||||
|
{%- block sidebarlogo %}
|
||||||
|
{%- if logo %}
|
||||||
|
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||||
|
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}"
|
||||||
|
alt="Logo"/>
|
||||||
|
</a></p>
|
||||||
|
{%- endif %}
|
||||||
|
{%- endblock %}
|
||||||
|
{%- for sidebartemplate in sidebars %}
|
||||||
|
{%- include sidebartemplate %}
|
||||||
|
{%- endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
#}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{% block body %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{{ super() }}
|
||||||
|
{# Placed at the end of the document so the pages load faster. #}
|
||||||
|
<script src="_static/bootstrap.min.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
{#
|
||||||
|
basic/search.html
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Template for the search page.
|
||||||
|
|
||||||
|
:copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
#}
|
||||||
|
{%- extends "layout.html" %}
|
||||||
|
{% set title = _('Search') %}
|
||||||
|
{% set script_files = script_files + ['_static/searchtools.js'] %}
|
||||||
|
{% block extrahead %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
|
||||||
|
</script>
|
||||||
|
{# this is used when loading the search index using $.ajax fails,
|
||||||
|
such as on Chrome for documents on localhost #}
|
||||||
|
<script type="text/javascript" id="searchindexloader"></script>
|
||||||
|
{{ super() }}
|
||||||
|
{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<h1 id="search-documentation">{{ _('Search') }}</h1>
|
||||||
|
<div id="fallback" class="admonition warning">
|
||||||
|
<script type="text/javascript">$('#fallback').hide();</script>
|
||||||
|
<p>
|
||||||
|
{% trans %}Please activate JavaScript to enable the search
|
||||||
|
functionality.{% endtrans %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{% trans %}From here you can search these documents. Enter your search
|
||||||
|
words into the box below and click "search". Note that the search
|
||||||
|
function will automatically search for all of the words. Pages
|
||||||
|
containing fewer words won't appear in the result list.{% endtrans %}
|
||||||
|
</p>
|
||||||
|
{{ searchform('form-inline', True) }}
|
||||||
|
{% if search_performed %}
|
||||||
|
<h2>{{ _('Search Results') }}</h2>
|
||||||
|
{% if not search_results %}
|
||||||
|
<p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<div id="search-results">
|
||||||
|
{% if search_results %}
|
||||||
|
<ul>
|
||||||
|
{% for href, caption, context in search_results %}
|
||||||
|
<li><a href="{{ pathto(item.href) }}">{{ caption }}</a>
|
||||||
|
<div class="context">{{ context|e }}</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,616 @@
|
||||||
|
.. _string-formatting-api:
|
||||||
|
|
||||||
|
*************
|
||||||
|
API Reference
|
||||||
|
*************
|
||||||
|
|
||||||
|
The {fmt} library API consists of the following parts:
|
||||||
|
|
||||||
|
* :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
|
||||||
|
for ``char``/UTF-8 with C++20 compile-time checks and minimal dependencies
|
||||||
|
* :ref:`fmt/format.h <format-api>`: the full format API providing additional
|
||||||
|
formatting functions and locale support
|
||||||
|
* :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
|
||||||
|
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
|
||||||
|
* :ref:`fmt/std.h <std-api>`: formatters for standard library types
|
||||||
|
* :ref:`fmt/compile.h <compile-api>`: format string compilation
|
||||||
|
* :ref:`fmt/color.h <color-api>`: terminal color and text style
|
||||||
|
* :ref:`fmt/os.h <os-api>`: system APIs
|
||||||
|
* :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||||
|
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||||
|
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
|
||||||
|
|
||||||
|
All functions and types provided by the library reside in namespace ``fmt`` and
|
||||||
|
macros have prefix ``FMT_``.
|
||||||
|
|
||||||
|
.. _core-api:
|
||||||
|
|
||||||
|
Core API
|
||||||
|
========
|
||||||
|
|
||||||
|
``fmt/core.h`` defines the core API which provides main formatting functions
|
||||||
|
for ``char``/UTF-8 with C++20 compile-time checks. It has minimal include
|
||||||
|
dependencies for better compile times. This header is only beneficial when
|
||||||
|
using {fmt} as a library and not in the header-only mode.
|
||||||
|
|
||||||
|
The following functions use :ref:`format string syntax <syntax>`
|
||||||
|
similar to that of Python's `str.format
|
||||||
|
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
||||||
|
They take *fmt* and *args* as arguments.
|
||||||
|
|
||||||
|
*fmt* is a format string that contains literal text and replacement fields
|
||||||
|
surrounded by braces ``{}``. The fields are replaced with formatted arguments
|
||||||
|
in the resulting string. `~fmt::format_string` is a format string which can be
|
||||||
|
implicitly constructed from a string literal or a ``constexpr`` string and is
|
||||||
|
checked at compile time in C++20. To pass a runtime format string wrap it in
|
||||||
|
`fmt::runtime`.
|
||||||
|
|
||||||
|
*args* is an argument list representing objects to be formatted.
|
||||||
|
|
||||||
|
.. _format:
|
||||||
|
|
||||||
|
.. doxygenfunction:: format(format_string<T...> fmt, T&&... args) -> std::string
|
||||||
|
.. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string
|
||||||
|
|
||||||
|
.. doxygenfunction:: format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt
|
||||||
|
.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string<T...> fmt, T&&... args) -> format_to_n_result<OutputIt>
|
||||||
|
.. doxygenfunction:: formatted_size(format_string<T...> fmt, T&&... args) -> size_t
|
||||||
|
|
||||||
|
.. doxygenstruct:: fmt::format_to_n_result
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. _print:
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::print(format_string<T...> fmt, T&&... args)
|
||||||
|
.. doxygenfunction:: fmt::vprint(string_view fmt, format_args args)
|
||||||
|
|
||||||
|
.. doxygenfunction:: print(std::FILE *f, format_string<T...> fmt, T&&... args)
|
||||||
|
.. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args)
|
||||||
|
|
||||||
|
Compile-Time Format String Checks
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Compile-time checks are enabled by default on compilers that support C++20
|
||||||
|
``consteval``. On older compilers you can use the ``FMT_STRING`` macro defined
|
||||||
|
in ``fmt/format.h`` instead. It requires C++14 and is a no-op in C++11.
|
||||||
|
|
||||||
|
.. doxygendefine:: FMT_STRING
|
||||||
|
|
||||||
|
To force the use of legacy compile-time checks, define the preprocessor variable
|
||||||
|
``FMT_ENFORCE_COMPILE_STRING``. When set, functions accepting ``FMT_STRING``
|
||||||
|
will fail to compile with regular strings. Runtime-checked formatting is still
|
||||||
|
possible using ``fmt::vformat``, ``fmt::vprint``, etc.
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_string
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::format_string
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::runtime(string_view) -> basic_runtime<char>
|
||||||
|
|
||||||
|
Named Arguments
|
||||||
|
---------------
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::arg(const S&, const T&)
|
||||||
|
|
||||||
|
Named arguments are not supported in compile-time checks at the moment.
|
||||||
|
|
||||||
|
Argument Lists
|
||||||
|
--------------
|
||||||
|
|
||||||
|
You can create your own formatting function with compile-time checks and small
|
||||||
|
binary footprint, for example (https://godbolt.org/z/oba4Mc):
|
||||||
|
|
||||||
|
.. code:: c++
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
void vlog(const char* file, int line, fmt::string_view format,
|
||||||
|
fmt::format_args args) {
|
||||||
|
fmt::print("{}: {}: ", file, line);
|
||||||
|
fmt::vprint(format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args>
|
||||||
|
void log(const char* file, int line, const S& format, Args&&... args) {
|
||||||
|
vlog(file, line, format, fmt::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MY_LOG(format, ...) \
|
||||||
|
log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__)
|
||||||
|
|
||||||
|
MY_LOG("invalid squishiness: {}", 42);
|
||||||
|
|
||||||
|
Note that ``vlog`` is not parameterized on argument types which improves compile
|
||||||
|
times and reduces binary code size compared to a fully parameterized version.
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::make_format_args(const Args&...)
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::format_arg_store
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::dynamic_format_arg_store
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_args
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::format_args
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_arg
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_parse_context
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_format_context
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::format_context
|
||||||
|
|
||||||
|
Compatibility
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_string_view
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::string_view
|
||||||
|
|
||||||
|
Locale
|
||||||
|
------
|
||||||
|
|
||||||
|
All formatting is locale-independent by default. Use the ``'L'`` format
|
||||||
|
specifier to insert the appropriate number separator characters from the
|
||||||
|
locale::
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <locale>
|
||||||
|
|
||||||
|
std::locale::global(std::locale("en_US.UTF-8"));
|
||||||
|
auto s = fmt::format("{:L}", 1000000); // s == "1,000,000"
|
||||||
|
|
||||||
|
.. _format-api:
|
||||||
|
|
||||||
|
Format API
|
||||||
|
==========
|
||||||
|
|
||||||
|
``fmt/format.h`` defines the full format API providing additional formatting
|
||||||
|
functions and locale support.
|
||||||
|
|
||||||
|
.. _udt:
|
||||||
|
|
||||||
|
Formatting User-Defined Types
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The {fmt} library provides formatters for many standard C++ types.
|
||||||
|
See :ref:`fmt/ranges.h <ranges-api>` for ranges and tuples including standard
|
||||||
|
containers such as ``std::vector``, :ref:`fmt/chrono.h <chrono-api>` for date
|
||||||
|
and time formatting and :ref:`fmt/std.h <std-api>` for path and variant
|
||||||
|
formatting.
|
||||||
|
|
||||||
|
To make a user-defined type formattable, specialize the ``formatter<T>`` struct
|
||||||
|
template and implement ``parse`` and ``format`` methods::
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
struct point {
|
||||||
|
double x, y;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct fmt::formatter<point> {
|
||||||
|
// Presentation format: 'f' - fixed, 'e' - exponential.
|
||||||
|
char presentation = 'f';
|
||||||
|
|
||||||
|
// Parses format specifications of the form ['f' | 'e'].
|
||||||
|
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
|
// [ctx.begin(), ctx.end()) is a character range that contains a part of
|
||||||
|
// the format string starting from the format specifications to be parsed,
|
||||||
|
// e.g. in
|
||||||
|
//
|
||||||
|
// fmt::format("{:f} - point of interest", point{1, 2});
|
||||||
|
//
|
||||||
|
// the range will contain "f} - point of interest". The formatter should
|
||||||
|
// parse specifiers until '}' or the end of the range. In this example
|
||||||
|
// the formatter should parse the 'f' specifier and return an iterator
|
||||||
|
// pointing to '}'.
|
||||||
|
|
||||||
|
// Please also note that this character range may be empty, in case of
|
||||||
|
// the "{}" format string, so therefore you should check ctx.begin()
|
||||||
|
// for equality with ctx.end().
|
||||||
|
|
||||||
|
// Parse the presentation format and store it in the formatter:
|
||||||
|
auto it = ctx.begin(), end = ctx.end();
|
||||||
|
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
|
||||||
|
|
||||||
|
// Check if reached the end of the range:
|
||||||
|
if (it != end && *it != '}') throw format_error("invalid format");
|
||||||
|
|
||||||
|
// Return an iterator past the end of the parsed range:
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formats the point p using the parsed format specification (presentation)
|
||||||
|
// stored in this formatter.
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const point& p, FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||||
|
// ctx.out() is an output iterator to write to.
|
||||||
|
return presentation == 'f'
|
||||||
|
? fmt::format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y)
|
||||||
|
: fmt::format_to(ctx.out(), "({:.1e}, {:.1e})", p.x, p.y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Then you can pass objects of type ``point`` to any formatting function::
|
||||||
|
|
||||||
|
point p = {1, 2};
|
||||||
|
std::string s = fmt::format("{:f}", p);
|
||||||
|
// s == "(1.0, 2.0)"
|
||||||
|
|
||||||
|
You can also reuse existing formatters via inheritance or composition, for
|
||||||
|
example::
|
||||||
|
|
||||||
|
enum class color {red, green, blue};
|
||||||
|
|
||||||
|
template <> struct fmt::formatter<color>: formatter<string_view> {
|
||||||
|
// parse is inherited from formatter<string_view>.
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(color c, FormatContext& ctx) const {
|
||||||
|
string_view name = "unknown";
|
||||||
|
switch (c) {
|
||||||
|
case color::red: name = "red"; break;
|
||||||
|
case color::green: name = "green"; break;
|
||||||
|
case color::blue: name = "blue"; break;
|
||||||
|
}
|
||||||
|
return formatter<string_view>::format(name, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Since ``parse`` is inherited from ``formatter<string_view>`` it will recognize
|
||||||
|
all string format specifications, for example
|
||||||
|
|
||||||
|
.. code-block:: c++
|
||||||
|
|
||||||
|
fmt::format("{:>10}", color::blue)
|
||||||
|
|
||||||
|
will return ``" blue"``.
|
||||||
|
|
||||||
|
You can also write a formatter for a hierarchy of classes::
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
struct A {
|
||||||
|
virtual ~A() {}
|
||||||
|
virtual std::string name() const { return "A"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct B : A {
|
||||||
|
virtual std::string name() const { return "B"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct fmt::formatter<T, std::enable_if_t<std::is_base_of<A, T>::value, char>> :
|
||||||
|
fmt::formatter<std::string> {
|
||||||
|
template <typename FormatCtx>
|
||||||
|
auto format(const A& a, FormatCtx& ctx) const {
|
||||||
|
return fmt::formatter<std::string>::format(a.name(), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
B b;
|
||||||
|
A& a = b;
|
||||||
|
fmt::print("{}", a); // prints "B"
|
||||||
|
}
|
||||||
|
|
||||||
|
If a type provides both a ``formatter`` specialization and an implicit
|
||||||
|
conversion to a formattable type, the specialization takes precedence over the
|
||||||
|
conversion.
|
||||||
|
|
||||||
|
For enums {fmt} also provides the ``format_as`` extension API. To format an enum
|
||||||
|
via this API define ``format_as`` that takes this enum and converts it to the
|
||||||
|
underlying type. ``format_as`` should be defined in the same namespace as the
|
||||||
|
enum.
|
||||||
|
|
||||||
|
Example (https://godbolt.org/z/r7vvGE1v7)::
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
namespace kevin_namespacy {
|
||||||
|
enum class film {
|
||||||
|
house_of_cards, american_beauty, se7en = 7
|
||||||
|
};
|
||||||
|
auto format_as(film f) { return fmt::underlying(f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
fmt::print("{}\n", kevin_namespacy::film::se7en); // prints "7"
|
||||||
|
}
|
||||||
|
|
||||||
|
Literal-Based API
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The following user-defined literals are defined in ``fmt/format.h``.
|
||||||
|
|
||||||
|
.. doxygenfunction:: operator""_a()
|
||||||
|
|
||||||
|
Utilities
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::ptr(T p) -> const void*
|
||||||
|
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
|
||||||
|
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::underlying(Enum e) -> typename std::underlying_type<Enum>::type
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::to_string(const T &value) -> std::string
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::join(Range &&range, string_view sep) -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>>
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::group_digits(T value) -> group_digits_view<T>
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::detail::buffer
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::basic_memory_buffer
|
||||||
|
:protected-members:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
System Errors
|
||||||
|
-------------
|
||||||
|
|
||||||
|
{fmt} does not use ``errno`` to communicate errors to the user, but it may call
|
||||||
|
system functions which set ``errno``. Users should not make any assumptions
|
||||||
|
about the value of ``errno`` being preserved by library functions.
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::system_error
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::format_system_error
|
||||||
|
|
||||||
|
Custom Allocators
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The {fmt} library supports custom dynamic memory allocators.
|
||||||
|
A custom allocator class can be specified as a template argument to
|
||||||
|
:class:`fmt::basic_memory_buffer`::
|
||||||
|
|
||||||
|
using custom_memory_buffer =
|
||||||
|
fmt::basic_memory_buffer<char, fmt::inline_buffer_size, custom_allocator>;
|
||||||
|
|
||||||
|
It is also possible to write a formatting function that uses a custom
|
||||||
|
allocator::
|
||||||
|
|
||||||
|
using custom_string =
|
||||||
|
std::basic_string<char, std::char_traits<char>, custom_allocator>;
|
||||||
|
|
||||||
|
custom_string vformat(custom_allocator alloc, fmt::string_view format_str,
|
||||||
|
fmt::format_args args) {
|
||||||
|
auto buf = custom_memory_buffer(alloc);
|
||||||
|
fmt::vformat_to(std::back_inserter(buf), format_str, args);
|
||||||
|
return custom_string(buf.data(), buf.size(), alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ...Args>
|
||||||
|
inline custom_string format(custom_allocator alloc,
|
||||||
|
fmt::string_view format_str,
|
||||||
|
const Args& ... args) {
|
||||||
|
return vformat(alloc, format_str, fmt::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
The allocator will be used for the output container only. Formatting functions
|
||||||
|
normally don't do any allocations for built-in and string types except for
|
||||||
|
non-default floating-point formatting that occasionally falls back on
|
||||||
|
``sprintf``.
|
||||||
|
|
||||||
|
.. _ranges-api:
|
||||||
|
|
||||||
|
Range and Tuple Formatting
|
||||||
|
==========================
|
||||||
|
|
||||||
|
The library also supports convenient formatting of ranges and tuples::
|
||||||
|
|
||||||
|
#include <fmt/ranges.h>
|
||||||
|
|
||||||
|
std::tuple<char, int, float> t{'a', 1, 2.0f};
|
||||||
|
// Prints "('a', 1, 2.0)"
|
||||||
|
fmt::print("{}", t);
|
||||||
|
|
||||||
|
|
||||||
|
NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main
|
||||||
|
``format.h`` header, but expect this to change in the future.
|
||||||
|
|
||||||
|
Using ``fmt::join``, you can separate tuple elements with a custom separator::
|
||||||
|
|
||||||
|
#include <fmt/ranges.h>
|
||||||
|
|
||||||
|
std::tuple<int, char> t = {1, 'a'};
|
||||||
|
// Prints "1, a"
|
||||||
|
fmt::print("{}", fmt::join(t, ", "));
|
||||||
|
|
||||||
|
.. _chrono-api:
|
||||||
|
|
||||||
|
Date and Time Formatting
|
||||||
|
========================
|
||||||
|
|
||||||
|
``fmt/chrono.h`` provides formatters for
|
||||||
|
|
||||||
|
* `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
|
||||||
|
* `std::chrono::time_point
|
||||||
|
<https://en.cppreference.com/w/cpp/chrono/time_point>`_
|
||||||
|
* `std::tm <https://en.cppreference.com/w/cpp/chrono/c/tm>`_
|
||||||
|
|
||||||
|
The format syntax is described in :ref:`chrono-specs`.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
#include <fmt/chrono.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::time_t t = std::time(nullptr);
|
||||||
|
|
||||||
|
// Prints "The date is 2020-11-07." (with the current date):
|
||||||
|
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
|
// Prints "Default format: 42s 100ms":
|
||||||
|
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||||
|
|
||||||
|
// Prints "strftime-like format: 03:15:30":
|
||||||
|
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.. doxygenfunction:: localtime(std::time_t time)
|
||||||
|
|
||||||
|
.. doxygenfunction:: gmtime(std::time_t time)
|
||||||
|
|
||||||
|
.. _std-api:
|
||||||
|
|
||||||
|
Standard Library Types Formatting
|
||||||
|
=================================
|
||||||
|
|
||||||
|
``fmt/std.h`` provides formatters for:
|
||||||
|
|
||||||
|
* `std::filesystem::path <https://en.cppreference.com/w/cpp/filesystem/path>`_
|
||||||
|
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
|
||||||
|
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
|
||||||
|
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
|
||||||
|
|
||||||
|
Formatting Variants
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
A ``std::variant`` is only formattable if every variant alternative is formattable, and requires the
|
||||||
|
``__cpp_lib_variant`` `library feature <https://en.cppreference.com/w/cpp/feature_test>`_.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
#include <fmt/std.h>
|
||||||
|
|
||||||
|
std::variant<char, float> v0{'x'};
|
||||||
|
// Prints "variant('x')"
|
||||||
|
fmt::print("{}", v0);
|
||||||
|
|
||||||
|
std::variant<std::monostate, char> v1;
|
||||||
|
// Prints "variant(monostate)"
|
||||||
|
|
||||||
|
.. _compile-api:
|
||||||
|
|
||||||
|
Format String Compilation
|
||||||
|
=========================
|
||||||
|
|
||||||
|
``fmt/compile.h`` provides format string compilation enabled via the
|
||||||
|
``FMT_COMPILE`` macro or the ``_cf`` user-defined literal. Format strings
|
||||||
|
marked with ``FMT_COMPILE`` or ``_cf`` are parsed, checked and converted into
|
||||||
|
efficient formatting code at compile-time. This supports arguments of built-in
|
||||||
|
and string types as well as user-defined types with ``constexpr`` ``parse``
|
||||||
|
functions in their ``formatter`` specializations. Format string compilation can
|
||||||
|
generate more binary code compared to the default API and is only recommended in
|
||||||
|
places where formatting is a performance bottleneck.
|
||||||
|
|
||||||
|
.. doxygendefine:: FMT_COMPILE
|
||||||
|
|
||||||
|
.. doxygenfunction:: operator""_cf()
|
||||||
|
|
||||||
|
.. _color-api:
|
||||||
|
|
||||||
|
Terminal Color and Text Style
|
||||||
|
=============================
|
||||||
|
|
||||||
|
``fmt/color.h`` provides support for terminal color and text style output.
|
||||||
|
|
||||||
|
.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args)
|
||||||
|
|
||||||
|
.. doxygenfunction:: fg(detail::color_type)
|
||||||
|
|
||||||
|
.. doxygenfunction:: bg(detail::color_type)
|
||||||
|
|
||||||
|
.. doxygenfunction:: styled(const T& value, text_style ts)
|
||||||
|
|
||||||
|
.. _os-api:
|
||||||
|
|
||||||
|
System APIs
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. doxygenclass:: fmt::ostream
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::windows_error
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. _ostream-api:
|
||||||
|
|
||||||
|
``std::ostream`` Support
|
||||||
|
========================
|
||||||
|
|
||||||
|
``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
|
||||||
|
user-defined types that have an overloaded insertion operator (``operator<<``).
|
||||||
|
In order to make a type formattable via ``std::ostream`` you should provide a
|
||||||
|
``formatter`` specialization inherited from ``ostream_formatter``::
|
||||||
|
|
||||||
|
#include <fmt/ostream.h>
|
||||||
|
|
||||||
|
struct date {
|
||||||
|
int year, month, day;
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const date& d) {
|
||||||
|
return os << d.year << '-' << d.month << '-' << d.day;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct fmt::formatter<date> : ostream_formatter {};
|
||||||
|
|
||||||
|
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
|
||||||
|
// s == "The date is 2012-12-9"
|
||||||
|
|
||||||
|
.. doxygenfunction:: streamed(const T &)
|
||||||
|
|
||||||
|
.. doxygenfunction:: print(std::ostream &os, format_string<T...> fmt, T&&... args)
|
||||||
|
|
||||||
|
.. _printf-api:
|
||||||
|
|
||||||
|
``printf`` Formatting
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
|
||||||
|
The following functions use `printf format string syntax
|
||||||
|
<https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
|
||||||
|
the POSIX extension for positional arguments. Unlike their standard
|
||||||
|
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
|
||||||
|
argument type doesn't match its format specification.
|
||||||
|
|
||||||
|
.. doxygenfunction:: printf(const S &format_str, const T&... args)
|
||||||
|
|
||||||
|
.. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
|
||||||
|
|
||||||
|
.. doxygenfunction:: sprintf(const S&, const T&...)
|
||||||
|
|
||||||
|
.. _xchar-api:
|
||||||
|
|
||||||
|
``wchar_t`` Support
|
||||||
|
===================
|
||||||
|
|
||||||
|
The optional header ``fmt/xchar.h`` provides support for ``wchar_t`` and exotic
|
||||||
|
character types.
|
||||||
|
|
||||||
|
.. doxygenstruct:: fmt::is_char
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::wstring_view
|
||||||
|
|
||||||
|
.. doxygentypedef:: fmt::wformat_context
|
||||||
|
|
||||||
|
.. doxygenfunction:: fmt::to_wstring(const T &value)
|
||||||
|
|
||||||
|
Compatibility with C++20 ``std::format``
|
||||||
|
========================================
|
||||||
|
|
||||||
|
{fmt} implements nearly all of the `C++20 formatting library
|
||||||
|
<https://en.cppreference.com/w/cpp/utility/format>`_ with the following
|
||||||
|
differences:
|
||||||
|
|
||||||
|
* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
|
||||||
|
collisions with standard library implementations.
|
||||||
|
* Width calculation doesn't use grapheme clusterization. The latter has been
|
||||||
|
implemented in a separate branch but hasn't been integrated yet.
|
||||||
|
* Most C++20 chrono types are not supported yet.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue