Compare commits
No commits in common. "master" and "main" have entirely different histories.
|
|
@ -1,12 +0,0 @@
|
||||||
[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,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 sevana-ou
|
||||||
|
|
||||||
|
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.
|
||||||
373
LICENSE_MPL.txt
373
LICENSE_MPL.txt
|
|
@ -1,373 +0,0 @@
|
||||||
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.
|
|
||||||
141
README.txt
141
README.txt
|
|
@ -1,141 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
#!/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()
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
rm -rf build_android
|
|
||||||
mkdir -p build_android
|
|
||||||
cd build_android
|
|
||||||
|
|
||||||
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
|
|
||||||
-DANDROID_NDK=$ANDROID_NDK_HOME \
|
|
||||||
-DANDROID_PLATFORM=24 \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DANDROID_ABI="arm64-v8a" \
|
|
||||||
../src
|
|
||||||
|
|
||||||
cmake --build . -j8
|
|
||||||
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
#!/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}')
|
|
||||||
23
run_ci.sh
23
run_ci.sh
|
|
@ -1,23 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
SEND_MSG="/var/ci/conformance_ci/send_telegram_message.py"
|
|
||||||
|
|
||||||
mkdir -p build
|
|
||||||
cd build
|
|
||||||
|
|
||||||
# Configure
|
|
||||||
cmake ../src
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
/usr/bin/python3 $SEND_MSG "rtphone cmake failed. $BUILD_URL"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build
|
|
||||||
make -j2
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
/usr/bin/python3 $SEND_MSG "rtphone build failed. $BUILD_URL"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
/usr/bin/python3 $SEND_MSG "rtphone builds ok. $BUILD_URL"
|
|
||||||
|
|
@ -1,380 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
project(rtphone)
|
|
||||||
|
|
||||||
# Rely on C++ 20
|
|
||||||
set (CMAKE_CXX_STANDARD 20)
|
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
set (L libs)
|
|
||||||
set (E engine)
|
|
||||||
|
|
||||||
option (USE_AMR_CODEC "Use AMR codec. Requires libraries." ON)
|
|
||||||
option (USE_EVS_CODEC "Use EVS codec." ON)
|
|
||||||
option (USE_OPUS_CODEC "Use Opus codec." ON)
|
|
||||||
option (USE_MUSL "Build with MUSL library" OFF)
|
|
||||||
|
|
||||||
# PIC code by default
|
|
||||||
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
set (RUNTIME_CPU_CAPABILITY_DETECTION ON)
|
|
||||||
|
|
||||||
set (LIB_PLATFORM ${CMAKE_CURRENT_SOURCE_DIR}/libs/libraries)
|
|
||||||
include (${LIB_PLATFORM}/platform_libs.cmake)
|
|
||||||
|
|
||||||
|
|
||||||
message("Libraries: ${LIB_PLATFORM}")
|
|
||||||
set (OPENSSL_INCLUDE ${LIB_PLATFORM}/openssl/1.1/include)
|
|
||||||
message ("Using OpenSSL include files from ${OPENSSL_INCLUDE}")
|
|
||||||
message ("Using OpenSSL libs: ${OPENSSL_SSL} and ${OPENSSL_CRYPTO}")
|
|
||||||
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*")
|
|
||||||
set (DEFINES ${DEFINES} -DTARGET_WIN -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS -D_UNICODE -D_CRT_SECURE_NO_WARNINGS)
|
|
||||||
set (TARGET_WIN ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Linux-specific definitions
|
|
||||||
if (CMAKE_SYSTEM MATCHES "Linux*")
|
|
||||||
set (DEFINES ${DEFINES} -DTARGET_LINUX -DHAVE_NETINET_IN_H)
|
|
||||||
set (TARGET_LINUX ON)
|
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} dl)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# macOS-specific definitions
|
|
||||||
if (CMAKE_SYSTEM MATCHES "Darwin*")
|
|
||||||
set (DEFINES ${DEFINES} -DTARGET_OSX)
|
|
||||||
set (TARGET_OSX ON)
|
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} dl)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#
|
|
||||||
if (CMAKE_SYSTEM MATCHES "Android")
|
|
||||||
message("Adding the Oboe library")
|
|
||||||
set (OBOE_DIR libs/oboe)
|
|
||||||
add_subdirectory (${OBOE_DIR} ./oboe)
|
|
||||||
include_directories (${OBOE_DIR}/include)
|
|
||||||
set (DEFINES ${DEFINES} -DTARGET_ANDROID -DHAVE_NETINET_IN_H)
|
|
||||||
set (TARGET_ANDROID ON)
|
|
||||||
set (LIBS_STATIC ${LIBS} oboe)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_MUSL)
|
|
||||||
set (DEFINES ${DEFINES} -DTARGET_MUSL)
|
|
||||||
set (TARGET_MUSL ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set (RTPHONE_SOURCES
|
|
||||||
${E}/engine_config.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}/agent/Agent_Impl.cpp
|
|
||||||
${E}/agent/Agent_Impl.h
|
|
||||||
${E}/agent/Agent_AudioManager.cpp
|
|
||||||
${E}/agent/Agent_AudioManager.h
|
|
||||||
${E}/endpoint/EP_Account.cpp
|
|
||||||
${E}/endpoint/EP_Account.h
|
|
||||||
${E}/endpoint/EP_AudioProvider.cpp
|
|
||||||
${E}/endpoint/EP_AudioProvider.h
|
|
||||||
${E}/endpoint/EP_DataProvider.cpp
|
|
||||||
${E}/endpoint/EP_DataProvider.h
|
|
||||||
${E}/endpoint/EP_Engine.cpp
|
|
||||||
${E}/endpoint/EP_Engine.h
|
|
||||||
${E}/endpoint/EP_NetworkQueue.cpp
|
|
||||||
${E}/endpoint/EP_NetworkQueue.h
|
|
||||||
${E}/endpoint/EP_Observer.cpp
|
|
||||||
${E}/endpoint/EP_Observer.h
|
|
||||||
${E}/endpoint/EP_Session.cpp
|
|
||||||
${E}/endpoint/EP_Session.h
|
|
||||||
|
|
||||||
${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_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_OPUS_CODEC)
|
|
||||||
set (DEFINES ${DEFINES} -DUSE_OPUS_CODEC)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library (rtphone STATIC ${RTPHONE_SOURCES})
|
|
||||||
|
|
||||||
add_subdirectory(${L}/resiprocate)
|
|
||||||
add_subdirectory(${L}/jrtplib/src)
|
|
||||||
add_subdirectory(${L}/libg729)
|
|
||||||
|
|
||||||
if (USE_EVS_CODEC)
|
|
||||||
add_subdirectory(${L}/libevs)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(${L}/libgsm)
|
|
||||||
add_subdirectory(${L}/gsmhr)
|
|
||||||
add_subdirectory(${L}/g722)
|
|
||||||
add_subdirectory(${L}/speexdsp)
|
|
||||||
add_subdirectory(${L}/libsrtp)
|
|
||||||
add_subdirectory(${L}/webrtc)
|
|
||||||
add_subdirectory(${L}/opus)
|
|
||||||
|
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} jrtplib g729_codec gsm_codec opus
|
|
||||||
gsmhr_codec g722_codec srtp3 resiprocate webrtc speexdsp)
|
|
||||||
|
|
||||||
if (USE_AMR_CODEC)
|
|
||||||
include (${LIB_PLATFORM}/platform_libs.cmake)
|
|
||||||
message("Media: AMR NB and WB codecs will be included.")
|
|
||||||
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC)
|
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (USE_EVS_CODEC)
|
|
||||||
message("Media: EVS codec will be included.")
|
|
||||||
set (DEFINES ${DEFINES} -DUSE_EVS_CODEC)
|
|
||||||
set (LIBS_STATIC ${LIBS_STATIC} evs_codec)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_compile_definitions(rtphone PUBLIC ${DEFINES})
|
|
||||||
|
|
||||||
if (TARGET_LINUX)
|
|
||||||
target_link_options(rtphone PUBLIC -Wl,-Bstatic)
|
|
||||||
endif()
|
|
||||||
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
|
|
||||||
PUBLIC
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/engine>
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libs
|
|
||||||
${LIB_PLATFORM}/opus/include
|
|
||||||
${E}/helper
|
|
||||||
${E}/audio
|
|
||||||
${E}/media
|
|
||||||
${L}
|
|
||||||
${L}/ice
|
|
||||||
PRIVATE
|
|
||||||
${L}/libevs/lib_com
|
|
||||||
${L}/libevs/lib_enc
|
|
||||||
${L}/libevs/lib_dec
|
|
||||||
${L}/speex/include
|
|
||||||
${L}/libs/json
|
|
||||||
)
|
|
||||||
|
|
||||||
# For MSVC static builds
|
|
||||||
# set_property(TARGET rtphone PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
|
||||||
|
|
@ -1,207 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
#include "Agent_AudioManager.h"
|
|
||||||
#include "../engine/audio/Audio_WavFile.h"
|
|
||||||
#include "../engine/audio/Audio_Null.h"
|
|
||||||
#include "HL_String.h"
|
|
||||||
|
|
||||||
#if defined(TARGET_ANDROID)
|
|
||||||
# include "../engine/audio/Audio_Android.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "AudioManager"
|
|
||||||
|
|
||||||
|
|
||||||
AudioManager::AudioManager()
|
|
||||||
:mTerminal(nullptr), mAudioMonitoring(nullptr)
|
|
||||||
{
|
|
||||||
mPlayer.setDelegate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioManager::~AudioManager()
|
|
||||||
{
|
|
||||||
// stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioManager& AudioManager::instance()
|
|
||||||
{
|
|
||||||
static std::shared_ptr<AudioManager> GAudioManager;
|
|
||||||
|
|
||||||
if (!GAudioManager)
|
|
||||||
GAudioManager = std::make_shared<AudioManager>();
|
|
||||||
|
|
||||||
return *GAudioManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::setTerminal(MT::Terminal* terminal)
|
|
||||||
{
|
|
||||||
mTerminal = terminal;
|
|
||||||
}
|
|
||||||
|
|
||||||
MT::Terminal* AudioManager::terminal()
|
|
||||||
{
|
|
||||||
return mTerminal;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::setAudioMonitoring(Audio::DataConnection* monitoring)
|
|
||||||
{
|
|
||||||
mAudioMonitoring = monitoring;
|
|
||||||
}
|
|
||||||
|
|
||||||
Audio::DataConnection* AudioManager::audioMonitoring()
|
|
||||||
{
|
|
||||||
return mAudioMonitoring;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define LOCK_MANAGER std::unique_lock<std::mutex> l(mGuard)
|
|
||||||
void AudioManager::start(int usageId)
|
|
||||||
{
|
|
||||||
assert(mTerminal);
|
|
||||||
LOCK_MANAGER;
|
|
||||||
|
|
||||||
ICELogInfo(<< "Start main audio with usage id " << usageId);
|
|
||||||
|
|
||||||
if (mUsage.obtain(usageId) > 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Audio::OsEngine::instance())
|
|
||||||
Audio::OsEngine::instance()->open();
|
|
||||||
|
|
||||||
if (!mAudioInput || !mAudioOutput)
|
|
||||||
{
|
|
||||||
// Disable AEC for now - because PVQA conflicts with speex AEC.
|
|
||||||
std::shared_ptr<Audio::Enumerator> enumerator(Audio::Enumerator::make(usageId == atNull));
|
|
||||||
if (!mTerminal->audio())
|
|
||||||
{
|
|
||||||
auto audio = std::make_shared<Audio::DevicePair>();
|
|
||||||
audio->setAgc(true);
|
|
||||||
audio->setAec(false);
|
|
||||||
audio->setMonitoring(mAudioMonitoring);
|
|
||||||
|
|
||||||
mTerminal->setAudio(audio);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mAudioInput)
|
|
||||||
{
|
|
||||||
enumerator->open(Audio::myMicrophone);
|
|
||||||
int inputIndex = enumerator->indexOfDefaultDevice();
|
|
||||||
|
|
||||||
// Construct and set to terminal's audio pair input device
|
|
||||||
if (usageId != atNull)
|
|
||||||
mAudioInput = Audio::PInputDevice(Audio::InputDevice::make(enumerator->idAt(inputIndex)));
|
|
||||||
else
|
|
||||||
mAudioInput = Audio::PInputDevice(new Audio::NullInputDevice());
|
|
||||||
|
|
||||||
mTerminal->audio()->setInput(mAudioInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mAudioOutput)
|
|
||||||
{
|
|
||||||
Audio::Enumerator *enumerator = Audio::Enumerator::make(usageId == atNull);
|
|
||||||
enumerator->open(Audio::mySpeaker);
|
|
||||||
int outputIndex = enumerator->indexOfDefaultDevice();
|
|
||||||
|
|
||||||
// Construct and set terminal's audio pair output device
|
|
||||||
if (usageId != atNull)
|
|
||||||
{
|
|
||||||
if (outputIndex >= enumerator->count())
|
|
||||||
outputIndex = 0;
|
|
||||||
|
|
||||||
mAudioOutput = Audio::POutputDevice(
|
|
||||||
Audio::OutputDevice::make(enumerator->idAt(outputIndex)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mAudioOutput = Audio::POutputDevice(new Audio::NullOutputDevice());
|
|
||||||
|
|
||||||
mTerminal->audio()->setOutput(mAudioOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open audio
|
|
||||||
if (mAudioInput)
|
|
||||||
mAudioInput->open();
|
|
||||||
if (mAudioOutput)
|
|
||||||
mAudioOutput->open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::close()
|
|
||||||
{
|
|
||||||
mUsage.clear();
|
|
||||||
if (mAudioInput)
|
|
||||||
{
|
|
||||||
mAudioInput->close();
|
|
||||||
mAudioInput.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mAudioOutput)
|
|
||||||
{
|
|
||||||
mAudioOutput->close();
|
|
||||||
mAudioOutput.reset();
|
|
||||||
}
|
|
||||||
mPlayer.setOutput(Audio::POutputDevice());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::stop(int usageId)
|
|
||||||
{
|
|
||||||
LOCK_MANAGER;
|
|
||||||
|
|
||||||
ICELogInfo( << "Stop main audio with usage id " << usageId);
|
|
||||||
if (mTerminal)
|
|
||||||
{
|
|
||||||
if (mTerminal->audio())
|
|
||||||
mTerminal->audio()->player().release(usageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mUsage.release(usageId))
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
|
|
||||||
// Reset device pair on terminal side
|
|
||||||
mTerminal->setAudio(Audio::PDevicePair());
|
|
||||||
|
|
||||||
if (Audio::OsEngine::instance())
|
|
||||||
Audio::OsEngine::instance()->close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::startPlayFile(int usageId, const std::string& path, AudioTarget target, LoopMode lm, int timelimit)
|
|
||||||
{
|
|
||||||
// Check if file exists
|
|
||||||
Audio::PWavFileReader r = std::make_shared<Audio::WavFileReader>();
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
r->open(strx::makeTstring(path));
|
|
||||||
#else
|
|
||||||
r->open(path);
|
|
||||||
#endif
|
|
||||||
if (!r->isOpened())
|
|
||||||
{
|
|
||||||
ICELogError(<< "Cannot open file to play");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delegate processing to existing audio device pair manager
|
|
||||||
mTerminal->audio()->player().add(usageId, r, lm == lmLoopAudio, timelimit);
|
|
||||||
start(usageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::stopPlayFile(int usageId)
|
|
||||||
{
|
|
||||||
stop(usageId);
|
|
||||||
mPlayer.release(usageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::onFilePlayed(Audio::Player::PlaylistItem& item)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioManager::process()
|
|
||||||
{
|
|
||||||
mPlayer.releasePlayed();
|
|
||||||
std::vector<int> ids;
|
|
||||||
mTerminal->audio()->player().retrieveUsageIds(ids);
|
|
||||||
for (unsigned i=0; i<ids.size(); i++)
|
|
||||||
stop(ids[i]);
|
|
||||||
}
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 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 __CLIENT_AUDIO_H
|
|
||||||
#define __CLIENT_AUDIO_H
|
|
||||||
|
|
||||||
#include "../engine/audio/Audio_Interface.h"
|
|
||||||
#include "../engine/audio/Audio_Player.h"
|
|
||||||
#include "../engine/media/MT_Box.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
AudioPrefix_Ring = 1,
|
|
||||||
AudioPrefix_Zero,
|
|
||||||
AudioPrefix_One,
|
|
||||||
AudioPrefix_Two,
|
|
||||||
AudioPrefix_Three,
|
|
||||||
AudioPrefix_Four,
|
|
||||||
AudioPrefix_Five,
|
|
||||||
AudioPrefix_Six,
|
|
||||||
AudioPrefix_Seven,
|
|
||||||
AudioPrefix_Eight,
|
|
||||||
AudioPrefix_Nine,
|
|
||||||
AudioPrefix_Asterisk,
|
|
||||||
AudioPrefix_Diez
|
|
||||||
};
|
|
||||||
|
|
||||||
#define AudioSessionCoeff 64
|
|
||||||
|
|
||||||
class AudioManager: public Audio::Player::EndOfAudioDelegate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AudioManager();
|
|
||||||
virtual ~AudioManager();
|
|
||||||
|
|
||||||
static AudioManager& instance();
|
|
||||||
|
|
||||||
// Enforces to close audio devices. Used to shutdown AudioManager on exit from application
|
|
||||||
void close();
|
|
||||||
|
|
||||||
// Terminal and settings must be available for AudioManager
|
|
||||||
void setTerminal(MT::Terminal* terminal);
|
|
||||||
MT::Terminal* terminal();
|
|
||||||
|
|
||||||
void setAudioMonitoring(Audio::DataConnection* monitoring);
|
|
||||||
Audio::DataConnection* audioMonitoring();
|
|
||||||
|
|
||||||
// Start/stop methods relies on usage counter; only first start and last stop opens/closes devices actually
|
|
||||||
void start(int usageId);
|
|
||||||
void stop(int usageId);
|
|
||||||
|
|
||||||
enum AudioTarget
|
|
||||||
{
|
|
||||||
atNull,
|
|
||||||
atReceiver,
|
|
||||||
atRinger
|
|
||||||
};
|
|
||||||
|
|
||||||
enum LoopMode
|
|
||||||
{
|
|
||||||
lmLoopAudio,
|
|
||||||
lmNoloop
|
|
||||||
};
|
|
||||||
|
|
||||||
void startPlayFile(int usageId, const std::string& path, AudioTarget target, LoopMode lm, int timelimit = 0);
|
|
||||||
void stopPlayFile(int usageId);
|
|
||||||
|
|
||||||
|
|
||||||
void onFilePlayed(Audio::Player::PlaylistItem& item);
|
|
||||||
|
|
||||||
// Must be called from main loop to release used audio devices
|
|
||||||
void process();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Audio::PInputDevice mAudioInput;
|
|
||||||
Audio::POutputDevice mAudioOutput;
|
|
||||||
Audio::Player mPlayer;
|
|
||||||
MT::Terminal* mTerminal;
|
|
||||||
Audio::DataConnection* mAudioMonitoring;
|
|
||||||
|
|
||||||
std::map<int, int> UsageMap;
|
|
||||||
UsageCounter mUsage;
|
|
||||||
std::mutex mGuard;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,856 +0,0 @@
|
||||||
#include "Agent_Impl.h"
|
|
||||||
#include "json/json.h"
|
|
||||||
#include "helper/HL_String.h"
|
|
||||||
#include "helper/HL_StreamState.h"
|
|
||||||
#include "helper/HL_VariantMap.h"
|
|
||||||
#include "helper/HL_CsvReader.h"
|
|
||||||
#include "helper/HL_Base64.h"
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
|
|
||||||
const std::string Status_Ok = "ok";
|
|
||||||
const std::string Status_SessionNotFound = "session not found";
|
|
||||||
const std::string Status_AccountNotFound = "account not found";
|
|
||||||
const std::string Status_FailedToOpenFile = "failed to open file";
|
|
||||||
const std::string Status_NoActiveProvider = "no active provider";
|
|
||||||
const std::string Status_NoMediaAction = "no valid media action";
|
|
||||||
const std::string Status_NoCommand = "no valid command";
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Agent"
|
|
||||||
|
|
||||||
AgentImpl::AgentImpl()
|
|
||||||
:mShutdown(false), mEventListChangeCondVar()
|
|
||||||
{
|
|
||||||
#if defined(TARGET_ANDROID) || defined(TARGET_WIN)
|
|
||||||
ice::GLogger.useDebugWindow(true);
|
|
||||||
#endif
|
|
||||||
ice::GLogger.setLevel(ice::LogLevel::LL_DEBUG);
|
|
||||||
}
|
|
||||||
|
|
||||||
AgentImpl::~AgentImpl()
|
|
||||||
{
|
|
||||||
stopAgentAndThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get access to internal audio manager. Value can be nullptr.
|
|
||||||
const std::shared_ptr<AudioManager>& AgentImpl::audioManager() const
|
|
||||||
{
|
|
||||||
return mAudioManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::setAudioMonitoring(Audio::DataConnection* monitoring)
|
|
||||||
{
|
|
||||||
mAudioMonitoring = monitoring;
|
|
||||||
if (mAudioManager)
|
|
||||||
mAudioManager->setAudioMonitoring(monitoring);
|
|
||||||
}
|
|
||||||
|
|
||||||
Audio::DataConnection* AgentImpl::monitoring() const
|
|
||||||
{
|
|
||||||
return mAudioMonitoring;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::run()
|
|
||||||
{
|
|
||||||
while (!mShutdown)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AgentImpl::command(const std::string& command)
|
|
||||||
{
|
|
||||||
if (command.empty())
|
|
||||||
return "";
|
|
||||||
|
|
||||||
JsonCpp::Value d, answer;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
JsonCpp::Reader r;
|
|
||||||
if (!r.parse(command, d))
|
|
||||||
return "";
|
|
||||||
|
|
||||||
std::string cmd = d["command"].asString();
|
|
||||||
if (cmd != "wait_for_event")
|
|
||||||
{
|
|
||||||
ICELogInfo(<< command);
|
|
||||||
}
|
|
||||||
if (cmd == "config")
|
|
||||||
processConfig(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "start")
|
|
||||||
processStart(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "stop")
|
|
||||||
processStop(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "account_create")
|
|
||||||
processCreateAccount(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "account_start")
|
|
||||||
processStartAccount(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "account_setuserinfo")
|
|
||||||
processSetUserInfoToAccount(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_create")
|
|
||||||
processCreateSession(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_start")
|
|
||||||
processStartSession(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_stop")
|
|
||||||
processStopSession(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_accept")
|
|
||||||
processAcceptSession(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_destroy")
|
|
||||||
processDestroySession(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_use_stream")
|
|
||||||
processUseStreamForSession(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "wait_for_event")
|
|
||||||
processWaitForEvent(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "session_get_media_stats")
|
|
||||||
processGetMediaStats(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "agent_network_changed")
|
|
||||||
processNetworkChanged(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "agent_add_root_cert")
|
|
||||||
processAddRootCert(d, answer);
|
|
||||||
else
|
|
||||||
if (cmd == "detach_log")
|
|
||||||
{
|
|
||||||
GLogger.closeFile();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (cmd == "attach_log")
|
|
||||||
{
|
|
||||||
GLogger.openFile();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (cmd == "log_message")
|
|
||||||
processLogMessage(d, answer);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
answer["status"] = Status_NoCommand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(std::exception& e)
|
|
||||||
{
|
|
||||||
answer["status"] = e.what();
|
|
||||||
}
|
|
||||||
std::string result = answer.toStyledString();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AgentImpl::waitForData(int /*milliseconds*/)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AgentImpl::read()
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processConfig(JsonCpp::Value &d, JsonCpp::Value &answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
|
|
||||||
std::string transport = d["transport"].asString();
|
|
||||||
config()[CONFIG_TRANSPORT] = (transport == "any") ? TransportType_Any : (transport == "udp" ? TransportType_Udp : (transport == "tcp" ? TransportType_Tcp : TransportType_Tls));
|
|
||||||
config()[CONFIG_IPV4] = d["ipv4"].asBool();
|
|
||||||
config()[CONFIG_IPV6] = d["ipv6"].asBool();
|
|
||||||
|
|
||||||
if (transport == "tls")
|
|
||||||
config()[CONFIG_SIPS] = true;
|
|
||||||
|
|
||||||
// Log file
|
|
||||||
std::string logfile = d["logfile"].asString();
|
|
||||||
ice::Logger& logger = ice::GLogger;
|
|
||||||
|
|
||||||
logger.useFile(logfile.empty() ? nullptr : logfile.c_str());
|
|
||||||
|
|
||||||
config()[CONFIG_MULTIPLEXING] = true;
|
|
||||||
config()[CONFIG_DISPLAYNAME] = "Voip quality tester";
|
|
||||||
|
|
||||||
mUseNativeAudio = d["nativeaudio"].asBool();
|
|
||||||
config()[CONFIG_OWN_DNS] = d["dns_servers"].asString();
|
|
||||||
config()[CONFIG_SIPS] = d["secure"].asBool();
|
|
||||||
config()[CONFIG_STUNSERVER_IP] = d["stun_server"].asString();
|
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processStart(JsonCpp::Value& request, JsonCpp::Value &answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
if (mThread)
|
|
||||||
{
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
return; // Started already
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process config (can be sent via start command as well)
|
|
||||||
// processConfig(request, answer);
|
|
||||||
|
|
||||||
// Start socket thread
|
|
||||||
SocketHeap::instance().start();
|
|
||||||
|
|
||||||
// Initialize terminal
|
|
||||||
MT::CodecList::Settings settings;
|
|
||||||
mTerminal = std::make_shared<MT::Terminal>(settings);
|
|
||||||
|
|
||||||
// Enable/disable codecs
|
|
||||||
PVariantMap priorityConfig = std::make_shared<VariantMap>();
|
|
||||||
MT::CodecList& cl = mTerminal->codeclist();
|
|
||||||
for (int i=0; i<cl.count(); i++)
|
|
||||||
priorityConfig->at(i) = i;
|
|
||||||
|
|
||||||
// Disable dynamic payload codec types - commented for now
|
|
||||||
/*if (cl.codecAt(i).payloadType() < 96)
|
|
||||||
priorityConfig->at(i) = i;
|
|
||||||
else
|
|
||||||
priorityConfig->at(i) = -1;*/
|
|
||||||
|
|
||||||
config()[CONFIG_CODEC_PRIORITY] = priorityConfig;
|
|
||||||
|
|
||||||
// Enable audio
|
|
||||||
mAudioManager = std::make_shared<AudioManager>();
|
|
||||||
mAudioManager->setTerminal(mTerminal.get());
|
|
||||||
if (mAudioMonitoring)
|
|
||||||
mAudioManager->setAudioMonitoring(mAudioMonitoring);
|
|
||||||
|
|
||||||
// Do not start audio manager here. Start right before call.
|
|
||||||
|
|
||||||
// Initialize endpoint
|
|
||||||
start();
|
|
||||||
|
|
||||||
// Start worker thread
|
|
||||||
mShutdown = false;
|
|
||||||
mThread = std::make_shared<std::thread>(&AgentImpl::run, this);
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processStop(JsonCpp::Value& /*request*/, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
stopAgentAndThread();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processCreateAccount(JsonCpp::Value &d, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
PVariantMap c = std::make_shared<VariantMap>();
|
|
||||||
|
|
||||||
(*c)[CONFIG_USERNAME] = d["username"].asString();
|
|
||||||
(*c)[CONFIG_PASSWORD] = d["password"].asString();
|
|
||||||
(*c)[CONFIG_DOMAIN] = d["domain"].asString();
|
|
||||||
(*c)[CONFIG_EXTERNALIP] = d["use_external_ip"].asBool();
|
|
||||||
|
|
||||||
auto nameAndPort = strx::parseHost(d["stun_server"].asString(), 3478);
|
|
||||||
(*c)[CONFIG_STUNSERVER_NAME] = nameAndPort.first;
|
|
||||||
(*c)[CONFIG_STUNSERVER_PORT] = nameAndPort.second;
|
|
||||||
|
|
||||||
PAccount account = createAccount(c);
|
|
||||||
mAccountMap[account->id()] = account;
|
|
||||||
answer["account_id"] = account->id();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processStartAccount(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
// Locate account in map
|
|
||||||
auto accountIter = mAccountMap.find(request["account_id"].asInt());
|
|
||||||
if (accountIter != mAccountMap.end())
|
|
||||||
{
|
|
||||||
accountIter->second->start();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_AccountNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processSetUserInfoToAccount(JsonCpp::Value &request, JsonCpp::Value &answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
// Locate account in map
|
|
||||||
auto accountIter = mAccountMap.find(request["account_id"].asInt());
|
|
||||||
if (accountIter != mAccountMap.end())
|
|
||||||
{
|
|
||||||
Account::UserInfo info;
|
|
||||||
JsonCpp::Value& arg = request["userinfo"];
|
|
||||||
std::vector<std::string> keys = arg.getMemberNames();
|
|
||||||
for (const std::string& k: keys)
|
|
||||||
info[k] = arg[k].asString();
|
|
||||||
accountIter->second->setUserInfo(info);
|
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_AccountNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processCreateSession(JsonCpp::Value &request, JsonCpp::Value &answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
auto accountIter = mAccountMap.find(request["account_id"].asInt());
|
|
||||||
if (accountIter != mAccountMap.end())
|
|
||||||
{
|
|
||||||
PSession session = createSession(accountIter->second);
|
|
||||||
mSessionMap[session->id()] = session;
|
|
||||||
answer["session_id"] = session->id();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_AccountNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
|
|
||||||
// Start audio manager
|
|
||||||
if (!mAudioManager)
|
|
||||||
{
|
|
||||||
// Agent was not started
|
|
||||||
ICELogError(<< "No audio manager installed.");
|
|
||||||
answer["status"] = "Audio manager not started. Most probably agent is not started.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
|
||||||
|
|
||||||
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
|
||||||
if (sessionIter != mSessionMap.end())
|
|
||||||
{
|
|
||||||
// Ensure audio provider is here
|
|
||||||
PSession session = sessionIter->second;
|
|
||||||
PDataProvider audioProvider = std::make_shared<AudioProvider>(*this, *mTerminal);
|
|
||||||
audioProvider->setState(audioProvider->state() | static_cast<int>(StreamState::Grabbing) | static_cast<int>(StreamState::Playing));
|
|
||||||
|
|
||||||
/*#if defined(USE_AQUA_LIBRARY)
|
|
||||||
std::string path_faults = request["path_faults"].asString();
|
|
||||||
|
|
||||||
sevana::aqua::config config = {
|
|
||||||
{"avlp", "off"},
|
|
||||||
{"smtnrm", "off"},
|
|
||||||
{"decor", "off"},
|
|
||||||
{"mprio", "off"},
|
|
||||||
{"npnt", "auto"},
|
|
||||||
{"voip", "on"},
|
|
||||||
{"enorm", "rms"},
|
|
||||||
{"g711", "off"},
|
|
||||||
{"spfrcor", "on"},
|
|
||||||
{"grad", "off"},
|
|
||||||
{"tmc", "on"},
|
|
||||||
{"miter", "1"},
|
|
||||||
{ "ratem", "%m" },
|
|
||||||
{ "trim", "a 10" },
|
|
||||||
{ "output", "json" },
|
|
||||||
{ "fau", path_faults},
|
|
||||||
{ "specp", "32"}
|
|
||||||
};
|
|
||||||
|
|
||||||
// std::string config = "-avlp on -smtnrm on -decor off -mprio off -npnt auto -voip off -enorm off -g711 on -spfrcor off -grad off -tmc on -miter 1 -trim a 10 -output json";
|
|
||||||
// if (temp_path.size())
|
|
||||||
// config += " -fau " + temp_path;
|
|
||||||
|
|
||||||
auto qc = std::make_shared<sevana::aqua>();
|
|
||||||
if (!qc->is_open())
|
|
||||||
{
|
|
||||||
ICELogError( << "Problem when initializing AQuA library");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
qc->configure_with(config);
|
|
||||||
|
|
||||||
mAquaMap[sessionIter->first] = qc;
|
|
||||||
dynamic_cast<AudioProvider*>(audioProvider.get())->configureMediaObserver(this, (void*)qc.get());
|
|
||||||
#endif
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: support SRTP via StreamState::Srtp option in audio provider state
|
|
||||||
|
|
||||||
// Get user headers
|
|
||||||
Session::UserHeaders info;
|
|
||||||
JsonCpp::Value& arg = request["userinfo"];
|
|
||||||
std::vector<std::string> keys = arg.getMemberNames();
|
|
||||||
for (const std::string& k: keys)
|
|
||||||
info[k] = arg[k].asString();
|
|
||||||
session->setUserHeaders(info);
|
|
||||||
|
|
||||||
session->addProvider(audioProvider);
|
|
||||||
session->start(request["target"].asString());
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
answer["status"] = Status_SessionNotFound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processStopSession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
|
|
||||||
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
|
||||||
if (sessionIter != mSessionMap.end())
|
|
||||||
{
|
|
||||||
PSession session = sessionIter->second;
|
|
||||||
session->stop();
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_SessionNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
auto sessionIter = mSessionMap.find(request["session_id"].asInt());
|
|
||||||
if (sessionIter != mSessionMap.end())
|
|
||||||
{
|
|
||||||
// Ensure audio manager is here
|
|
||||||
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
|
||||||
|
|
||||||
// Accept session on SIP level
|
|
||||||
PSession session = sessionIter->second;
|
|
||||||
|
|
||||||
// Get user headers
|
|
||||||
Session::UserHeaders info;
|
|
||||||
JsonCpp::Value& arg = request["userinfo"];
|
|
||||||
std::vector<std::string> keys = arg.getMemberNames();
|
|
||||||
for (const std::string& k: keys)
|
|
||||||
info[k] = arg[k].asString();
|
|
||||||
session->setUserHeaders(info);
|
|
||||||
|
|
||||||
// Accept finally
|
|
||||||
session->accept();
|
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_SessionNotFound;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
|
|
||||||
int sessionId = request["session_id"].asInt();
|
|
||||||
auto sessionIter = mSessionMap.find(sessionId);
|
|
||||||
if (sessionIter != mSessionMap.end())
|
|
||||||
mSessionMap.erase(sessionIter);
|
|
||||||
//#if defined(USE_AQUA_LIBRARY)
|
|
||||||
// closeAqua(sessionId);
|
|
||||||
//#endif
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processWaitForEvent(JsonCpp::Value &request, JsonCpp::Value &answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
|
|
||||||
//int x = 0;
|
|
||||||
//int y = 1/x;
|
|
||||||
|
|
||||||
int timeout = request["timeout"].asInt();
|
|
||||||
std::unique_lock<std::mutex> eventLock(mEventListMutex);
|
|
||||||
if (mEventList.empty())
|
|
||||||
mEventListChangeCondVar.wait_for(eventLock, chrono::milliseconds(timeout));
|
|
||||||
|
|
||||||
if (!mEventList.empty())
|
|
||||||
{
|
|
||||||
answer = mEventList.front();
|
|
||||||
mEventList.erase(mEventList.begin());
|
|
||||||
}
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
int sessionId = request["session_id"].asInt();
|
|
||||||
SessionMap::iterator sessionIter = mSessionMap.find(sessionId);
|
|
||||||
if (sessionIter != mSessionMap.end())
|
|
||||||
{
|
|
||||||
PSession session = sessionIter->second;
|
|
||||||
VariantMap result;
|
|
||||||
session->getSessionInfo(Session::InfoOptions::Detailed,
|
|
||||||
result);
|
|
||||||
|
|
||||||
if (result.exists(SessionInfo_AudioCodec))
|
|
||||||
answer["codec"] = result[SessionInfo_AudioCodec].asStdString();
|
|
||||||
if (result.exists(SessionInfo_NetworkMos))
|
|
||||||
answer["network_mos"] = result[SessionInfo_NetworkMos].asFloat();
|
|
||||||
if (result.exists(SessionInfo_PacketLoss))
|
|
||||||
answer["rtp_lost"] = result[SessionInfo_LostRtp].asInt();
|
|
||||||
if (result.exists(SessionInfo_DroppedRtp))
|
|
||||||
answer["rtp_dropped"] = result[SessionInfo_DroppedRtp].asInt();
|
|
||||||
|
|
||||||
if (result.exists(SessionInfo_SentRtp))
|
|
||||||
answer["rtp_sent"] = result[SessionInfo_SentRtp].asInt();
|
|
||||||
if (result.exists(SessionInfo_ReceivedRtp))
|
|
||||||
answer["rtp_received"] = result[SessionInfo_ReceivedRtp].asInt();
|
|
||||||
if (result.exists(SessionInfo_Duration))
|
|
||||||
answer["duration"] = result[SessionInfo_Duration].asInt();
|
|
||||||
if (result.exists(SessionInfo_Jitter))
|
|
||||||
answer["jitter"] = result[SessionInfo_Jitter].asFloat() * 1000; // Take milliseconds
|
|
||||||
if (result.exists(SessionInfo_Rtt))
|
|
||||||
answer["rtt"] = result[SessionInfo_Rtt].asFloat();
|
|
||||||
if (result.exists(SessionInfo_BitrateSwitchCounter))
|
|
||||||
answer["bitrate_switch_counter"] = result[SessionInfo_BitrateSwitchCounter].asInt();
|
|
||||||
if (result.exists(SessionInfo_SSRC))
|
|
||||||
answer["rtp_ssrc"] = result[SessionInfo_SSRC].asInt();
|
|
||||||
if (result.exists(SessionInfo_RemotePeer))
|
|
||||||
answer["rtp_remotepeer"] = result[SessionInfo_RemotePeer].asStdString();
|
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_SessionNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processNetworkChanged(JsonCpp::Value& /*request*/, JsonCpp::Value& /*answer*/)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string BeginCertificate = "-----BEGIN CERTIFICATE-----";
|
|
||||||
const std::string EndCertificate = "-----END CERTIFICATE-----";
|
|
||||||
|
|
||||||
void AgentImpl::processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
std::string pem = request["cert"].asString();
|
|
||||||
|
|
||||||
std::string::size_type pb = 0, pe = 0;
|
|
||||||
while (pb != std::string::npos && pe != std::string::npos) {
|
|
||||||
pb = pem.find(BeginCertificate, pb);
|
|
||||||
pe = pem.find(EndCertificate, pe);
|
|
||||||
|
|
||||||
if (pb != std::string::npos && pe != std::string::npos && pe > pb) {
|
|
||||||
std::string cert = pem.substr(pb, pe - pb + EndCertificate.size());
|
|
||||||
addRootCert(ByteBuffer(cert.c_str(), cert.size()));
|
|
||||||
|
|
||||||
pb = ++pe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processLogMessage(JsonCpp::Value &request, JsonCpp::Value &answer)
|
|
||||||
{
|
|
||||||
int level = request["level"].asInt();
|
|
||||||
std::string message = request["message"].asString();
|
|
||||||
|
|
||||||
ICELog(static_cast<ice::LogLevel>(level), "App", << message);
|
|
||||||
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::stopAgentAndThread()
|
|
||||||
{
|
|
||||||
// Stop user agent
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Stop worker thread
|
|
||||||
if (mThread)
|
|
||||||
{
|
|
||||||
mShutdown = true;
|
|
||||||
if (mThread->joinable())
|
|
||||||
{
|
|
||||||
l.unlock();
|
|
||||||
mThread->join();
|
|
||||||
l.lock();
|
|
||||||
}
|
|
||||||
mThread.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close used audio
|
|
||||||
if (mAudioManager)
|
|
||||||
{
|
|
||||||
// Ensure audio manager is stopped
|
|
||||||
mAudioManager->stop(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
|
||||||
|
|
||||||
// Free audio manager
|
|
||||||
mAudioManager.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close terminal after audio manager - because audio manager has reference to terminal
|
|
||||||
mTerminal.reset();
|
|
||||||
|
|
||||||
SocketHeap::instance().stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Value& answer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::recursive_mutex> l(mAgentMutex);
|
|
||||||
SessionMap::iterator sessionIter = mSessionMap.find(request["session_id"].asInt());
|
|
||||||
if (sessionIter != mSessionMap.end())
|
|
||||||
{
|
|
||||||
// Extract ptr to session
|
|
||||||
PSession session = sessionIter->second;
|
|
||||||
|
|
||||||
// Parse command
|
|
||||||
std::string actionText = request["media_action"].asString(),
|
|
||||||
directionText = request["media_direction"].asString();
|
|
||||||
|
|
||||||
MT::Stream::MediaDirection direction = directionText == "incoming" ? MT::Stream::MediaDirection::Incoming
|
|
||||||
: MT::Stream::MediaDirection::Outgoing;
|
|
||||||
std::string path = request["path"].asString();
|
|
||||||
|
|
||||||
// Try to open file
|
|
||||||
AudioProvider* prov = session->findProviderForActiveAudio();
|
|
||||||
if (prov)
|
|
||||||
{
|
|
||||||
if (actionText == "read")
|
|
||||||
{
|
|
||||||
if (path.empty())
|
|
||||||
{
|
|
||||||
// Turn off playing into the stream
|
|
||||||
prov->readFile(Audio::PWavFileReader(), direction);
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Audio::PWavFileReader reader = std::make_shared<Audio::WavFileReader>();
|
|
||||||
if (!reader->open(strx::makeTstring(path)))
|
|
||||||
answer["status"] = Status_FailedToOpenFile;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prov->readFile(reader, direction);
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (actionText == "write")
|
|
||||||
{
|
|
||||||
if (path.empty())
|
|
||||||
{
|
|
||||||
// Turn off recording from the stream
|
|
||||||
prov->writeFile(Audio::PWavFileWriter(), direction);
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Audio::PWavFileWriter writer = std::make_shared<Audio::WavFileWriter>();
|
|
||||||
if (!writer->open(strx::makeTstring(path), AUDIO_SAMPLERATE, AUDIO_CHANNELS))
|
|
||||||
answer["status"] = Status_FailedToOpenFile;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
prov->writeFile(writer, direction);
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (actionText == "mirror")
|
|
||||||
{
|
|
||||||
prov->setupMirror(request["enable"].asBool());
|
|
||||||
answer["status"] = Status_Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_AccountNotFound;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
answer["status"] = Status_NoMediaAction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::onMedia(const void* data, int length, MT::Stream::MediaDirection direction, void* context, void* userTag)
|
|
||||||
{
|
|
||||||
/*switch (direction)
|
|
||||||
{
|
|
||||||
case MT::Stream::MediaDirection::Incoming: mAquaIncoming.appendBuffer(data, length); break;
|
|
||||||
case MT::Stream::MediaDirection::Outgoing: mAquaOutgoing.appendBuffer(data, length); break;
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Called on new incoming session; providers shoukld
|
|
||||||
#define EVENT_WITH_NAME(X) JsonCpp::Value v; v["event_name"] = X;
|
|
||||||
|
|
||||||
PDataProvider AgentImpl::onProviderNeeded(const std::string& name)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("provider_needed");
|
|
||||||
v["provider_name"] = name;
|
|
||||||
addEvent(v);
|
|
||||||
|
|
||||||
return PDataProvider(new AudioProvider(*this, *mTerminal));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called on new session offer
|
|
||||||
void AgentImpl::onNewSession(PSession s)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_incoming");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
v["remote_peer"] = s->remoteAddress();
|
|
||||||
mSessionMap[s->id()] = s;
|
|
||||||
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when session is terminated
|
|
||||||
void AgentImpl::onSessionTerminated(PSession s, int responsecode, int reason)
|
|
||||||
{
|
|
||||||
/*if (mIncomingAudioDump)
|
|
||||||
mIncomingAudioDump->close();
|
|
||||||
if (mOutgoingAudioDump)
|
|
||||||
mOutgoingAudioDump->close();
|
|
||||||
*/
|
|
||||||
mAudioManager->stop(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
|
|
||||||
// Gather statistics before
|
|
||||||
EVENT_WITH_NAME("session_terminated");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
v["error_code"] = responsecode;
|
|
||||||
v["reason_code"] = reason;
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when session is established ok i.e. after all ICE signalling is finished
|
|
||||||
// Conntype is type of establish event - EV_SIP or EV_ICE
|
|
||||||
void AgentImpl::onSessionEstablished(PSession s, int conntype, const RtpPair<InternetAddress>& p)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_established");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
v["conn_type"] = conntype == EV_SIP ? "sip" : "ice";
|
|
||||||
if (conntype == EV_ICE)
|
|
||||||
v["media_target"] = p.mRtp.toStdString();
|
|
||||||
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::onSessionProvisional(PSession s, int code)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_provisional");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
v["code"] = code;
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when user agent started
|
|
||||||
void AgentImpl::onStart(int errorcode)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("agent_started");
|
|
||||||
v["error_code"] = errorcode;
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when user agent stopped
|
|
||||||
void AgentImpl::onStop()
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("agent_stopped");
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when account registered
|
|
||||||
void AgentImpl::onAccountStart(PAccount account)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("account_started");
|
|
||||||
v["account_id"] = account->id();
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when account removed or failed (non zero error code)
|
|
||||||
void AgentImpl::onAccountStop(PAccount account, int error)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("account_stopped");
|
|
||||||
v["account_id"] = account->id();
|
|
||||||
v["error_code"] = error;
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when connectivity checks failed.
|
|
||||||
void AgentImpl::onConnectivityFailed(PSession s)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_connectivity_failed");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when new candidate is gathered
|
|
||||||
void AgentImpl::onCandidateGathered(PSession s, const char* address)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_candidate_gathered");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
v["address"] = address;
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when network change detected
|
|
||||||
void AgentImpl::onNetworkChange(PSession s)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_network_changed");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when all candidates are gathered
|
|
||||||
void AgentImpl::onGathered(PSession s)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_candidates_gathered");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when new connectivity check is finished
|
|
||||||
void AgentImpl::onCheckFinished(PSession s, const char* description)
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("session_conncheck_finished");
|
|
||||||
v["session_id"] = s->id();
|
|
||||||
v["description"] = description;
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when log message must be recorded
|
|
||||||
void AgentImpl::onLog(const char* /*msg*/)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// Called when problem with SIP connection(s) detected
|
|
||||||
void AgentImpl::onSipConnectionFailed()
|
|
||||||
{
|
|
||||||
EVENT_WITH_NAME("sip_connection_failed");
|
|
||||||
addEvent(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgentImpl::addEvent(const JsonCpp::Value& v)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(mEventListMutex);
|
|
||||||
mEventList.push_back(v);
|
|
||||||
mEventListChangeCondVar.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(USE_AQUA_LIBRARY)
|
|
||||||
/*void AgentImpl::closeAqua(int sessionId)
|
|
||||||
{
|
|
||||||
auto aquaIter = mAquaMap.find(sessionId);
|
|
||||||
if (aquaIter != mAquaMap.end()) {
|
|
||||||
aquaIter->second->close();
|
|
||||||
mAquaMap.erase(aquaIter);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
#ifndef __AGENT_IMPL_H
|
|
||||||
#define __AGENT_IMPL_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
#include "../endpoint/EP_Engine.h"
|
|
||||||
#include "../endpoint/EP_AudioProvider.h"
|
|
||||||
#include "../media/MT_Box.h"
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
|
|
||||||
#include "json/json.h"
|
|
||||||
#include "Agent_AudioManager.h"
|
|
||||||
#include <mutex>
|
|
||||||
#include <condition_variable>
|
|
||||||
|
|
||||||
|
|
||||||
class AgentImpl: public UserAgent, public MT::Stream::MediaObserver
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
std::recursive_mutex mAgentMutex;
|
|
||||||
std::mutex mEventListMutex;
|
|
||||||
std::condition_variable mEventListChangeCondVar;
|
|
||||||
std::vector<JsonCpp::Value> mEventList;
|
|
||||||
bool mUseNativeAudio = false;
|
|
||||||
|
|
||||||
typedef std::map<int, PAccount> AccountMap;
|
|
||||||
AccountMap mAccountMap;
|
|
||||||
|
|
||||||
typedef std::map<int, PSession> SessionMap;
|
|
||||||
SessionMap mSessionMap;
|
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<std::thread> mThread;
|
|
||||||
volatile bool mShutdown;
|
|
||||||
std::shared_ptr<MT::Terminal> mTerminal;
|
|
||||||
std::shared_ptr<AudioManager> mAudioManager;
|
|
||||||
Audio::DataConnection* mAudioMonitoring = nullptr;
|
|
||||||
|
|
||||||
void run();
|
|
||||||
void addEvent(const JsonCpp::Value& v);
|
|
||||||
void processConfig(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processStart(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processStop(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processCreateAccount(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processStartAccount(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processSetUserInfoToAccount(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processCreateSession(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processStartSession(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processStopSession(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processWaitForEvent(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processNetworkChanged(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void processLogMessage(JsonCpp::Value& request, JsonCpp::Value& answer);
|
|
||||||
void stopAgentAndThread();
|
|
||||||
|
|
||||||
public:
|
|
||||||
AgentImpl();
|
|
||||||
~AgentImpl();
|
|
||||||
|
|
||||||
std::string command(const std::string& command);
|
|
||||||
bool waitForData(int milliseconds);
|
|
||||||
std::string read();
|
|
||||||
|
|
||||||
// Get access to internal audio manager. Value can be nullptr.
|
|
||||||
const std::shared_ptr<AudioManager>& audioManager() const;
|
|
||||||
|
|
||||||
void setAudioMonitoring(Audio::DataConnection* monitoring);
|
|
||||||
Audio::DataConnection* monitoring() const;
|
|
||||||
|
|
||||||
// UserAgent overrides
|
|
||||||
// Called on new incoming session; providers shoukld
|
|
||||||
PDataProvider onProviderNeeded(const std::string& name) override;
|
|
||||||
|
|
||||||
// Called on new session offer
|
|
||||||
void onNewSession(PSession s) override;
|
|
||||||
|
|
||||||
// Called when session is terminated
|
|
||||||
void onSessionTerminated(PSession s, int responsecode, int reason) override;
|
|
||||||
|
|
||||||
// Called when session is established ok i.e. after all ICE signalling is finished
|
|
||||||
// Conntype is type of establish event - EV_SIP or EV_ICE
|
|
||||||
void onSessionEstablished(PSession s, int conntype, const RtpPair<InternetAddress>& p) override;
|
|
||||||
|
|
||||||
void onSessionProvisional(PSession s, int code) override;
|
|
||||||
|
|
||||||
// Called when user agent started
|
|
||||||
void onStart(int errorcode) override;
|
|
||||||
|
|
||||||
// Called when user agent stopped
|
|
||||||
void onStop() override;
|
|
||||||
|
|
||||||
// Called when account registered
|
|
||||||
void onAccountStart(PAccount account) override;
|
|
||||||
|
|
||||||
// Called when account removed or failed (non zero error code)
|
|
||||||
void onAccountStop(PAccount account, int error) override;
|
|
||||||
|
|
||||||
// Called when connectivity checks failed.
|
|
||||||
void onConnectivityFailed(PSession s) override;
|
|
||||||
|
|
||||||
// Called when new candidate is gathered
|
|
||||||
void onCandidateGathered(PSession s, const char* address) override;
|
|
||||||
|
|
||||||
// Called when network change detected
|
|
||||||
void onNetworkChange(PSession s) override;
|
|
||||||
|
|
||||||
// Called when all candidates are gathered
|
|
||||||
void onGathered(PSession s) override;
|
|
||||||
|
|
||||||
// Called when new connectivity check is finished
|
|
||||||
void onCheckFinished(PSession s, const char* description) override;
|
|
||||||
|
|
||||||
// Called when log message must be recorded
|
|
||||||
void onLog(const char* msg) override;
|
|
||||||
|
|
||||||
// Called when problem with SIP connection(s) detected
|
|
||||||
void onSipConnectionFailed() override;
|
|
||||||
|
|
||||||
// Called on incoming & outgoing audio for voice sessions
|
|
||||||
void onMedia(const void* data, int length, MT::Stream::MediaDirection direction, void* context, void* userTag) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#include "Agent_Interface.h"
|
|
||||||
#include "Agent_Impl.h"
|
|
||||||
Agent::Agent()
|
|
||||||
:mContext(new AgentImpl());
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Agent::~Agent()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Agent::write(const std::string& command)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Agent::waitForData(int milliseconds)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Agent::read()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#ifndef __AGENT_JSON_H
|
|
||||||
#define __AGENT_JSON_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class Agent
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
void* mContext;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Agent();
|
|
||||||
~Agent();
|
|
||||||
void write(const std::string& command);
|
|
||||||
bool waitForData(int milliseconds);
|
|
||||||
std::string read();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,599 +0,0 @@
|
||||||
#include "Audio_Android.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
#include <mutex>
|
|
||||||
#include <iostream>
|
|
||||||
#include "../helper/HL_String.h"
|
|
||||||
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Audio"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------- AndroidEnumerator -----------------------------
|
|
||||||
|
|
||||||
AndroidEnumerator::AndroidEnumerator()
|
|
||||||
{}
|
|
||||||
|
|
||||||
AndroidEnumerator::~AndroidEnumerator()
|
|
||||||
{}
|
|
||||||
|
|
||||||
int AndroidEnumerator::indexOfDefaultDevice()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidEnumerator::count()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidEnumerator::idAt(int index)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AndroidEnumerator::nameAt(int index)
|
|
||||||
{
|
|
||||||
return "Audio";
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidEnumerator::open(int direction)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void AndroidEnumerator::close()
|
|
||||||
{}
|
|
||||||
|
|
||||||
// -----------------------
|
|
||||||
|
|
||||||
OpenSLEngine::OpenSLEngine()
|
|
||||||
{}
|
|
||||||
|
|
||||||
OpenSLEngine::~OpenSLEngine()
|
|
||||||
{}
|
|
||||||
|
|
||||||
void OpenSLEngine::open()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
if (++mUsageCounter == 1)
|
|
||||||
internalOpen();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenSLEngine::close()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
if (mUsageCounter == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (--mUsageCounter == 0)
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CHECK_OPENSLES_ERROR if (resultCode != SL_RESULT_SUCCESS) throw Exception(ERR_OPENSLES, (int)resultCode)
|
|
||||||
|
|
||||||
void OpenSLEngine::internalOpen()
|
|
||||||
{
|
|
||||||
SLresult resultCode;
|
|
||||||
|
|
||||||
// Instantiate OpenSL ES engine object
|
|
||||||
resultCode = slCreateEngine(&mEngineObject, 0, nullptr, 0, nullptr, nullptr);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Bring it online (realize)
|
|
||||||
resultCode = (*mEngineObject)->Realize(mEngineObject, SL_BOOLEAN_FALSE);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Get interface finally
|
|
||||||
resultCode = (*mEngineObject)->GetInterface(mEngineObject, SL_IID_ENGINE, &mEngineInterface);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
ICELogInfo(<< "OpenSL engine object created.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpenSLEngine::internalClose()
|
|
||||||
{
|
|
||||||
if (mEngineObject != nullptr)
|
|
||||||
{
|
|
||||||
ICELogInfo(<< "Destroy OpenSL engine object.");
|
|
||||||
|
|
||||||
(*mEngineObject)->Destroy(mEngineObject);
|
|
||||||
mEngineObject = nullptr;
|
|
||||||
mEngineInterface = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SLEngineItf OpenSLEngine::getNativeEngine() const
|
|
||||||
{
|
|
||||||
return mEngineInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
static OpenSLEngine OpenSLEngineInstance;
|
|
||||||
|
|
||||||
OpenSLEngine& OpenSLEngine::instance()
|
|
||||||
{
|
|
||||||
return OpenSLEngineInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------- Input implementation ----------------
|
|
||||||
AndroidInputDevice::AndroidInputDevice(int devId)
|
|
||||||
{}
|
|
||||||
|
|
||||||
AndroidInputDevice::~AndroidInputDevice()
|
|
||||||
{}
|
|
||||||
|
|
||||||
static int RateToProbe[12][2] = {
|
|
||||||
{ SL_SAMPLINGRATE_16, 16000 },
|
|
||||||
{ SL_SAMPLINGRATE_8, 8000 },
|
|
||||||
{ SL_SAMPLINGRATE_32, 32000 },
|
|
||||||
{ SL_SAMPLINGRATE_44_1, 44100 },
|
|
||||||
{ SL_SAMPLINGRATE_11_025, 10025 },
|
|
||||||
{ SL_SAMPLINGRATE_22_05, 22050 },
|
|
||||||
{ SL_SAMPLINGRATE_24, 24000 },
|
|
||||||
{ SL_SAMPLINGRATE_48, 48000 },
|
|
||||||
{ SL_SAMPLINGRATE_64, 64000 },
|
|
||||||
{ SL_SAMPLINGRATE_88_2, 88200 },
|
|
||||||
{ SL_SAMPLINGRATE_96, 96000 },
|
|
||||||
{ SL_SAMPLINGRATE_192, 192000} };
|
|
||||||
|
|
||||||
bool AndroidInputDevice::open()
|
|
||||||
{
|
|
||||||
if (active())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
OpenSLEngine::instance().open();
|
|
||||||
|
|
||||||
// Probe few sampling rates
|
|
||||||
bool opened = false;
|
|
||||||
for (int rateIndex = 0; rateIndex < 12 && !opened; rateIndex++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
internalOpen(RateToProbe[rateIndex][0], RateToProbe[rateIndex][1]);
|
|
||||||
mDeviceRate = RateToProbe[rateIndex][1];
|
|
||||||
ICELogInfo(<< "Input Opened with rate " << mDeviceRate << " and rate index " << rateIndex);
|
|
||||||
opened = mDeviceRate != 0;
|
|
||||||
if (!opened)
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{
|
|
||||||
opened = false;
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mActive = opened;
|
|
||||||
|
|
||||||
return opened;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::close()
|
|
||||||
{
|
|
||||||
// There is no check for active() value because close() can be called to cleanup after bad open() call.
|
|
||||||
internalClose();
|
|
||||||
OpenSLEngine::instance().close();
|
|
||||||
mActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Format AndroidInputDevice::getFormat()
|
|
||||||
{
|
|
||||||
return Format(mDeviceRate, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidInputDevice::active() const
|
|
||||||
{
|
|
||||||
return mActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidInputDevice::fakeMode()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::setFakeMode(bool fakemode)
|
|
||||||
{}
|
|
||||||
|
|
||||||
int AndroidInputDevice::readBuffer(void* buffer)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
while (mSdkRateCache.filled() < AUDIO_MIC_BUFFER_SIZE)
|
|
||||||
{
|
|
||||||
mDataCondVar.wait(l);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSdkRateCache.read(buffer, AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CHECK_SL_INTERFACE(INTF, ERR) {if (!INTF) throw Exception(ERR_OPENSLES, ERR); if (!(*INTF)) throw Exception(ERR_OPENSLES, ERR);}
|
|
||||||
|
|
||||||
void AndroidInputDevice::internalOpen(int rateCode, int rate)
|
|
||||||
{
|
|
||||||
SLresult resultCode = 0;
|
|
||||||
SLuint32 nrOfChannels = 1;
|
|
||||||
|
|
||||||
// Prepare audio source
|
|
||||||
SLDataLocator_IODevice devDescription = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
|
|
||||||
SLDataSource audioSource = { &devDescription, NULL };
|
|
||||||
|
|
||||||
// Source flags
|
|
||||||
SLuint32 speakersFlags = nrOfChannels > 1 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER;
|
|
||||||
|
|
||||||
// Buffer queue
|
|
||||||
SLDataLocator_AndroidSimpleBufferQueue queueDescription = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 };
|
|
||||||
|
|
||||||
// Audio format
|
|
||||||
SLDataFormat_PCM formatDescription = { SL_DATAFORMAT_PCM, nrOfChannels, (SLuint32)rateCode, SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16, (SLuint32)speakersFlags, SL_BYTEORDER_LITTLEENDIAN };
|
|
||||||
|
|
||||||
SLDataSink audioSink = { &queueDescription, &formatDescription };
|
|
||||||
|
|
||||||
// Create recorder
|
|
||||||
// Do not forget about RECORD_AUDIO permission
|
|
||||||
const SLInterfaceID interfacesList[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
|
|
||||||
const SLboolean interfacesRequirements[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
|
|
||||||
|
|
||||||
// Get access to OpenSL engine
|
|
||||||
SLEngineItf engine_interface = OpenSLEngine::instance().getNativeEngine();
|
|
||||||
CHECK_SL_INTERFACE(engine_interface, -1);
|
|
||||||
|
|
||||||
resultCode = (*engine_interface)->CreateAudioRecorder(
|
|
||||||
OpenSLEngine::instance().getNativeEngine(),
|
|
||||||
&mRecorderObject, &audioSource, &audioSink, 2, interfacesList, interfacesRequirements);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
CHECK_SL_INTERFACE(mRecorderObject, -2);
|
|
||||||
|
|
||||||
// Obtain stream type
|
|
||||||
resultCode = (*mRecorderObject)->GetInterface(mRecorderObject, SL_IID_ANDROIDCONFIGURATION, &mAndroidCfg);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Now audio recorder goes to real world
|
|
||||||
resultCode = (*mRecorderObject)->Realize(mRecorderObject, SL_BOOLEAN_FALSE);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Get recorder interface
|
|
||||||
resultCode = (*mRecorderObject)->GetInterface(mRecorderObject, SL_IID_RECORD, &mRecorderInterface);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
CHECK_SL_INTERFACE(mRecorderInterface, -3);
|
|
||||||
|
|
||||||
// Now buffer queue interface...
|
|
||||||
resultCode = (*mRecorderObject)->GetInterface(mRecorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &mRecorderBufferInterface);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
CHECK_SL_INTERFACE(mRecorderBufferInterface, -4);
|
|
||||||
|
|
||||||
// Resampler is needed to provide SDK's rate
|
|
||||||
mResampler = std::make_shared<Resampler>();
|
|
||||||
mResampler->start(nrOfChannels, rate, AUDIO_SAMPLERATE);
|
|
||||||
|
|
||||||
// Allocate recorder buffer size
|
|
||||||
mBufferSize = (AUDIO_MIC_BUFFER_LENGTH / 10) * (rate / 100) * 2;
|
|
||||||
mRecorderBuffer.setCapacity(mBufferSize * AUDIO_MIC_BUFFER_COUNT);
|
|
||||||
mRecorderBufferIndex = 0;
|
|
||||||
|
|
||||||
// Setup data consuming callback
|
|
||||||
resultCode = (*mRecorderBufferInterface)->RegisterCallback(mRecorderBufferInterface, DeviceCallback, (void*)this);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Setup buffers
|
|
||||||
for (int i=0; i<AUDIO_MIC_BUFFER_COUNT; i++)
|
|
||||||
(*mRecorderBufferInterface)->Enqueue(mRecorderBufferInterface, mRecorderBuffer.data() + i * mBufferSize, mBufferSize);
|
|
||||||
|
|
||||||
// Start finally
|
|
||||||
resultCode = (*mRecorderInterface)->SetRecordState(mRecorderInterface, SL_RECORDSTATE_RECORDING);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::internalClose()
|
|
||||||
{
|
|
||||||
if (!mRecorderObject)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (*mRecorderObject)
|
|
||||||
{
|
|
||||||
if (active())
|
|
||||||
{
|
|
||||||
// Stop recording
|
|
||||||
(*mRecorderInterface)->SetRecordState(mRecorderInterface, SL_RECORDSTATE_STOPPED);
|
|
||||||
|
|
||||||
// Wait until recording will not stop really
|
|
||||||
SLuint32 state = SL_RECORDSTATE_STOPPED;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
(*mRecorderInterface)->GetRecordState(mRecorderInterface, &state);
|
|
||||||
SyncHelper::delay(1);
|
|
||||||
}
|
|
||||||
while (state == SL_RECORDSTATE_RECORDING);
|
|
||||||
}
|
|
||||||
(*mRecorderObject)->Destroy(mRecorderObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
mRecorderObject = nullptr;
|
|
||||||
mRecorderInterface = nullptr;
|
|
||||||
mRecorderBufferInterface = nullptr;
|
|
||||||
mAndroidCfg = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::handleCallback(SLAndroidSimpleBufferQueueItf bq)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
|
|
||||||
// Send data to AudioPair
|
|
||||||
if (mConnection)
|
|
||||||
mConnection->onMicData(getFormat(), mRecorderBuffer.data() + mRecorderBufferIndex * mBufferSize, mBufferSize);
|
|
||||||
/*
|
|
||||||
// Send audio to cache with native sample rate
|
|
||||||
mDeviceRateCache.add(mRecorderBuffer.data() + mRecorderBufferIndex * mBufferSize, mBufferSize);
|
|
||||||
|
|
||||||
// Check if there is enough data (10 ms) to send
|
|
||||||
int tenMsSize = (int)Format(mDeviceRate, 1).sizeFromTime(10);
|
|
||||||
while (mDeviceRateCache.filled() >= tenMsSize)
|
|
||||||
{
|
|
||||||
char* resampled = (char*)alloca(Format().sizeFromTime(10));
|
|
||||||
int processed = 0;
|
|
||||||
int outlen = mResampler->processBuffer(mDeviceRateCache.data(), tenMsSize, processed, resampled, Format().sizeFromTime(10));
|
|
||||||
if (outlen > 0)
|
|
||||||
mSdkRateCache.add(resampled, (int)Format().sizeFromTime(10));
|
|
||||||
mDeviceRateCache.erase(tenMsSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell about data
|
|
||||||
while (mSdkRateCache.filled() >= AUDIO_MIC_BUFFER_SIZE)
|
|
||||||
{
|
|
||||||
if (mConnection)
|
|
||||||
mConnection->onMicData(Format(), mSdkRateCache.data(), AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
mSdkRateCache.erase(AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// Re-enqueue used buffer
|
|
||||||
(*mRecorderBufferInterface)->Enqueue(mRecorderBufferInterface, mRecorderBuffer.data() + mRecorderBufferIndex * mBufferSize, mBufferSize);
|
|
||||||
mRecorderBufferIndex++;
|
|
||||||
mRecorderBufferIndex %= AUDIO_MIC_BUFFER_COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::DeviceCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (context)
|
|
||||||
reinterpret_cast<AndroidInputDevice*>(context)->handleCallback(bq);
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------ AndroidOutputDevice -----------------
|
|
||||||
AndroidOutputDevice::AndroidOutputDevice(int devId)
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << strx::toHex(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidOutputDevice::~AndroidOutputDevice()
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "Deleting AndroidOutputDevice.");
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidOutputDevice::open()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
bool opened = false;
|
|
||||||
for (int rateIndex = 0; rateIndex < 12 && !opened; rateIndex++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
internalOpen(RateToProbe[rateIndex][0], RateToProbe[rateIndex][1], true);
|
|
||||||
opened = true;
|
|
||||||
mDeviceRate = RateToProbe[rateIndex][1];
|
|
||||||
ICELogCritical(<< "Output opened with rate " << mDeviceRate << " and index " << rateIndex);
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{
|
|
||||||
opened = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (opened)
|
|
||||||
ICELogInfo(<< "Speaker opened on rate " << mDeviceRate);
|
|
||||||
|
|
||||||
return opened;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::close()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Format AndroidOutputDevice::getFormat()
|
|
||||||
{
|
|
||||||
return Format(mDeviceRate, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidOutputDevice::fakeMode()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::setFakeMode(bool fakemode)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::internalOpen(int rateId, int rate, bool voice)
|
|
||||||
{
|
|
||||||
mInShutdown = false;
|
|
||||||
|
|
||||||
SLresult resultCode;
|
|
||||||
SLuint32 channels = 1;
|
|
||||||
|
|
||||||
// Configure audio source
|
|
||||||
SLDataLocator_AndroidSimpleBufferQueue queue_desc = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 };
|
|
||||||
|
|
||||||
const SLInterfaceID interfacesList[] = { SL_IID_VOLUME };
|
|
||||||
const SLboolean interfaceRequirements[] = { SL_BOOLEAN_FALSE };
|
|
||||||
resultCode = (*OpenSLEngine::instance().getNativeEngine())->CreateOutputMix(
|
|
||||||
OpenSLEngine::instance().getNativeEngine(), &mMixer, 1, interfacesList,
|
|
||||||
interfaceRequirements);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Bring mixer online
|
|
||||||
resultCode = (*mMixer)->Realize(mMixer, SL_BOOLEAN_FALSE);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Prepare mixer configuration
|
|
||||||
SLuint32 speakers =
|
|
||||||
channels > 1 ? (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT) : SL_SPEAKER_FRONT_CENTER;
|
|
||||||
|
|
||||||
// Describe audio format
|
|
||||||
SLDataFormat_PCM pcm_format = {SL_DATAFORMAT_PCM, channels, (SLuint32) rateId,
|
|
||||||
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
||||||
speakers, SL_BYTEORDER_LITTLEENDIAN};
|
|
||||||
|
|
||||||
// Describe audio source - buffers + audio format
|
|
||||||
SLDataSource audio_source = { &queue_desc, &pcm_format };
|
|
||||||
|
|
||||||
// Describe audio sink
|
|
||||||
SLDataLocator_OutputMix mixer_desc = { SL_DATALOCATOR_OUTPUTMIX, mMixer };
|
|
||||||
SLDataSink audio_sink = { &mixer_desc, NULL };
|
|
||||||
|
|
||||||
// Create player instance
|
|
||||||
const SLInterfaceID playerInterfaces[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
|
||||||
SL_IID_VOLUME,
|
|
||||||
SL_IID_ANDROIDCONFIGURATION };
|
|
||||||
const SLboolean playerInterfacesReqs[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
|
|
||||||
|
|
||||||
resultCode = (*OpenSLEngine::instance().getNativeEngine())->CreateAudioPlayer(
|
|
||||||
OpenSLEngine::instance().getNativeEngine(), &mPlayer,
|
|
||||||
&audio_source, &audio_sink, 3, playerInterfaces, playerInterfacesReqs);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Get android config interface
|
|
||||||
resultCode = (*mPlayer)->GetInterface(mPlayer, SL_IID_ANDROIDCONFIGURATION, &mAndroidConfig);
|
|
||||||
|
|
||||||
if (resultCode == SL_RESULT_SUCCESS)
|
|
||||||
{
|
|
||||||
SLint32 streamType = voice ? SL_ANDROID_STREAM_VOICE : SL_ANDROID_STREAM_MEDIA;
|
|
||||||
resultCode = (*mAndroidConfig)->SetConfiguration(mAndroidConfig, SL_ANDROID_KEY_STREAM_TYPE,
|
|
||||||
&streamType, sizeof(SLint32));
|
|
||||||
if (resultCode != SL_RESULT_SUCCESS)
|
|
||||||
ICELogCritical(<< "Failed to set audio destination with error " << (unsigned)resultCode);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ICELogCritical(<< "Failed to obtain android cfg audio interface with error " << (unsigned)resultCode);
|
|
||||||
|
|
||||||
// Bring player online
|
|
||||||
resultCode = (*mPlayer)->Realize(mPlayer, SL_BOOLEAN_FALSE);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Obtain player control
|
|
||||||
resultCode = (*mPlayer)->GetInterface(mPlayer, SL_IID_PLAY, &mPlayerControl);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Get the buffer queue interface
|
|
||||||
resultCode = (*mPlayer)->GetInterface(mPlayer, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
|
||||||
&mBufferQueue);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Setup callback
|
|
||||||
resultCode = (*mBufferQueue)->RegisterCallback(mBufferQueue, DeviceCallback, this);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
// Enqueue buffers
|
|
||||||
mBufferSize = (int)Format(rate, channels).sizeFromTime(AUDIO_SPK_BUFFER_LENGTH);
|
|
||||||
mPlayBuffer.setCapacity(AUDIO_SPK_BUFFER_COUNT * mBufferSize);
|
|
||||||
|
|
||||||
mBufferIndex = 0;
|
|
||||||
for (int i = 0; i < AUDIO_SPK_BUFFER_COUNT; i++)
|
|
||||||
(*mBufferQueue)->Enqueue(mBufferQueue, mPlayBuffer.data() + i * mBufferSize,
|
|
||||||
(SLuint32)mBufferSize);
|
|
||||||
|
|
||||||
// Set the player's state to playing
|
|
||||||
resultCode = (*mPlayerControl)->SetPlayState(mPlayerControl, SL_PLAYSTATE_PLAYING);
|
|
||||||
CHECK_OPENSLES_ERROR;
|
|
||||||
|
|
||||||
ICELogInfo(<< "Android audio output is opened and playing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::internalClose()
|
|
||||||
{
|
|
||||||
if (mPlayer)
|
|
||||||
{
|
|
||||||
if (*mPlayer)
|
|
||||||
{
|
|
||||||
mInShutdown = true;
|
|
||||||
ICELogInfo(<< "Stop player");
|
|
||||||
if (mPlayerControl) {
|
|
||||||
if (*mPlayerControl) {
|
|
||||||
SLuint32 state = SL_PLAYSTATE_PLAYING;
|
|
||||||
(*mPlayerControl)->SetPlayState(mPlayerControl, SL_PLAYSTATE_STOPPED);
|
|
||||||
|
|
||||||
while (state != SL_PLAYSTATE_STOPPED) {
|
|
||||||
(*mPlayerControl)->GetPlayState(mPlayerControl, &state);
|
|
||||||
SyncHelper::delay(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear buffer queue
|
|
||||||
ICELogInfo(<< "Clear player buffer queue");
|
|
||||||
(*mBufferQueue)->Clear(mBufferQueue);
|
|
||||||
|
|
||||||
ICELogInfo(<< "Destroy player object");
|
|
||||||
// Destroy player object
|
|
||||||
(*mPlayer)->Destroy(mPlayer);
|
|
||||||
|
|
||||||
ICELogInfo(<< "Android audio output closed.");
|
|
||||||
|
|
||||||
mPlayer = nullptr;
|
|
||||||
mPlayerControl = nullptr;
|
|
||||||
mBufferQueue = nullptr;
|
|
||||||
mEffect = nullptr;
|
|
||||||
mAndroidConfig = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mMixer)
|
|
||||||
{
|
|
||||||
if (*mMixer)
|
|
||||||
(*mMixer)->Destroy(mMixer);
|
|
||||||
mMixer = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::handleCallback(SLAndroidSimpleBufferQueueItf bq)
|
|
||||||
{
|
|
||||||
if (mInShutdown)
|
|
||||||
return;
|
|
||||||
/*{
|
|
||||||
char silence[mBufferSize]; memset(silence, 0, mBufferSize);
|
|
||||||
(*mBufferQueue)->Enqueue(mBufferQueue, silence, mBufferSize);
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Ask producer about data
|
|
||||||
char* buffer = mPlayBuffer.mutableData() + mBufferIndex * mBufferSize;
|
|
||||||
if (mConnection)
|
|
||||||
{
|
|
||||||
Format f = getFormat();
|
|
||||||
if (f.mRate != 0)
|
|
||||||
mConnection->onSpkData(f, buffer, mBufferSize);
|
|
||||||
}
|
|
||||||
(*mBufferQueue)->Enqueue(mBufferQueue, buffer, (SLuint32)mBufferSize);
|
|
||||||
|
|
||||||
mBufferIndex++;
|
|
||||||
mBufferIndex %= AUDIO_SPK_BUFFER_COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::DeviceCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
|
|
||||||
{
|
|
||||||
if (!context)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
reinterpret_cast<AndroidOutputDevice*>(context)->handleCallback(bq);
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // TARGET_ANDROID
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 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 __AUDIO_ANDROID_H
|
|
||||||
#define __AUDIO_ANDROID_H
|
|
||||||
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
#include "Audio_DataWindow.h"
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <SLES/OpenSLES.h>
|
|
||||||
#include <SLES/OpenSLES_Android.h>
|
|
||||||
#include <SLES/OpenSLES_AndroidConfiguration.h>
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class AndroidEnumerator: public Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AndroidEnumerator();
|
|
||||||
~AndroidEnumerator();
|
|
||||||
|
|
||||||
void open(int direction);
|
|
||||||
void close();
|
|
||||||
|
|
||||||
int count();
|
|
||||||
std::string nameAt(int index);
|
|
||||||
int idAt(int index);
|
|
||||||
int indexOfDefaultDevice();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
|
|
||||||
class AndroidInputDevice: public InputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AndroidInputDevice(int devId);
|
|
||||||
~AndroidInputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
int readBuffer(void* buffer);
|
|
||||||
bool active() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool mActive = false;
|
|
||||||
SLObjectItf mRecorderObject = nullptr;
|
|
||||||
SLRecordItf mRecorderInterface = nullptr;
|
|
||||||
SLAndroidSimpleBufferQueueItf mRecorderBufferInterface = nullptr;
|
|
||||||
SLAndroidConfigurationItf mAndroidCfg = nullptr;
|
|
||||||
|
|
||||||
PResampler mResampler;
|
|
||||||
DataWindow mDeviceRateCache, mSdkRateCache;
|
|
||||||
int mDeviceRate; // Actual rate of opened recorder
|
|
||||||
int mBufferSize; // Size of buffer used for recording (at native sample rate)
|
|
||||||
DataWindow mRecorderBuffer;
|
|
||||||
std::condition_variable mDataCondVar;
|
|
||||||
int mRecorderBufferIndex;
|
|
||||||
std::mutex mMutex;
|
|
||||||
|
|
||||||
void internalOpen(int rateCode, int rate);
|
|
||||||
void internalClose();
|
|
||||||
void handleCallback(SLAndroidSimpleBufferQueueItf bq);
|
|
||||||
static void DeviceCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
|
|
||||||
};
|
|
||||||
|
|
||||||
class AndroidOutputDevice: public OutputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AndroidOutputDevice(int devId);
|
|
||||||
~AndroidOutputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::mutex mMutex;
|
|
||||||
int mDeviceRate = 0;
|
|
||||||
SLObjectItf mMixer = nullptr;
|
|
||||||
SLObjectItf mPlayer = nullptr;
|
|
||||||
SLPlayItf mPlayerControl = nullptr;
|
|
||||||
SLAndroidSimpleBufferQueueItf mBufferQueue = nullptr;
|
|
||||||
SLAndroidConfigurationItf mAndroidConfig = nullptr;
|
|
||||||
SLEffectSendItf mEffect = nullptr;
|
|
||||||
|
|
||||||
DataWindow mPlayBuffer;
|
|
||||||
int mBufferIndex = 0, mBufferSize = 0;
|
|
||||||
bool mInShutdown = false;
|
|
||||||
|
|
||||||
void internalOpen(int rateId, int rate, bool voice);
|
|
||||||
void internalClose();
|
|
||||||
|
|
||||||
void handleCallback(SLAndroidSimpleBufferQueueItf bq);
|
|
||||||
static void DeviceCallback(SLAndroidSimpleBufferQueueItf bq, void* context);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class OpenSLEngine: public OsEngine
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OpenSLEngine();
|
|
||||||
~OpenSLEngine();
|
|
||||||
|
|
||||||
// open() / close() methods are based on usage counting.
|
|
||||||
// It means every close() call must be matched by corresponding open() call.
|
|
||||||
// True audio engine close will happen only on last close() call.
|
|
||||||
void open() override;
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
SLEngineItf getNativeEngine() const;
|
|
||||||
|
|
||||||
static OpenSLEngine& instance();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::mutex mMutex;
|
|
||||||
int mUsageCounter = 0;
|
|
||||||
SLObjectItf mEngineObject = nullptr;
|
|
||||||
SLEngineItf mEngineInterface = nullptr;
|
|
||||||
|
|
||||||
void internalOpen();
|
|
||||||
void internalClose();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // TARGET_ANDROID
|
|
||||||
|
|
||||||
#endif // __AUDIO_ANDROID_H
|
|
||||||
|
|
@ -1,245 +0,0 @@
|
||||||
#include "Audio_AndroidOboe.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
#include <mutex>
|
|
||||||
#include <iostream>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include "../helper/HL_String.h"
|
|
||||||
#include "../helper/HL_Time.h"
|
|
||||||
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Audio"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------- AndroidEnumerator -----------------------------
|
|
||||||
|
|
||||||
AndroidEnumerator::AndroidEnumerator()
|
|
||||||
{}
|
|
||||||
|
|
||||||
AndroidEnumerator::~AndroidEnumerator()
|
|
||||||
{}
|
|
||||||
|
|
||||||
int AndroidEnumerator::indexOfDefaultDevice()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidEnumerator::count()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidEnumerator::idAt(int index)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AndroidEnumerator::nameAt(int index)
|
|
||||||
{
|
|
||||||
return "Audio";
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidEnumerator::open(int direction)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void AndroidEnumerator::close()
|
|
||||||
{}
|
|
||||||
|
|
||||||
// --------------- Input implementation ----------------
|
|
||||||
AndroidInputDevice::AndroidInputDevice(int devId)
|
|
||||||
{}
|
|
||||||
|
|
||||||
AndroidInputDevice::~AndroidInputDevice()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidInputDevice::open()
|
|
||||||
{
|
|
||||||
if (active())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
oboe::AudioStreamBuilder builder;
|
|
||||||
builder.setDirection(oboe::Direction::Input);
|
|
||||||
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
|
|
||||||
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
|
||||||
builder.setFormat(oboe::AudioFormat::I16);
|
|
||||||
builder.setChannelCount(oboe::ChannelCount::Mono);
|
|
||||||
builder.setCallback(this);
|
|
||||||
oboe::Result rescode = builder.openStream(&mRecordingStream);
|
|
||||||
if (rescode != oboe::Result::OK)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mDeviceRate = mRecordingStream->getSampleRate();
|
|
||||||
ICELogInfo(<< "Input Opened with rate " << mDeviceRate);
|
|
||||||
mActive = true;
|
|
||||||
|
|
||||||
rescode = mRecordingStream->requestStart();
|
|
||||||
if (rescode != oboe::Result::OK)
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
mActive = false;
|
|
||||||
}
|
|
||||||
return mActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::close()
|
|
||||||
{
|
|
||||||
// There is no check for active() value because close() can be called to cleanup after bad open() call.
|
|
||||||
if (mRecordingStream != nullptr)
|
|
||||||
{
|
|
||||||
mRecordingStream->close();
|
|
||||||
delete mRecordingStream; mRecordingStream = nullptr;
|
|
||||||
}
|
|
||||||
mActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
oboe::DataCallbackResult
|
|
||||||
AndroidInputDevice::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
|
|
||||||
// Send data to AudioPair
|
|
||||||
if (mConnection)
|
|
||||||
mConnection->onMicData(getFormat(), audioData, numFrames);
|
|
||||||
|
|
||||||
return oboe::DataCallbackResult::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Format AndroidInputDevice::getFormat()
|
|
||||||
{
|
|
||||||
return Format(mDeviceRate, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidInputDevice::active() const
|
|
||||||
{
|
|
||||||
return mActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidInputDevice::fakeMode()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidInputDevice::setFakeMode(bool fakemode)
|
|
||||||
{}
|
|
||||||
|
|
||||||
int AndroidInputDevice::readBuffer(void* buffer)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("AndroidInputDevice::readBuffer() is not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------ AndroidOutputDevice -----------------
|
|
||||||
AndroidOutputDevice::AndroidOutputDevice(int devId)
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << strx::toHex(this));
|
|
||||||
}
|
|
||||||
AndroidOutputDevice::~AndroidOutputDevice()
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "Deleting AndroidOutputDevice.");
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidOutputDevice::open()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
|
|
||||||
if (mActive)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
mRequestedFrames = 0;
|
|
||||||
mStartTime = 0.0;
|
|
||||||
mEndTime = 0.0;
|
|
||||||
|
|
||||||
oboe::AudioStreamBuilder builder;
|
|
||||||
builder.setDirection(oboe::Direction::Output);
|
|
||||||
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
|
|
||||||
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
|
||||||
builder.setFormat(oboe::AudioFormat::I16);
|
|
||||||
builder.setChannelCount(oboe::ChannelCount::Mono);
|
|
||||||
// builder.setDataCallback(this);
|
|
||||||
builder.setCallback(this);
|
|
||||||
//builder.setErrorCallback(this)
|
|
||||||
|
|
||||||
oboe::Result rescode = builder.openStream(&mPlayingStream);
|
|
||||||
if (rescode != oboe::Result::OK)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mDeviceRate = mPlayingStream->getSampleRate();
|
|
||||||
ICELogInfo(<< "Input Opened with rate " << mDeviceRate);
|
|
||||||
mActive = true;
|
|
||||||
|
|
||||||
rescode = mPlayingStream->requestStart();
|
|
||||||
if (rescode != oboe::Result::OK)
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
mActive = false;
|
|
||||||
}
|
|
||||||
return mActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::close()
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> l(mMutex);
|
|
||||||
if (!mActive)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (mPlayingStream != nullptr)
|
|
||||||
{
|
|
||||||
mPlayingStream->close();
|
|
||||||
delete mPlayingStream; mPlayingStream = nullptr;
|
|
||||||
}
|
|
||||||
mEndTime = now_ms();
|
|
||||||
mActive = false;
|
|
||||||
|
|
||||||
ICELogInfo(<< "For time " << mEndTime - mStartTime << " ms was requested "
|
|
||||||
<< float(mRequestedFrames) / getFormat().mRate * 1000 << " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
Format AndroidOutputDevice::getFormat()
|
|
||||||
{
|
|
||||||
return {mDeviceRate, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidOutputDevice::fakeMode()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidOutputDevice::setFakeMode(bool /*fakemode*/)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
oboe::DataCallbackResult AndroidOutputDevice::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames)
|
|
||||||
{
|
|
||||||
if (mInShutdown)
|
|
||||||
return oboe::DataCallbackResult::Stop;
|
|
||||||
|
|
||||||
if (mStartTime == 0.0)
|
|
||||||
mStartTime = now_ms();
|
|
||||||
|
|
||||||
// Ask producer about data
|
|
||||||
memset(audioData, 0, numFrames * 2);
|
|
||||||
if (mConnection)
|
|
||||||
{
|
|
||||||
Format f = getFormat();
|
|
||||||
if (f.mRate != 0)
|
|
||||||
mConnection->onSpkData(f, audioData, numFrames * 2);
|
|
||||||
}
|
|
||||||
mRequestedFrames += numFrames;
|
|
||||||
|
|
||||||
return oboe::DataCallbackResult::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - special case https://github.com/google/oboe/blob/master/docs/notes/disconnect.md
|
|
||||||
void AndroidOutputDevice::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result) {
|
|
||||||
if (result == oboe::Result::ErrorDisconnected) {
|
|
||||||
// LOGI("Restarting AudioStream after disconnect");
|
|
||||||
// soundEngine.restart(); // please check oboe samples for soundEngine.restart(); call
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // TARGET_ANDROID
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 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 __AUDIO_ANDROID_OBOE_H
|
|
||||||
#define __AUDIO_ANDROID_OBOE_H
|
|
||||||
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
#include "Audio_DataWindow.h"
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
#include "../helper/HL_Statistics.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "oboe/Oboe.h"
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class AndroidEnumerator: public Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AndroidEnumerator();
|
|
||||||
~AndroidEnumerator();
|
|
||||||
|
|
||||||
void open(int direction);
|
|
||||||
void close();
|
|
||||||
|
|
||||||
int count();
|
|
||||||
std::string nameAt(int index);
|
|
||||||
int idAt(int index);
|
|
||||||
int indexOfDefaultDevice();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
|
|
||||||
class AndroidInputDevice: public InputDevice, public oboe::AudioStreamCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AndroidInputDevice(int devId);
|
|
||||||
~AndroidInputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
int readBuffer(void* buffer);
|
|
||||||
bool active() const;
|
|
||||||
|
|
||||||
oboe::DataCallbackResult
|
|
||||||
onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames);
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool mActive = false;
|
|
||||||
oboe::AudioStream* mRecordingStream = nullptr;
|
|
||||||
PResampler mResampler;
|
|
||||||
DataWindow mDeviceRateCache, mSdkRateCache;
|
|
||||||
int mDeviceRate; // Actual rate of opened recorder
|
|
||||||
int mBufferSize; // Size of buffer used for recording (at native sample rate)
|
|
||||||
DataWindow mRecorderBuffer;
|
|
||||||
std::condition_variable mDataCondVar;
|
|
||||||
int mRecorderBufferIndex;
|
|
||||||
std::mutex mMutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AndroidOutputDevice: public OutputDevice, public oboe::AudioStreamCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AndroidOutputDevice(int devId);
|
|
||||||
~AndroidOutputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
|
|
||||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames);
|
|
||||||
void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::mutex mMutex;
|
|
||||||
int mDeviceRate = 0;
|
|
||||||
oboe::AudioStream* mPlayingStream = nullptr;
|
|
||||||
DataWindow mPlayBuffer;
|
|
||||||
int mBufferIndex = 0, mBufferSize = 0;
|
|
||||||
bool mInShutdown = false;
|
|
||||||
bool mActive = false;
|
|
||||||
|
|
||||||
// Statistics
|
|
||||||
float mRequestedFrames = 0.0, mStartTime = 0.0, mEndTime = 0.0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // TARGET_ANDROID
|
|
||||||
|
|
||||||
#endif // __AUDIO_ANDROID_H
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,197 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __AUDIO_COREAUDIO_H
|
|
||||||
#define __AUDIO_COREAUDIO_H
|
|
||||||
|
|
||||||
#ifdef TARGET_OSX
|
|
||||||
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
#include "Audio_DataWindow.h"
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
#include <AudioToolbox/AudioQueue.h>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// Define CoreAudio buffer time length in milliseconds
|
|
||||||
#define COREAUDIO_BUFFER_TIME 20
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
|
|
||||||
class AudioException: public Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AudioException(int code, OSStatus subcode)
|
|
||||||
:Exception(code, int(subcode))
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
//#ifndef AudioDeviceID
|
|
||||||
//# define AudioDeviceID unsigned
|
|
||||||
//#endif
|
|
||||||
class MacEnumerator: public Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MacEnumerator();
|
|
||||||
~MacEnumerator();
|
|
||||||
|
|
||||||
void open(int direction);
|
|
||||||
void close();
|
|
||||||
|
|
||||||
int count();
|
|
||||||
std::tstring nameAt(int index);
|
|
||||||
int idAt(int index);
|
|
||||||
int indexOfDefaultDevice();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
struct DeviceInfo
|
|
||||||
{
|
|
||||||
AudioDeviceID mId;
|
|
||||||
std::string mName;
|
|
||||||
bool mCanChangeOutputVolume;
|
|
||||||
bool mCanChangeInputVolume;
|
|
||||||
int mInputCount, mOutputCount;
|
|
||||||
int mDefaultRate;
|
|
||||||
DeviceInfo(): mId(0), mCanChangeOutputVolume(false), mCanChangeInputVolume(false), mInputCount(0), mOutputCount(0), mDefaultRate(16000) {}
|
|
||||||
};
|
|
||||||
std::vector<DeviceInfo> mDeviceList;
|
|
||||||
unsigned mDefaultInput, mDefaultOutput;
|
|
||||||
int mDirection;
|
|
||||||
void getInfo(DeviceInfo& di);
|
|
||||||
};
|
|
||||||
|
|
||||||
class CoreAudioUnit
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CoreAudioUnit();
|
|
||||||
~CoreAudioUnit();
|
|
||||||
|
|
||||||
void open(bool voice);
|
|
||||||
void close();
|
|
||||||
AudioStreamBasicDescription getFormat(int scope, int bus);
|
|
||||||
void setFormat(AudioStreamBasicDescription& format, int scope, int bus);
|
|
||||||
bool getEnabled(int scope, int bus);
|
|
||||||
void setEnabled(bool enabled, int scope, int bus);
|
|
||||||
void makeCurrent(AudioDeviceID deviceId, int scope, int bus);
|
|
||||||
void setCallback(AURenderCallbackStruct cb, int callbackType, int scope, int bus);
|
|
||||||
void setBufferFrameSizeInMilliseconds(int ms);
|
|
||||||
int getBufferFrameSize();
|
|
||||||
void initialize();
|
|
||||||
AudioUnit getHandle();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AudioUnit mUnit;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MacDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MacDevice(int devId);
|
|
||||||
~MacDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
void setRender(bool render);
|
|
||||||
void setCapture(bool capture);
|
|
||||||
int getId();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
DataConnection* connection();
|
|
||||||
void setConnection(DataConnection* c);
|
|
||||||
void provideAudioToSpeaker(int channels, void* buffer, int length);
|
|
||||||
void obtainAudioFromMic(int channels, const void* buffer, int length);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
AudioDeviceID mDeviceId;
|
|
||||||
bool mCapture, mRender;
|
|
||||||
bool mActive;
|
|
||||||
int mUsageCount;
|
|
||||||
Mutex mGuard;
|
|
||||||
|
|
||||||
CoreAudioUnit mAudioUnit;
|
|
||||||
AudioComponent mComponent;
|
|
||||||
AudioStreamBasicDescription mCaptureInputFormat, mCaptureOutputFormat, mRenderInputFormat, mRenderOutputFormat, mStreamFormat;
|
|
||||||
AudioBufferList* mInputBufferList;
|
|
||||||
DataConnection* mConnection;
|
|
||||||
SpeexResampler mCaptureResampler, mRenderResampler;
|
|
||||||
ByteBuffer mTail;
|
|
||||||
DataWindow mInputBuffer, mOutputBuffer;
|
|
||||||
bool createUnit(bool voice);
|
|
||||||
void destroyUnit();
|
|
||||||
void startStream();
|
|
||||||
void stopStream();
|
|
||||||
void setupStreamFormat();
|
|
||||||
bool createResampleUnit(AudioStreamBasicDescription format);
|
|
||||||
|
|
||||||
static OSStatus outputCallback( void *inRefCon,
|
|
||||||
AudioUnitRenderActionFlags *ioActionFlags,
|
|
||||||
const AudioTimeStamp *inTimeStamp,
|
|
||||||
UInt32 inBusNumber,
|
|
||||||
UInt32 inNumberFrames,
|
|
||||||
AudioBufferList *ioData );
|
|
||||||
|
|
||||||
static OSStatus inputCallback(void *inRefCon,
|
|
||||||
AudioUnitRenderActionFlags *ioActionFlags,
|
|
||||||
const AudioTimeStamp *inTimeStamp,
|
|
||||||
UInt32 inBusNumber,
|
|
||||||
UInt32 inNumberFrames,
|
|
||||||
AudioBufferList *ioData);
|
|
||||||
#ifdef TARGET_IOS
|
|
||||||
static void propListener(void *inClientData,
|
|
||||||
AudioSessionPropertyID inID,
|
|
||||||
UInt32 inDataSize,
|
|
||||||
const void * inData);
|
|
||||||
static void interruptionListener(void *inClientData, UInt32 inInterruption);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<MacDevice> PMacDevice;
|
|
||||||
|
|
||||||
class MacInputDevice: public InputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MacInputDevice(int devId);
|
|
||||||
~MacInputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
int readBuffer(void* buffer);
|
|
||||||
protected:
|
|
||||||
PMacDevice mDevice;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class MacOutputDevice: public OutputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MacOutputDevice(int devId);
|
|
||||||
~MacOutputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PMacDevice mDevice;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // TARGET_OSX
|
|
||||||
|
|
||||||
#endif // __AUDIO_COREAUDIO_H
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
/* Copyright(C) 2007-2018 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/. */
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include "Audio_DataWindow.h"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
DataWindow::DataWindow()
|
|
||||||
{
|
|
||||||
mFilled = 0;
|
|
||||||
mData = NULL;
|
|
||||||
mCapacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataWindow::~DataWindow()
|
|
||||||
{
|
|
||||||
if (mData)
|
|
||||||
free(mData);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::setCapacity(int capacity)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
int tail = capacity - mCapacity;
|
|
||||||
mData = (char*)realloc(mData, capacity);
|
|
||||||
if (tail > 0)
|
|
||||||
memset(mData + mCapacity, 0, tail);
|
|
||||||
mCapacity = capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::addZero(int length)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
|
|
||||||
if (length > mCapacity)
|
|
||||||
length = mCapacity;
|
|
||||||
|
|
||||||
int avail = mCapacity - mFilled;
|
|
||||||
|
|
||||||
if (avail < length)
|
|
||||||
{
|
|
||||||
memmove(mData, mData + length - avail, mFilled - (length - avail));
|
|
||||||
mFilled -= length - avail;
|
|
||||||
}
|
|
||||||
memset(mData + mFilled, 0, length);
|
|
||||||
mFilled += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void DataWindow::add(const void* data, int length)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
|
|
||||||
if (length > mCapacity)
|
|
||||||
{
|
|
||||||
// Use latest bytes from data buffer in this case.
|
|
||||||
data = (char*)data + length - mCapacity;
|
|
||||||
length = mCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check how much free space we have
|
|
||||||
int avail = mCapacity - mFilled;
|
|
||||||
|
|
||||||
if (avail < length)
|
|
||||||
{
|
|
||||||
// Find the portion of data to move & save
|
|
||||||
int delta = length - avail;
|
|
||||||
|
|
||||||
// Move the data
|
|
||||||
if (mFilled - delta > 0)
|
|
||||||
memmove(mData, mData + delta, mFilled - delta);
|
|
||||||
mFilled -= delta;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(mData + mFilled, data, length);
|
|
||||||
mFilled += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::add(short sample)
|
|
||||||
{
|
|
||||||
add(&sample, sizeof sample);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::erase(int length)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
if (length > mFilled)
|
|
||||||
length = mFilled;
|
|
||||||
if (length != mFilled)
|
|
||||||
memmove(mData, mData + length, mFilled - length);
|
|
||||||
mFilled -= length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* DataWindow::data() const
|
|
||||||
{
|
|
||||||
return mData;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* DataWindow::mutableData()
|
|
||||||
{
|
|
||||||
return mData;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::clear()
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
mFilled = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
short DataWindow::shortAt(int index) const
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
assert(index < mFilled / 2);
|
|
||||||
return ((short*)mData)[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::setShortAt(short value, int index)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
assert(index < mFilled / 2);
|
|
||||||
((short*)mData)[index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DataWindow::read(void* buffer, int length)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
if (length > mFilled)
|
|
||||||
length = mFilled;
|
|
||||||
if (length)
|
|
||||||
{
|
|
||||||
if (buffer)
|
|
||||||
memcpy(buffer, mData, length);
|
|
||||||
if (length < mFilled)
|
|
||||||
memmove(mData, mData+length, mFilled - length);
|
|
||||||
mFilled -= length;
|
|
||||||
}
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DataWindow::filled() const
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
return mFilled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::setFilled(int filled)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
mFilled = filled;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DataWindow::capacity() const
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
return mCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::zero(int length)
|
|
||||||
{
|
|
||||||
Lock l(mMutex);
|
|
||||||
assert(length <= mCapacity);
|
|
||||||
mFilled = length;
|
|
||||||
memset(mData, 0, mFilled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src)
|
|
||||||
{
|
|
||||||
Lock lockDst(dst.mMutex), lockSrc(src.mMutex);
|
|
||||||
|
|
||||||
dst.setCapacity(src.filled()*2);
|
|
||||||
short* input = (short*)src.mutableData();
|
|
||||||
short* output = (short*)dst.mutableData();
|
|
||||||
|
|
||||||
for (int i=0; i<src.filled()/2; i++)
|
|
||||||
output[i*2] = output[i*2+1] = input[i];
|
|
||||||
dst.mFilled = src.filled() * 2;
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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 __AUDIO_BUFFER_H
|
|
||||||
#define __AUDIO_BUFFER_H
|
|
||||||
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class DataWindow
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DataWindow();
|
|
||||||
~DataWindow();
|
|
||||||
|
|
||||||
void setCapacity(int capacity);
|
|
||||||
int capacity() const;
|
|
||||||
|
|
||||||
void addZero(int length);
|
|
||||||
void add(const void* data, int length);
|
|
||||||
void add(short sample);
|
|
||||||
int read(void* buffer, int length);
|
|
||||||
void erase(int length = -1);
|
|
||||||
const char* data() const;
|
|
||||||
char* mutableData();
|
|
||||||
int filled() const;
|
|
||||||
void setFilled(int filled);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
short shortAt(int index) const;
|
|
||||||
void setShortAt(short value, int index);
|
|
||||||
void zero(int length);
|
|
||||||
|
|
||||||
static void makeStereoFromMono(DataWindow& dst, DataWindow& src);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
mutable Mutex mMutex;
|
|
||||||
char* mData;
|
|
||||||
int mFilled;
|
|
||||||
int mCapacity;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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/. */
|
|
||||||
|
|
||||||
#define NOMINMAX
|
|
||||||
#include "Audio_DevicePair.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Audio"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
// --- DevicePair ---
|
|
||||||
DevicePair::DevicePair()
|
|
||||||
:mConfig(nullptr), mDelegate(nullptr), mAec(false), mAgc(false), mAecFilter(AUDIO_MIC_BUFFER_LENGTH*10, AUDIO_MIC_BUFFER_LENGTH, AUDIO_SAMPLERATE), mAgcFilter(AUDIO_CHANNELS),
|
|
||||||
mMonitoring(nullptr)
|
|
||||||
{
|
|
||||||
mInputBuffer.setCapacity(AUDIO_MIC_BUFFER_SIZE * (AUDIO_MIC_BUFFER_COUNT + 1));
|
|
||||||
mOutputBuffer.setCapacity(AUDIO_SPK_BUFFER_SIZE * (AUDIO_SPK_BUFFER_COUNT + 1));
|
|
||||||
mInputResampingData.setCapacity(AUDIO_MIC_BUFFER_SIZE * (AUDIO_MIC_BUFFER_COUNT + 1));
|
|
||||||
mOutput10msBuffer.setCapacity((int)Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH));
|
|
||||||
mOutputNativeData.setCapacity((int)Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH * AUDIO_SPK_BUFFER_COUNT * 24));
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair::~DevicePair()
|
|
||||||
{
|
|
||||||
if (mInput)
|
|
||||||
{
|
|
||||||
if (mInput->connection() == this)
|
|
||||||
mInput->setConnection(nullptr);
|
|
||||||
mInput.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mOutput)
|
|
||||||
{
|
|
||||||
if (mOutput->connection() == this)
|
|
||||||
mOutput->setConnection(nullptr);
|
|
||||||
mOutput.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setAec(bool aec)
|
|
||||||
{
|
|
||||||
mAec = aec;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevicePair::aec()
|
|
||||||
{
|
|
||||||
return mAec;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setAgc(bool agc)
|
|
||||||
{
|
|
||||||
mAgc = agc;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevicePair::agc()
|
|
||||||
{
|
|
||||||
return mAgc;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
VariantMap* DevicePair::config()
|
|
||||||
{
|
|
||||||
return mConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setConfig(VariantMap* config)
|
|
||||||
{
|
|
||||||
mConfig = config;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
PInputDevice DevicePair::input()
|
|
||||||
{
|
|
||||||
return mInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setInput(PInputDevice input)
|
|
||||||
{
|
|
||||||
if (mInput == input)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
mInput = input;
|
|
||||||
mInput->setConnection(this);
|
|
||||||
if (mDelegate)
|
|
||||||
mDelegate->deviceChanged(this);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
POutputDevice DevicePair::output()
|
|
||||||
{
|
|
||||||
return mOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setOutput(POutputDevice output)
|
|
||||||
{
|
|
||||||
if (output == mOutput)
|
|
||||||
return *this;
|
|
||||||
|
|
||||||
mOutput = output;
|
|
||||||
mOutput->setConnection(this);
|
|
||||||
if (mDelegate)
|
|
||||||
mDelegate->deviceChanged(this);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevicePair::start()
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
if (mInput)
|
|
||||||
result = mInput->open();
|
|
||||||
if (mOutput && result)
|
|
||||||
result &= mOutput->open();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevicePair::stop()
|
|
||||||
{
|
|
||||||
if (mInput)
|
|
||||||
mInput->close();
|
|
||||||
if (mOutput)
|
|
||||||
mOutput->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setDelegate(Delegate* dc)
|
|
||||||
{
|
|
||||||
mDelegate = dc;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair::Delegate* DevicePair::delegate()
|
|
||||||
{
|
|
||||||
return mDelegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevicePair& DevicePair::setMonitoring(DataConnection* monitoring)
|
|
||||||
{
|
|
||||||
mMonitoring = monitoring;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataConnection* DevicePair::monitoring()
|
|
||||||
{
|
|
||||||
return mMonitoring;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player& DevicePair::player()
|
|
||||||
{
|
|
||||||
return mPlayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevicePair::onMicData(const Format& f, const void* buffer, int length)
|
|
||||||
{
|
|
||||||
#ifdef DUMP_NATIVEINPUT
|
|
||||||
if (!mNativeInputDump)
|
|
||||||
{
|
|
||||||
mNativeInputDump = std::make_shared<WavFileWriter>();
|
|
||||||
mNativeInputDump->open("nativeinput.wav", f.mRate, f.mChannels);
|
|
||||||
}
|
|
||||||
if (mNativeInputDump)
|
|
||||||
mNativeInputDump->write(buffer, length);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// send the data to internal queue - it can hold data which were not processed by resampler in last call
|
|
||||||
mInputResampingData.add(buffer, length);
|
|
||||||
|
|
||||||
// split processing by blocks
|
|
||||||
int blocks = mInputResampingData.filled() / (int)f.sizeFromTime(AUDIO_MIC_BUFFER_LENGTH);
|
|
||||||
|
|
||||||
for (int blockIndex = 0; blockIndex < blocks; blockIndex++)
|
|
||||||
{
|
|
||||||
|
|
||||||
size_t wasProcessed = 0;
|
|
||||||
|
|
||||||
size_t wasProduced = mMicResampler.resample(f.mRate, // Source rate
|
|
||||||
mInputResampingData.data(), // Source data
|
|
||||||
(int)f.sizeFromTime(AUDIO_MIC_BUFFER_LENGTH), // Source size
|
|
||||||
wasProcessed,
|
|
||||||
AUDIO_SAMPLERATE, // Dest rate
|
|
||||||
mInputBuffer.mutableData() + mInputBuffer.filled(),
|
|
||||||
mInputBuffer.capacity() - mInputBuffer.filled());
|
|
||||||
|
|
||||||
mInputBuffer.setFilled(mInputBuffer.filled() + wasProduced);
|
|
||||||
mInputResampingData.erase((int)f.sizeFromTime(AUDIO_MIC_BUFFER_LENGTH));
|
|
||||||
processMicData(Format(), mInputBuffer.mutableData(), (int)Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH));
|
|
||||||
|
|
||||||
mInputBuffer.erase((int)Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevicePair::onSpkData(const Format& f, void* buffer, int length)
|
|
||||||
{
|
|
||||||
//ICELogMedia(<< "Audio::DevicePair::onSpkData() begin");
|
|
||||||
#ifdef DUMP_NATIVEOUTPUT
|
|
||||||
if (!mNativeOutputDump)
|
|
||||||
{
|
|
||||||
mNativeOutputDump = std::make_shared<WavFileWriter>();
|
|
||||||
mNativeOutputDump->open("nativeoutput.wav", f.mRate, f.mChannels);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef CONSOLE_LOGGING
|
|
||||||
printf("Speaker requests %d\n", length);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Format nativeFormat = mOutput->getFormat();
|
|
||||||
// See how much bytes are needed yet - mOutputNativeData can contain some data already
|
|
||||||
int required = length - mOutputNativeData.filled();
|
|
||||||
if (required > 0)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Find how much blocks must be received from RTP/decoder side
|
|
||||||
int nativeBufferSize = (int)nativeFormat.sizeFromTime(AUDIO_SPK_BUFFER_LENGTH);
|
|
||||||
int blocks = required / nativeBufferSize;
|
|
||||||
if (required % nativeBufferSize)
|
|
||||||
blocks++;
|
|
||||||
|
|
||||||
// Now request data from terminal or whetever delegate is
|
|
||||||
for (int blockIndex = 0; blockIndex < blocks; blockIndex++)
|
|
||||||
{
|
|
||||||
memset(mOutput10msBuffer.mutableData(), 0, (size_t)mOutput10msBuffer.capacity());
|
|
||||||
|
|
||||||
// Ask audio data on main AUDIO_SAMPLERATE frequency
|
|
||||||
if (mDelegate)
|
|
||||||
mDelegate->onSpkData(Format(), mOutput10msBuffer.mutableData(), mOutput10msBuffer.capacity());
|
|
||||||
|
|
||||||
// Replace received data with custom file or data playing
|
|
||||||
mPlayer.onSpkData(Format(), mOutput10msBuffer.mutableData(), mOutput10msBuffer.capacity());
|
|
||||||
|
|
||||||
// Save it to process with AEC
|
|
||||||
if (mAec)
|
|
||||||
mAecSpkBuffer.add(mOutput10msBuffer.data(), mOutput10msBuffer.capacity());
|
|
||||||
|
|
||||||
// Resample these 10 milliseconds it to native format
|
|
||||||
size_t wasProcessed = 0;
|
|
||||||
size_t wasProduced = mSpkResampler.resample(Format().mRate,
|
|
||||||
mOutput10msBuffer.data(),
|
|
||||||
mOutput10msBuffer.capacity(),
|
|
||||||
wasProcessed, f.mRate,
|
|
||||||
mOutputNativeData.mutableData() + mOutputNativeData.filled(),
|
|
||||||
mOutputNativeData.capacity() - mOutputNativeData.filled());
|
|
||||||
mOutputNativeData.setFilled(mOutputNativeData.filled() + wasProduced);
|
|
||||||
#ifdef CONSOLE_LOGGING
|
|
||||||
printf("Resampled %d to %d\n", wasProcessed, wasProduced);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert(mOutputNativeData.filled() >= length);
|
|
||||||
#ifdef DUMP_NATIVEOUTPUT
|
|
||||||
if (mNativeOutputDump)
|
|
||||||
mNativeOutputDump->write(mOutputNativeData.data(), length);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
mOutputNativeData.read(buffer, length);
|
|
||||||
|
|
||||||
// Send data to monitoring if needed
|
|
||||||
if (mMonitoring)
|
|
||||||
mMonitoring->onSpkData(f, buffer, length);
|
|
||||||
|
|
||||||
#define AEC_FRAME_SIZE (AUDIO_CHANNELS * (AUDIO_SAMPLERATE / 1000) * AEC_FRAME_TIME * sizeof(short))
|
|
||||||
|
|
||||||
// AEC filter wants frames.
|
|
||||||
if (mAec)
|
|
||||||
{
|
|
||||||
int nrOfFrames = mAecSpkBuffer.filled() / AEC_FRAME_SIZE;
|
|
||||||
for (int frameIndex=0; frameIndex < nrOfFrames; frameIndex++)
|
|
||||||
mAecFilter.toSpeaker(mAecSpkBuffer.mutableData() + AEC_FRAME_SIZE * frameIndex);
|
|
||||||
mAecSpkBuffer.erase(nrOfFrames * AEC_FRAME_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevicePair::processMicData(const Format& f, void* buffer, int length)
|
|
||||||
{
|
|
||||||
if (mAgc)
|
|
||||||
mAgcFilter.process(buffer, length);
|
|
||||||
|
|
||||||
if (mAec)
|
|
||||||
mAecFilter.fromMic(buffer);
|
|
||||||
|
|
||||||
if (mDelegate)
|
|
||||||
mDelegate->onMicData(f, buffer, length);
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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 __AUDIO_DEVICEPAIR_H
|
|
||||||
#define __AUDIO_DEVICEPAIR_H
|
|
||||||
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include "Audio_Player.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
#include "Audio_DataWindow.h"
|
|
||||||
|
|
||||||
//#define DUMP_NATIVEOUTPUT
|
|
||||||
//#define DUMP_NATIVEINPUT
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
|
|
||||||
class DevicePair: protected DataConnection
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
class Delegate: public DataConnection
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void deviceChanged(DevicePair* dpair) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
DevicePair();
|
|
||||||
virtual ~DevicePair();
|
|
||||||
|
|
||||||
DevicePair& setAec(bool aec);
|
|
||||||
bool aec();
|
|
||||||
DevicePair& setAgc(bool agc);
|
|
||||||
bool agc();
|
|
||||||
|
|
||||||
VariantMap* config();
|
|
||||||
DevicePair& setConfig(VariantMap* config);
|
|
||||||
|
|
||||||
PInputDevice input();
|
|
||||||
DevicePair& setInput(PInputDevice input);
|
|
||||||
|
|
||||||
POutputDevice output();
|
|
||||||
DevicePair& setOutput(POutputDevice output);
|
|
||||||
|
|
||||||
bool start();
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
DevicePair& setDelegate(Delegate* dc);
|
|
||||||
Delegate* delegate();
|
|
||||||
|
|
||||||
DevicePair& setMonitoring(DataConnection* monitoring);
|
|
||||||
DataConnection* monitoring();
|
|
||||||
|
|
||||||
Player& player();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
VariantMap* mConfig;
|
|
||||||
PInputDevice mInput;
|
|
||||||
POutputDevice mOutput;
|
|
||||||
Delegate* mDelegate;
|
|
||||||
bool mAec;
|
|
||||||
bool mAgc;
|
|
||||||
AgcFilter mAgcFilter;
|
|
||||||
AecFilter mAecFilter;
|
|
||||||
Player mPlayer;
|
|
||||||
UniversalResampler mMicResampler, mSpkResampler;
|
|
||||||
DataWindow mInputBuffer, mOutputBuffer, mAecSpkBuffer, mInputResampingData, mOutputNativeData, mOutput10msBuffer;
|
|
||||||
DataConnection* mMonitoring;
|
|
||||||
|
|
||||||
#ifdef DUMP_NATIVEOUTPUT
|
|
||||||
std::shared_ptr<WavFileWriter> mNativeOutputDump;
|
|
||||||
#endif
|
|
||||||
#ifdef DUMP_NATIVEINPUT
|
|
||||||
std::shared_ptr<WavFileWriter> mNativeInputDump;
|
|
||||||
#endif
|
|
||||||
void onMicData(const Format& f, const void* buffer, int length);
|
|
||||||
void onSpkData(const Format& f, void* buffer, int length);
|
|
||||||
void processMicData(const Format& f, void* buffer, int length);
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<DevicePair> PDevicePair;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,187 +0,0 @@
|
||||||
/* Copyright(C) 2007-2025 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 __AUDIO_DSOUND_H
|
|
||||||
#define __AUDIO_DSOUND_H
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <mmsystem.h>
|
|
||||||
|
|
||||||
#include "../Helper/HL_Sync.h"
|
|
||||||
#include "../Helper/HL_ByteBuffer.h"
|
|
||||||
#include "Audio_WavFile.h"
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <EndpointVolume.h>
|
|
||||||
#include <MMDeviceAPI.h>
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
# include <Functiondiscoverykeys_devpkey.h>
|
|
||||||
#endif
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <InitGuid.h>
|
|
||||||
#include <dsound.h>
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class VistaEnumerator: public Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
VistaEnumerator();
|
|
||||||
~VistaEnumerator();
|
|
||||||
|
|
||||||
void open(int direction);
|
|
||||||
void close();
|
|
||||||
|
|
||||||
int count();
|
|
||||||
std::tstring nameAt(int index);
|
|
||||||
int idAt(int index);
|
|
||||||
int indexOfDefaultDevice();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
IMMDeviceCollection* mCollection;
|
|
||||||
IMMDevice* mDefaultDevice;
|
|
||||||
IMMDeviceEnumerator* mEnumerator;
|
|
||||||
EDataFlow mDirection;
|
|
||||||
std::vector<std::wstring> mNameList;
|
|
||||||
|
|
||||||
void enumerate();
|
|
||||||
IMMDevice* mapIndexToInterface(int index);
|
|
||||||
};
|
|
||||||
|
|
||||||
class XpEnumerator: public Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
XpEnumerator();
|
|
||||||
~XpEnumerator();
|
|
||||||
|
|
||||||
void open(int direction);
|
|
||||||
void close();
|
|
||||||
|
|
||||||
int count();
|
|
||||||
std::tstring nameAt(int index);
|
|
||||||
int idAt(int index);
|
|
||||||
int indexOfDefaultDevice();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<std::wstring> mNameList;
|
|
||||||
int mDirection;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DSoundHelper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void checkComResult(HRESULT code);
|
|
||||||
static GUID deviceId2Guid(int deviceId, bool captureDevice);
|
|
||||||
};
|
|
||||||
|
|
||||||
#if !defined(_MSC_VER)
|
|
||||||
typedef struct IDirectSoundNotify8 *LPDIRECTSOUNDNOTIFY8;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class DSoundInputDevice: public InputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DSoundInputDevice(GUID deviceId);
|
|
||||||
~DSoundInputDevice();
|
|
||||||
|
|
||||||
void enableDenoiser(bool enable);
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
|
|
||||||
bool isSimulate() const;
|
|
||||||
void setSimulate(bool s);
|
|
||||||
|
|
||||||
int readBuffer(void* buffer);
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Mutex mGuard; /// Mutex to protect this instance.
|
|
||||||
LPDIRECTSOUNDCAPTURE8 mDevice;
|
|
||||||
LPDIRECTSOUNDCAPTUREBUFFER8 mBuffer;
|
|
||||||
LPDIRECTSOUNDNOTIFY8 mNotifications;
|
|
||||||
DSBPOSITIONNOTIFY mEventArray[AUDIO_MIC_BUFFER_COUNT];
|
|
||||||
HANDLE mEventSignals[AUDIO_MIC_BUFFER_COUNT]; // Helper array to make WaitForMultipleObjects in loop
|
|
||||||
|
|
||||||
int mBufferIndex;
|
|
||||||
int mNextBuffer;
|
|
||||||
GUID mGUID;
|
|
||||||
|
|
||||||
HANDLE mThreadHandle;
|
|
||||||
HANDLE mShutdownSignal;
|
|
||||||
volatile bool mSimulate; /// Marks if simulate mode is active.
|
|
||||||
int mRefCount;
|
|
||||||
ByteBuffer mQueue;
|
|
||||||
unsigned mReadOffset;
|
|
||||||
DenoiseFilter mDenoiser;
|
|
||||||
volatile bool mEnableDenoiser;
|
|
||||||
char mTempBuffer[AUDIO_MIC_BUFFER_SIZE];
|
|
||||||
StubTimer mNullAudio;
|
|
||||||
|
|
||||||
#ifdef AUDIO_DUMPINPUT
|
|
||||||
WavFileWriter mDump;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool tryReadBuffer(void* buffer);
|
|
||||||
void openDevice();
|
|
||||||
void closeDevice();
|
|
||||||
|
|
||||||
static void threadProc(void* arg);
|
|
||||||
};
|
|
||||||
|
|
||||||
class DSoundOutputDevice: public OutputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DSoundOutputDevice(GUID deviceId);
|
|
||||||
~DSoundOutputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
|
|
||||||
unsigned playedTime() const;
|
|
||||||
bool isSimulate() const;
|
|
||||||
void setSimulate(bool s);
|
|
||||||
bool closing();
|
|
||||||
Format getFormat();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Mutex mGuard; /// Mutex to protect this instance
|
|
||||||
int mDeviceID;
|
|
||||||
LPDIRECTSOUND8 mDevice;
|
|
||||||
LPDIRECTSOUNDBUFFER mPrimaryBuffer;
|
|
||||||
LPDIRECTSOUNDBUFFER mBuffer;
|
|
||||||
GUID mGUID;
|
|
||||||
unsigned mWriteOffset;
|
|
||||||
unsigned mPlayedSamples;
|
|
||||||
unsigned mSentBytes;
|
|
||||||
DWORD mPlayCursor; // Measured in bytes
|
|
||||||
unsigned mBufferSize;
|
|
||||||
unsigned mTotalPlayed; // Measured in bytes
|
|
||||||
unsigned mTail; // Measured in bytes
|
|
||||||
HANDLE mShutdownSignal;
|
|
||||||
HANDLE mBufferSignal;
|
|
||||||
HANDLE mThreadHandle;
|
|
||||||
bool mSimulate;
|
|
||||||
StubTimer mNullAudio;
|
|
||||||
DWORD mWriteCursor;
|
|
||||||
char mMediaFrame[AUDIO_SPK_BUFFER_SIZE];
|
|
||||||
unsigned mRefCount;
|
|
||||||
|
|
||||||
void openDevice();
|
|
||||||
void closeDevice();
|
|
||||||
void restoreBuffer();
|
|
||||||
bool process();
|
|
||||||
bool getMediaFrame();
|
|
||||||
|
|
||||||
static void threadProc(void* arg);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
# include <WinSock2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
// --- QPCSource
|
|
||||||
TimeSource::TimeSource(int quantTime, int nrOfQuants)
|
|
||||||
{
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
mCounter.QuadPart = 0;
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_IOS)
|
|
||||||
mach_timebase_info(&mTimebase);
|
|
||||||
mRatio = ((double)mTimebase.numer / (double)mTimebase.denom) / 1000000;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
mQuantTime = quantTime;
|
|
||||||
mDepthTime = quantTime * nrOfQuants;
|
|
||||||
mTailTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeSource::start()
|
|
||||||
{
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
if (!QueryPerformanceFrequency(&mFreq))
|
|
||||||
throw Exception(ERR_QPC, GetLastError());
|
|
||||||
if (!QueryPerformanceCounter(&mCounter))
|
|
||||||
throw Exception(ERR_QPC, GetLastError());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimeSource::stop()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned TimeSource::time()
|
|
||||||
{
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
LARGE_INTEGER c;
|
|
||||||
if (!QueryPerformanceCounter(&c))
|
|
||||||
throw Exception(ERR_QPC, GetLastError());
|
|
||||||
|
|
||||||
//find the f
|
|
||||||
double f = (double)mFreq.QuadPart / 1000.0;
|
|
||||||
|
|
||||||
//find the difference
|
|
||||||
unsigned __int64 diff = c.QuadPart - mCounter.QuadPart;
|
|
||||||
|
|
||||||
mCounter.QuadPart = c.QuadPart;
|
|
||||||
|
|
||||||
diff = (unsigned __int64)((double)diff / f + 0.5); //get ms
|
|
||||||
diff += mTailTime;
|
|
||||||
|
|
||||||
if (diff > mDepthTime)
|
|
||||||
{
|
|
||||||
mTailTime = 0;
|
|
||||||
return mDepthTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTailTime = (unsigned )(diff % (unsigned __int64)mQuantTime);
|
|
||||||
unsigned int t = (unsigned )(diff / (unsigned __int64)mQuantTime);
|
|
||||||
return t * mQuantTime;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_IOS)
|
|
||||||
uint64_t t = mach_absolute_time();
|
|
||||||
uint64_t c = uint64_t((double)t * mRatio + 0.5);
|
|
||||||
|
|
||||||
uint64_t diff = c - this->mTime + mTailTime;
|
|
||||||
mTime = c;
|
|
||||||
if (diff > mDepthTime)
|
|
||||||
{
|
|
||||||
mTailTime = 0;
|
|
||||||
return mDepthTime;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mTailTime = diff % mQuantTime;
|
|
||||||
uint64_t t = diff / mQuantTime;
|
|
||||||
return t * mQuantTime;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_LINUX)
|
|
||||||
assert(0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_ANDROID)
|
|
||||||
assert(0);
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- StubTimer ---
|
|
||||||
StubTimer::StubTimer(int bufferTime, int bufferCount)
|
|
||||||
:mBufferTime(bufferTime), mBufferCount(bufferCount), mTimeSource(bufferTime, bufferCount), mActive(false), mCurrentTime(0)
|
|
||||||
{
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
mStubSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
StubTimer::~StubTimer()
|
|
||||||
{
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
::CloseHandle(mStubSignal);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void StubTimer::start()
|
|
||||||
{
|
|
||||||
mTimeSource.start();
|
|
||||||
mCurrentTime = mTimeSource.time();
|
|
||||||
mActive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StubTimer::stop()
|
|
||||||
{
|
|
||||||
mTimeSource.stop();
|
|
||||||
mActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StubTimer::waitForBuffer()
|
|
||||||
{
|
|
||||||
if (!mActive)
|
|
||||||
start();
|
|
||||||
|
|
||||||
unsigned t = mTimeSource.time();
|
|
||||||
|
|
||||||
while (!t)
|
|
||||||
{
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
::WaitForSingleObject(mStubSignal, mBufferTime);
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_IOS)
|
|
||||||
usleep(100);
|
|
||||||
#endif
|
|
||||||
t = mTimeSource.time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __AUDIO_HELPER_H
|
|
||||||
#define __AUDIO_HELPER_H
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
#include <EndpointVolume.h>
|
|
||||||
#include <MMDeviceAPI.h>
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
# include <Functiondiscoverykeys_devpkey.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_IOS)
|
|
||||||
# include <AudioUnit/AudioUnit.h>
|
|
||||||
# include <AudioToolbox/AudioConverter.h>
|
|
||||||
# include <AudioToolbox/AudioServices.h>
|
|
||||||
# include <mach/mach_time.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class TimeSource
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
LARGE_INTEGER mCounter; /// Current value from QPC.
|
|
||||||
LARGE_INTEGER mFreq; /// Current frequency from QPC.
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_IOS)
|
|
||||||
uint64_t mTime;
|
|
||||||
struct mach_timebase_info mTimebase;
|
|
||||||
double mRatio;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
unsigned mQuantTime; /// Used time quants length in milliseconds.
|
|
||||||
unsigned mDepthTime; /// Number of available time quants.
|
|
||||||
unsigned mTailTime; /// Not-accounted milliseconds.
|
|
||||||
|
|
||||||
public:
|
|
||||||
TimeSource(int quantTime, int nrOfQuants);
|
|
||||||
~TimeSource() = default;
|
|
||||||
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
unsigned time();
|
|
||||||
};
|
|
||||||
|
|
||||||
class StubTimer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
StubTimer(int bufferTime, int bufferCount);
|
|
||||||
~StubTimer();
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
void waitForBuffer();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
unsigned mBufferTime;
|
|
||||||
unsigned mBufferCount;
|
|
||||||
unsigned mCurrentTime;
|
|
||||||
TimeSource mTimeSource;
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
HANDLE mStubSignal;
|
|
||||||
#endif
|
|
||||||
bool mActive;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 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/. */
|
|
||||||
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include "../helper/HL_OsVersion.h"
|
|
||||||
|
|
||||||
#if !defined(USE_NULL_AUDIO)
|
|
||||||
# ifdef TARGET_WIN
|
|
||||||
# include "Audio_Wmme.h"
|
|
||||||
# include "Audio_DirectSound.h"
|
|
||||||
# endif
|
|
||||||
# ifdef TARGET_OSX
|
|
||||||
# include "Audio_CoreAudio.h"
|
|
||||||
# endif
|
|
||||||
# ifdef TARGET_ANDROID
|
|
||||||
# include "Audio_Android.h"
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
#include "Audio_Null.h"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
Device::Device()
|
|
||||||
:mConnection(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Device::~Device()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Device::setConnection(DataConnection* connection)
|
|
||||||
{
|
|
||||||
mConnection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataConnection* Device::connection()
|
|
||||||
{
|
|
||||||
return mConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDevice::InputDevice()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDevice::~InputDevice()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
InputDevice* InputDevice::make(int devId)
|
|
||||||
{
|
|
||||||
#if defined(USE_NULL_AUDIO)
|
|
||||||
return new NullInputDevice();
|
|
||||||
#else
|
|
||||||
#if defined(TARGET_WIN) && defined(_MSC_VER)
|
|
||||||
// return new WmmeInputDevice(index);
|
|
||||||
return new DSoundInputDevice(DSoundHelper::deviceId2Guid(devId, true));
|
|
||||||
#endif
|
|
||||||
#ifdef TARGET_OSX
|
|
||||||
return new MacInputDevice(devId);
|
|
||||||
#endif
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
return new AndroidInputDevice(devId);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputDevice::OutputDevice()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputDevice::~OutputDevice()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputDevice* OutputDevice::make(int devId)
|
|
||||||
{
|
|
||||||
#if defined(USE_NULL_AUDIO)
|
|
||||||
return new NullOutputDevice();
|
|
||||||
#else
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
//return new WmmeOutputDevice(index);
|
|
||||||
return new DSoundOutputDevice(DSoundHelper::deviceId2Guid(devId, false));
|
|
||||||
#endif
|
|
||||||
#ifdef TARGET_OSX
|
|
||||||
return new MacOutputDevice(devId);
|
|
||||||
#endif
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
return new AndroidOutputDevice(devId);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Enumerator ---
|
|
||||||
Enumerator::Enumerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Enumerator::~Enumerator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int Enumerator::nameToIndex(const std::tstring& name)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < count(); i++)
|
|
||||||
if (nameAt(i) == name)
|
|
||||||
return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Enumerator* Enumerator::make(bool useNull)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (useNull)
|
|
||||||
return new NullEnumerator();
|
|
||||||
#ifndef USE_NULL_AUDIO
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
if (winVersion() > Win_Xp)
|
|
||||||
return new VistaEnumerator();
|
|
||||||
else
|
|
||||||
return new XpEnumerator();
|
|
||||||
#endif
|
|
||||||
#ifdef TARGET_OSX
|
|
||||||
return new MacEnumerator();
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
return new NullEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----- OsEngine ------------
|
|
||||||
|
|
||||||
OsEngine* OsEngine::instance()
|
|
||||||
{
|
|
||||||
#ifdef USE_NULL_AUDIO
|
|
||||||
return nullptr;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef TARGET_ANDROID
|
|
||||||
return nullptr; // As we use Oboe library for now
|
|
||||||
//return &OpenSLEngine::instance();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
/* Copyright(C) 2007-2016 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 __AUDIO_INTERFACE_H
|
|
||||||
#define __AUDIO_INTERFACE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "../helper/HL_Types.h"
|
|
||||||
#include "../helper/HL_VariantMap.h"
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "Audio_WavFile.h"
|
|
||||||
#include "Audio_Quality.h"
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
myMicrophone = 1,
|
|
||||||
mySpeaker = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Format
|
|
||||||
{
|
|
||||||
int mRate;
|
|
||||||
int mChannels;
|
|
||||||
|
|
||||||
Format()
|
|
||||||
:mRate(AUDIO_SAMPLERATE), mChannels(AUDIO_CHANNELS)
|
|
||||||
{}
|
|
||||||
|
|
||||||
Format(int rate, int channels)
|
|
||||||
:mRate(rate), mChannels(channels)
|
|
||||||
{}
|
|
||||||
|
|
||||||
size_t samplesFromSize(size_t length) const
|
|
||||||
{
|
|
||||||
return length / 2 / mChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns milliseconds
|
|
||||||
float timeFromSize(size_t length) const
|
|
||||||
{
|
|
||||||
return float(samplesFromSize(length) / (mRate / 1000.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
float sizeFromTime(size_t milliseconds) const
|
|
||||||
{
|
|
||||||
return float((milliseconds * mRate) / 500.0 * mChannels);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string toString()
|
|
||||||
{
|
|
||||||
char buffer[64];
|
|
||||||
sprintf(buffer, "%dHz %dch", mRate, mChannels);
|
|
||||||
return std::string(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator == (const Format& rhs) const
|
|
||||||
{
|
|
||||||
return mRate == rhs.mRate && mChannels == rhs.mChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator != (const Format& rhs) const
|
|
||||||
{
|
|
||||||
return mRate != rhs.mRate || mChannels != rhs.mChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rate() const
|
|
||||||
{
|
|
||||||
return mRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
int channels() const
|
|
||||||
{
|
|
||||||
return mChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class DataConnection
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void onMicData(const Format& format, const void* buffer, int length) = 0;
|
|
||||||
virtual void onSpkData(const Format& format, void* buffer, int length) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Device();
|
|
||||||
virtual ~Device();
|
|
||||||
|
|
||||||
void setConnection(DataConnection* connection);
|
|
||||||
DataConnection* connection();
|
|
||||||
|
|
||||||
virtual bool open() = 0;
|
|
||||||
virtual void close() = 0;
|
|
||||||
virtual Format getFormat() = 0;
|
|
||||||
protected:
|
|
||||||
DataConnection* mConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class InputDevice: public Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
InputDevice();
|
|
||||||
virtual ~InputDevice();
|
|
||||||
|
|
||||||
static InputDevice* make(int devId);
|
|
||||||
};
|
|
||||||
typedef std::shared_ptr<InputDevice> PInputDevice;
|
|
||||||
|
|
||||||
class OutputDevice: public Device
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
OutputDevice();
|
|
||||||
virtual ~OutputDevice();
|
|
||||||
|
|
||||||
static OutputDevice* make(int devId);
|
|
||||||
};
|
|
||||||
typedef std::shared_ptr<OutputDevice> POutputDevice;
|
|
||||||
|
|
||||||
class Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Enumerator();
|
|
||||||
virtual ~Enumerator();
|
|
||||||
int nameToIndex(const std::tstring& name);
|
|
||||||
|
|
||||||
virtual void open(int direction) = 0;
|
|
||||||
virtual void close() = 0;
|
|
||||||
|
|
||||||
virtual int count() = 0;
|
|
||||||
virtual std::tstring nameAt(int index) = 0;
|
|
||||||
virtual int idAt(int index) = 0;
|
|
||||||
virtual int indexOfDefaultDevice() = 0;
|
|
||||||
|
|
||||||
static Enumerator* make(bool useNull = false);
|
|
||||||
};
|
|
||||||
|
|
||||||
class OsEngine
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void open() = 0;
|
|
||||||
virtual void close() = 0;
|
|
||||||
|
|
||||||
static OsEngine* instance();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,337 +0,0 @@
|
||||||
/* Copyright(C) 2007-2018 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/. */
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "Audio_Mixer.h"
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Mixer"
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
Mixer::Stream::Stream()
|
|
||||||
{
|
|
||||||
mResampler8.start(AUDIO_CHANNELS, 8000, AUDIO_SAMPLERATE);
|
|
||||||
mResampler16.start(AUDIO_CHANNELS, 16000, AUDIO_SAMPLERATE);
|
|
||||||
mResampler32.start(AUDIO_CHANNELS, 32000, AUDIO_SAMPLERATE);
|
|
||||||
mResampler48.start(AUDIO_CHANNELS, 48000, AUDIO_SAMPLERATE);
|
|
||||||
mActive = false;
|
|
||||||
mContext = nullptr;
|
|
||||||
mSSRC = 0;
|
|
||||||
mFadeOutCounter = 0;
|
|
||||||
mData.setCapacity(AUDIO_SPK_BUFFER_SIZE * AUDIO_SPK_BUFFER_COUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mixer::Stream::~Stream()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::Stream::setSsrc(unsigned ssrc)
|
|
||||||
{
|
|
||||||
mSSRC = ssrc;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned Mixer::Stream::ssrc()
|
|
||||||
{
|
|
||||||
return mSSRC;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::Stream::setContext(void* context)
|
|
||||||
{
|
|
||||||
mContext = context;
|
|
||||||
}
|
|
||||||
void* Mixer::Stream::context()
|
|
||||||
{
|
|
||||||
return mContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataWindow& Mixer::Stream::data()
|
|
||||||
{
|
|
||||||
return mData;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Mixer::Stream::active()
|
|
||||||
{
|
|
||||||
return mActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::Stream::setActive(bool active)
|
|
||||||
{
|
|
||||||
mActive = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::Stream::addPcm(int rate, const void* input, int length)
|
|
||||||
{
|
|
||||||
// Resample to internal sample rate
|
|
||||||
size_t outputSize = size_t(0.5 + length * ((float)AUDIO_SAMPLERATE / rate));
|
|
||||||
if (mTempBuffer.size() < outputSize)
|
|
||||||
mTempBuffer.resize(outputSize);
|
|
||||||
|
|
||||||
Resampler* resampler = (rate == 8000) ? &mResampler8 : ((rate == 16000) ? &mResampler16 : ((rate == 32000) ? &mResampler32 : &mResampler48));
|
|
||||||
size_t inputProcessed = 0;
|
|
||||||
resampler->processBuffer(input, length, inputProcessed, mTempBuffer.mutableData(), outputSize);
|
|
||||||
// inputProcessed result value is ignored here - rate will be 8/16/32/48k, inputProcessed is equal to length
|
|
||||||
|
|
||||||
// Queue data
|
|
||||||
mData.add(mTempBuffer.data(), outputSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mixer::Mixer()
|
|
||||||
{
|
|
||||||
mActiveCounter = 0;
|
|
||||||
mOutput.setCapacity(32768);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mixer::~Mixer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::unregisterChannel(void* channel)
|
|
||||||
{
|
|
||||||
for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++)
|
|
||||||
{
|
|
||||||
Stream& c = mChannelList[i];
|
|
||||||
if (c.active() && c.context() == channel)
|
|
||||||
{
|
|
||||||
c.setActive(false); // stream is not active anymore
|
|
||||||
c.data().clear(); // clear data
|
|
||||||
mActiveCounter--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::clear(void* context, unsigned ssrc)
|
|
||||||
{
|
|
||||||
for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++)
|
|
||||||
{
|
|
||||||
Stream& c = mChannelList[i];
|
|
||||||
if (c.active() && c.context() == context && c.ssrc() == ssrc)
|
|
||||||
{
|
|
||||||
c.setActive(false);
|
|
||||||
c.data().clear();
|
|
||||||
mActiveCounter--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Mixer::Stream* Mixer::allocateChannel(void* context, unsigned ssrc)
|
|
||||||
{
|
|
||||||
// Allocate new channel
|
|
||||||
Lock l(mMutex);
|
|
||||||
Stream* channel;
|
|
||||||
for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT;i++)
|
|
||||||
{
|
|
||||||
channel = &mChannelList[i];
|
|
||||||
if (!channel->active())
|
|
||||||
{
|
|
||||||
channel->setSsrc(ssrc);
|
|
||||||
channel->setContext(context);
|
|
||||||
channel->data().clear();
|
|
||||||
mActiveCounter++;
|
|
||||||
channel->setActive(true);
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::addPcm(void* context, unsigned ssrc,
|
|
||||||
const void* inputData, int inputLength,
|
|
||||||
int inputRate, bool fadeOut)
|
|
||||||
{
|
|
||||||
assert(inputRate == 8000 || inputRate == 16000 || inputRate == 32000);
|
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Locate a channel
|
|
||||||
Stream* channel = NULL;
|
|
||||||
|
|
||||||
for (i=0; i<AUDIO_MIX_CHANNEL_COUNT && !channel; i++)
|
|
||||||
{
|
|
||||||
Stream& c = mChannelList[i];
|
|
||||||
if (c.active() && c.context() == context && c.ssrc() == ssrc)
|
|
||||||
channel = &c;
|
|
||||||
}
|
|
||||||
if (!channel)
|
|
||||||
{
|
|
||||||
channel = allocateChannel(context, ssrc);
|
|
||||||
if (!channel)
|
|
||||||
throw Exception(ERR_MIXER_OVERFLOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
channel->addPcm(inputRate, inputData, inputLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::addPcm(void* context, unsigned ssrc, Audio::DataWindow& w, int rate, bool fadeOut)
|
|
||||||
{
|
|
||||||
assert(rate == 8000 || rate == 16000 || rate == 32000 || rate == 48000);
|
|
||||||
|
|
||||||
int i;
|
|
||||||
|
|
||||||
// Locate a channel
|
|
||||||
Stream* channel = NULL;
|
|
||||||
|
|
||||||
for (i=0; i<AUDIO_MIX_CHANNEL_COUNT && !channel; i++)
|
|
||||||
{
|
|
||||||
Stream& c = mChannelList[i];
|
|
||||||
if (c.active() && c.context() == context && c.ssrc() == ssrc)
|
|
||||||
channel = &c;
|
|
||||||
}
|
|
||||||
if (!channel)
|
|
||||||
{
|
|
||||||
channel = allocateChannel(context, ssrc);
|
|
||||||
if (!channel)
|
|
||||||
throw Exception(ERR_MIXER_OVERFLOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
channel->addPcm(rate, w.data(), w.filled());
|
|
||||||
//ICELogSpecial(<<"Mixer stream " << int(this) << " has " << w.filled() << " bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::mix()
|
|
||||||
{
|
|
||||||
// Current sample
|
|
||||||
int sample = 0;
|
|
||||||
|
|
||||||
// Counter of processed active channels
|
|
||||||
int processed = 0;
|
|
||||||
|
|
||||||
// Samples & sources counters
|
|
||||||
unsigned sampleCounter = 0, sourceCounter;
|
|
||||||
|
|
||||||
short outputBuffer[512];
|
|
||||||
unsigned outputCounter = 0;
|
|
||||||
|
|
||||||
// Build active channel map
|
|
||||||
Stream* channelList[AUDIO_MIX_CHANNEL_COUNT];
|
|
||||||
int activeCounter = 0;
|
|
||||||
for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++)
|
|
||||||
if (mChannelList[i].active())
|
|
||||||
channelList[activeCounter++] = &mChannelList[i];
|
|
||||||
|
|
||||||
// No active channels - nothing to mix - exit
|
|
||||||
if (!activeCounter)
|
|
||||||
{
|
|
||||||
// ICELogDebug(<< "No active channel");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimized versions for 1& 2 active channels
|
|
||||||
if (activeCounter == 1)
|
|
||||||
{
|
|
||||||
// Copy much samples as we have
|
|
||||||
Stream& audio = *channelList[0];
|
|
||||||
|
|
||||||
// Copy the decoded data
|
|
||||||
mOutput.add(audio.data().data(), audio.data().filled());
|
|
||||||
|
|
||||||
// Erase copied audio samples
|
|
||||||
audio.data().erase(audio.data().filled());
|
|
||||||
//ICELogSpecial(<<"Length of mixer stream " << audio.data().filled());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (activeCounter == 2)
|
|
||||||
{
|
|
||||||
Stream& audio1 = *channelList[0];
|
|
||||||
Stream& audio2 = *channelList[1];
|
|
||||||
int filled1 = audio1.data().filled() / 2, filled2 = audio2.data().filled() / 2;
|
|
||||||
int available = filled1 > filled2 ? filled1 : filled2;
|
|
||||||
|
|
||||||
// Find how much samples can be mixed
|
|
||||||
int filled = mOutput.filled() / 2;
|
|
||||||
|
|
||||||
int maxsize = mOutput.capacity() / 2;
|
|
||||||
if (maxsize - filled < available)
|
|
||||||
available = maxsize - filled;
|
|
||||||
|
|
||||||
short sample = 0;
|
|
||||||
for (int i=0; i<available; i++)
|
|
||||||
{
|
|
||||||
short sample1 = filled1 > i ? audio1.data().shortAt(i) : 0;
|
|
||||||
short sample2 = filled2 > i ? audio2.data().shortAt(i) : 0;
|
|
||||||
sample = (abs(sample1) > abs(sample2)) ? sample1 : sample2;
|
|
||||||
|
|
||||||
mOutput.add(sample);
|
|
||||||
}
|
|
||||||
audio1.data().erase(available*2);
|
|
||||||
audio2.data().erase(available*2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
sample = 0;
|
|
||||||
sourceCounter = 0;
|
|
||||||
processed = 0;
|
|
||||||
for (int i=0; i<activeCounter; i++)
|
|
||||||
{
|
|
||||||
Stream& audio = *channelList[i];
|
|
||||||
processed++;
|
|
||||||
|
|
||||||
if (audio.data().filled() > (int)sampleCounter * 2)
|
|
||||||
{
|
|
||||||
short currentSample = audio.data().shortAt(sampleCounter);
|
|
||||||
if (abs(currentSample) > abs(sample))
|
|
||||||
sample = currentSample;
|
|
||||||
sourceCounter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceCounter)
|
|
||||||
{
|
|
||||||
outputBuffer[outputCounter++] = (short)sample;
|
|
||||||
sampleCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if time to flash output buffer
|
|
||||||
if ((!sourceCounter || outputCounter == 512) && outputCounter)
|
|
||||||
{
|
|
||||||
mOutput.add(outputBuffer, outputCounter * 2);
|
|
||||||
outputCounter = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (sourceCounter);
|
|
||||||
|
|
||||||
processed = 0;
|
|
||||||
for (int i=0; i<activeCounter; i++)
|
|
||||||
{
|
|
||||||
Stream& audio = *channelList[i];
|
|
||||||
audio.data().erase(sampleCounter*2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int Mixer::getPcm(void* outputData, int outputLength)
|
|
||||||
{
|
|
||||||
if (mOutput.filled() < outputLength)
|
|
||||||
mix();
|
|
||||||
|
|
||||||
//ICELogSpecial(<<"Mixer has " << mOutput.filled() << " available bytes");
|
|
||||||
memset(outputData, 0, outputLength);
|
|
||||||
return mOutput.read(outputData, outputLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Mixer::mixAndGetPcm(Audio::DataWindow& output)
|
|
||||||
{
|
|
||||||
// Mix
|
|
||||||
mix();
|
|
||||||
|
|
||||||
// Set output space
|
|
||||||
output.setCapacity(mOutput.filled());
|
|
||||||
|
|
||||||
// Read mixed data to output
|
|
||||||
return mOutput.read(output.mutableData(), output.capacity());
|
|
||||||
}
|
|
||||||
|
|
||||||
int Mixer::available()
|
|
||||||
{
|
|
||||||
return mOutput.filled();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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 _RX_MIXER_H
|
|
||||||
#define _RX_MIXER_H
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
#include "Audio_DataWindow.h"
|
|
||||||
#include <map>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class Mixer
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
class Stream
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
DataWindow mData;
|
|
||||||
Resampler mResampler8,
|
|
||||||
mResampler16,
|
|
||||||
mResampler32,
|
|
||||||
mResampler48;
|
|
||||||
bool mActive;
|
|
||||||
void* mContext;
|
|
||||||
unsigned mSSRC;
|
|
||||||
unsigned mFadeOutCounter;
|
|
||||||
ByteBuffer mTempBuffer;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Stream();
|
|
||||||
~Stream();
|
|
||||||
|
|
||||||
void setSsrc(unsigned ssrc);
|
|
||||||
unsigned ssrc();
|
|
||||||
void setContext(void* context);
|
|
||||||
void* context();
|
|
||||||
DataWindow& data();
|
|
||||||
bool active();
|
|
||||||
void setActive(bool active);
|
|
||||||
void addPcm(int rate, const void* input, int length);
|
|
||||||
};
|
|
||||||
|
|
||||||
Stream mChannelList[AUDIO_MIX_CHANNEL_COUNT];
|
|
||||||
Mutex mMutex;
|
|
||||||
DataWindow mOutput;
|
|
||||||
std::atomic_int mActiveCounter;
|
|
||||||
|
|
||||||
void mix();
|
|
||||||
Stream* allocateChannel(void* context, unsigned ssrc);
|
|
||||||
|
|
||||||
public:
|
|
||||||
Mixer();
|
|
||||||
~Mixer();
|
|
||||||
|
|
||||||
void unregisterChannel(void* context);
|
|
||||||
void clear(void* context, unsigned ssrc);
|
|
||||||
void addPcm(void* context, unsigned ssrc, const void* inputData, int inputLength, int inputRate, bool fadeOut);
|
|
||||||
void addPcm(void* context, unsigned ssrc, Audio::DataWindow& w, int rate, bool fadeOut);
|
|
||||||
int getPcm(void* outputData, int outputLength);
|
|
||||||
int mixAndGetPcm(Audio::DataWindow& output);
|
|
||||||
int available();
|
|
||||||
};
|
|
||||||
} //end of namespace
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
#include "Audio_Null.h"
|
|
||||||
#include "helper/HL_Log.h"
|
|
||||||
#include <assert.h>
|
|
||||||
#include <chrono>
|
|
||||||
#define LOG_SUBSYSTEM "NULL audio"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
NullTimer::NullTimer(int interval, Delegate *delegate, const char* name)
|
|
||||||
:mShutdown(false), mDelegate(delegate), mInterval(interval), mThreadName(name)
|
|
||||||
{
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
NullTimer::~NullTimer()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullTimer::start()
|
|
||||||
{
|
|
||||||
mShutdown = false;
|
|
||||||
mWorkerThread = std::thread(&NullTimer::run, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullTimer::stop()
|
|
||||||
{
|
|
||||||
mShutdown = true;
|
|
||||||
if (mWorkerThread.joinable())
|
|
||||||
mWorkerThread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullTimer::run()
|
|
||||||
{
|
|
||||||
mTail = 0;
|
|
||||||
while (!mShutdown)
|
|
||||||
{
|
|
||||||
// Get current timestamp
|
|
||||||
std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
|
|
||||||
|
|
||||||
while (mTail >= mInterval * 1000)
|
|
||||||
{
|
|
||||||
if (mDelegate)
|
|
||||||
mDelegate->onTimerSignal(*this);
|
|
||||||
mTail -= mInterval * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep for mInterval - mTail milliseconds
|
|
||||||
std::this_thread::sleep_for(std::chrono::microseconds(mInterval * 1000 - mTail));
|
|
||||||
|
|
||||||
mTail += (int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - timestamp).count();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------- NullInputDevice -------------------------
|
|
||||||
NullInputDevice::NullInputDevice()
|
|
||||||
:mBuffer(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NullInputDevice::~NullInputDevice()
|
|
||||||
{
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NullInputDevice::open()
|
|
||||||
{
|
|
||||||
mBuffer = malloc(AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
memset(mBuffer, 0, AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
mTimeCounter = 0; mDataCounter = 0;
|
|
||||||
// Creation of timer starts it also. So first onTimerSignal can come even before open() returns.
|
|
||||||
mTimer = std::make_shared<NullTimer>(AUDIO_MIC_BUFFER_LENGTH, this, "NullMicrophoneThread");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullInputDevice::internalClose()
|
|
||||||
{
|
|
||||||
mTimer.reset();
|
|
||||||
if (mBuffer)
|
|
||||||
{
|
|
||||||
free(mBuffer);
|
|
||||||
mBuffer = nullptr;
|
|
||||||
}
|
|
||||||
ICELogInfo(<<"Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullInputDevice::close()
|
|
||||||
{
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
Format NullInputDevice::getFormat()
|
|
||||||
{
|
|
||||||
assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
return Format();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullInputDevice::onTimerSignal(NullTimer& timer)
|
|
||||||
{
|
|
||||||
mTimeCounter += AUDIO_MIC_BUFFER_LENGTH;
|
|
||||||
mDataCounter += AUDIO_MIC_BUFFER_SIZE;
|
|
||||||
if (mConnection)
|
|
||||||
mConnection->onMicData(getFormat(), mBuffer, AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------- NullOutputDevice --------------------------
|
|
||||||
NullOutputDevice::NullOutputDevice()
|
|
||||||
:mBuffer(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NullOutputDevice::~NullOutputDevice()
|
|
||||||
{
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool NullOutputDevice::open()
|
|
||||||
{
|
|
||||||
mTimeCounter = 0; mDataCounter = 0;
|
|
||||||
mBuffer = malloc(AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
// Creation of timer starts it also. So first onSpkData() can come before open() returns even.
|
|
||||||
mTimer = std::make_shared<NullTimer>(AUDIO_SPK_BUFFER_LENGTH, this, "NullSpeakerThread");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullOutputDevice::internalClose()
|
|
||||||
{
|
|
||||||
mTimer.reset();
|
|
||||||
free(mBuffer); mBuffer = nullptr;
|
|
||||||
ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullOutputDevice::close()
|
|
||||||
{
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
Format NullOutputDevice::getFormat()
|
|
||||||
{
|
|
||||||
assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
return Format();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullOutputDevice::onTimerSignal(NullTimer &timer)
|
|
||||||
{
|
|
||||||
mTimeCounter += AUDIO_SPK_BUFFER_LENGTH;
|
|
||||||
mDataCounter += AUDIO_SPK_BUFFER_SIZE;
|
|
||||||
if (mConnection)
|
|
||||||
mConnection->onSpkData(getFormat(), mBuffer, AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------- NullEnumerator --------------------------
|
|
||||||
NullEnumerator::NullEnumerator()
|
|
||||||
{}
|
|
||||||
|
|
||||||
NullEnumerator::~NullEnumerator()
|
|
||||||
{}
|
|
||||||
|
|
||||||
void NullEnumerator::open(int direction)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void NullEnumerator::close()
|
|
||||||
{}
|
|
||||||
|
|
||||||
int NullEnumerator::count()
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::tstring NullEnumerator::nameAt(int index)
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
return L"null";
|
|
||||||
#else
|
|
||||||
return "null";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int NullEnumerator::idAt(int index)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NullEnumerator::indexOfDefaultDevice()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
#ifndef __AUDIO_NULL_H
|
|
||||||
#define __AUDIO_NULL_H
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class NullTimer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
class Delegate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void onTimerSignal(NullTimer& timer) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::thread mWorkerThread;
|
|
||||||
volatile bool mShutdown;
|
|
||||||
Delegate* mDelegate;
|
|
||||||
int mInterval, // Interval - wanted number of milliseconds
|
|
||||||
mTail; // Number of milliseconds that can be sent immediately to sink
|
|
||||||
std::string mThreadName;
|
|
||||||
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
void run();
|
|
||||||
public:
|
|
||||||
/* Interval is in milliseconds. */
|
|
||||||
NullTimer(int interval, Delegate* delegate, const char* name = nullptr);
|
|
||||||
~NullTimer();
|
|
||||||
};
|
|
||||||
|
|
||||||
class NullInputDevice: public InputDevice, public NullTimer::Delegate
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
void* mBuffer = nullptr;
|
|
||||||
std::shared_ptr<NullTimer> mTimer;
|
|
||||||
int64_t mTimeCounter = 0, mDataCounter = 0;
|
|
||||||
void internalClose();
|
|
||||||
|
|
||||||
public:
|
|
||||||
NullInputDevice();
|
|
||||||
virtual ~NullInputDevice();
|
|
||||||
|
|
||||||
bool open() override;
|
|
||||||
void close() override;
|
|
||||||
Format getFormat() override;
|
|
||||||
|
|
||||||
void onTimerSignal(NullTimer& timer) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NullOutputDevice: public OutputDevice, public NullTimer::Delegate
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
std::shared_ptr<NullTimer> mTimer;
|
|
||||||
void* mBuffer = nullptr;
|
|
||||||
int64_t mDataCounter = 0, mTimeCounter = 0;
|
|
||||||
|
|
||||||
void internalClose();
|
|
||||||
public:
|
|
||||||
NullOutputDevice();
|
|
||||||
virtual ~NullOutputDevice();
|
|
||||||
|
|
||||||
bool open() override;
|
|
||||||
void close() override;
|
|
||||||
Format getFormat() override;
|
|
||||||
|
|
||||||
void onTimerSignal(NullTimer& timer) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NullEnumerator: public Enumerator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NullEnumerator();
|
|
||||||
~NullEnumerator();
|
|
||||||
|
|
||||||
void open(int direction) override;
|
|
||||||
void close() override;
|
|
||||||
|
|
||||||
int count() override;
|
|
||||||
std::tstring nameAt(int index) override;
|
|
||||||
int idAt(int index) override;
|
|
||||||
int indexOfDefaultDevice() override;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
/* Copyright(C) 2007-2021 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/. */
|
|
||||||
|
|
||||||
#include "Audio_Player.h"
|
|
||||||
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Player"
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
// -------------- Player -----------
|
|
||||||
Player::Player()
|
|
||||||
:mDelegate(nullptr), mPlayedTime(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Player::~Player()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::setDelegate(EndOfAudioDelegate* d)
|
|
||||||
{
|
|
||||||
mDelegate = d;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player::EndOfAudioDelegate* Player::getDelegate() const
|
|
||||||
{
|
|
||||||
return mDelegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::setOutput(POutputDevice output)
|
|
||||||
{
|
|
||||||
mOutput = output;
|
|
||||||
if (mOutput)
|
|
||||||
mOutput->setConnection(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
POutputDevice Player::getOutput() const
|
|
||||||
{
|
|
||||||
return mOutput;
|
|
||||||
}
|
|
||||||
void Player::onMicData(const Format& f, const void* buffer, int length)
|
|
||||||
{
|
|
||||||
// Do nothing here - this data sink is not used in player
|
|
||||||
}
|
|
||||||
|
|
||||||
#define BYTES_PER_MILLISECOND (AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
|
|
||||||
|
|
||||||
void Player::onSpkData(const Format& f, void* buffer, int length)
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
|
|
||||||
// Fill buffer by zero if player owns dedicated device
|
|
||||||
if (mOutput)
|
|
||||||
memset(buffer, 0, length);
|
|
||||||
|
|
||||||
// See if there is item in playlist
|
|
||||||
int produced = 0;
|
|
||||||
while (mPlaylist.size() && produced < length)
|
|
||||||
{
|
|
||||||
PlaylistItem& item = mPlaylist.front();
|
|
||||||
// Check for timelength
|
|
||||||
if (item.mTimelength > 0 && item.mTimelength < mPlayedTime)
|
|
||||||
{
|
|
||||||
onFilePlayed();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int wasread = item.mFile->read((char*)buffer+produced, length-produced);
|
|
||||||
mPlayedTime += float(wasread) / BYTES_PER_MILLISECOND;
|
|
||||||
produced += wasread;
|
|
||||||
if (wasread < length-produced)
|
|
||||||
{
|
|
||||||
if (item.mLoop)
|
|
||||||
{
|
|
||||||
item.mFile->rewind();
|
|
||||||
wasread = item.mFile->read((char*)buffer+produced, (length - produced));
|
|
||||||
mPlayedTime += float(wasread) / BYTES_PER_MILLISECOND;
|
|
||||||
produced += wasread;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
onFilePlayed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::onFilePlayed()
|
|
||||||
{
|
|
||||||
// Save usage id to release later from main loop
|
|
||||||
mFinishedUsages.push_back(mPlaylist.front().mUsageId);
|
|
||||||
|
|
||||||
// Send event
|
|
||||||
if (mDelegate)
|
|
||||||
mDelegate->onFilePlayed(mPlaylist.front());
|
|
||||||
|
|
||||||
// Remove played item & reset played time
|
|
||||||
mPlaylist.pop_front();
|
|
||||||
mPlayedTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::obtain(int usage)
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
auto usageIter = mUsage.find(usage);
|
|
||||||
if (usageIter == mUsage.end())
|
|
||||||
mUsage[usage] = 1;
|
|
||||||
else
|
|
||||||
usageIter->second = usageIter->second + 1;
|
|
||||||
|
|
||||||
if (mUsage.size() == 1 && mOutput)
|
|
||||||
mOutput->open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::release(int usage)
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
UsageMap::iterator usageIter = mUsage.find(usage);
|
|
||||||
if (usageIter == mUsage.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
usageIter->second = usageIter->second - 1;
|
|
||||||
if (!usageIter->second)
|
|
||||||
mUsage.erase(usageIter);
|
|
||||||
|
|
||||||
for (unsigned i=0; i<mPlaylist.size(); i++)
|
|
||||||
if (mPlaylist[i].mUsageId == usage)
|
|
||||||
mPlaylist.erase(mPlaylist.begin() + i);
|
|
||||||
|
|
||||||
if (mUsage.empty() && mOutput)
|
|
||||||
mOutput->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Player::releasePlayed()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
int result = mFinishedUsages.size();
|
|
||||||
while (!mFinishedUsages.empty())
|
|
||||||
{
|
|
||||||
release(mFinishedUsages.front());
|
|
||||||
mFinishedUsages.erase(mFinishedUsages.begin());
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::add(int usageId, PWavFileReader file, bool loop, int timelength)
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
PlaylistItem item;
|
|
||||||
item.mFile = file;
|
|
||||||
item.mLoop = loop;
|
|
||||||
item.mTimelength = timelength;
|
|
||||||
item.mUsageId = usageId;
|
|
||||||
mPlaylist.push_back(item);
|
|
||||||
|
|
||||||
obtain(usageId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::clear()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
while (mPlaylist.size())
|
|
||||||
onFilePlayed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Player::retrieveUsageIds(std::vector<int>& ids)
|
|
||||||
{
|
|
||||||
ids.assign(mFinishedUsages.begin(), mFinishedUsages.end());
|
|
||||||
mFinishedUsages.clear();
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
/* Copyright(C) 2007-2021 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 __AUDIO_PLAYER_H
|
|
||||||
#define __AUDIO_PLAYER_H
|
|
||||||
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
#include "../helper/HL_Statistics.h"
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
#include <deque>
|
|
||||||
#include <map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class Player: public DataConnection
|
|
||||||
{
|
|
||||||
friend class DevicePair;
|
|
||||||
public:
|
|
||||||
struct PlaylistItem
|
|
||||||
{
|
|
||||||
PWavFileReader mFile;
|
|
||||||
bool mLoop;
|
|
||||||
int mTimelength;
|
|
||||||
int mUsageId;
|
|
||||||
};
|
|
||||||
typedef std::deque<PlaylistItem> Playlist;
|
|
||||||
|
|
||||||
class EndOfAudioDelegate
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void onFilePlayed(PlaylistItem& item) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
typedef std::map<int, int> UsageMap;
|
|
||||||
Audio::POutputDevice mOutput;
|
|
||||||
UsageMap mUsage; // References map
|
|
||||||
std::vector<int> mFinishedUsages; // Finished plays
|
|
||||||
|
|
||||||
Mutex mGuard;
|
|
||||||
Playlist mPlaylist;
|
|
||||||
float mPlayedTime;
|
|
||||||
EndOfAudioDelegate* mDelegate;
|
|
||||||
|
|
||||||
void onMicData(const Format& f, const void* buffer, int length);
|
|
||||||
void onSpkData(const Format& f, void* buffer, int length);
|
|
||||||
void onFilePlayed();
|
|
||||||
void obtain(int usageId);
|
|
||||||
|
|
||||||
public:
|
|
||||||
Player();
|
|
||||||
~Player();
|
|
||||||
|
|
||||||
void setDelegate(EndOfAudioDelegate* d);
|
|
||||||
EndOfAudioDelegate* getDelegate() const;
|
|
||||||
|
|
||||||
void setOutput(POutputDevice output);
|
|
||||||
POutputDevice getOutput() const;
|
|
||||||
|
|
||||||
void add(int usageId, PWavFileReader file, bool loop, int timelength);
|
|
||||||
void release(int usageId);
|
|
||||||
void clear();
|
|
||||||
int releasePlayed();
|
|
||||||
void retrieveUsageIds(std::vector<int>& ids);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "Audio_Quality.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
#include "../helper/HL_Types.h"
|
|
||||||
#include "speex/speex_preprocess.h"
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
# include <malloc.h>
|
|
||||||
#endif
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
#ifndef SHRT_MAX
|
|
||||||
# define SHRT_MAX 32767 /* maximum (signed) short value */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AgcFilter::AgcFilter(int channels)
|
|
||||||
{
|
|
||||||
static const float DefaultLevel = 0.8f;
|
|
||||||
|
|
||||||
for (int i=0; i<channels; i++)
|
|
||||||
{
|
|
||||||
Channel c;
|
|
||||||
float level = DefaultLevel;
|
|
||||||
c.mSampleMax = 1;
|
|
||||||
c.mCounter = 0;
|
|
||||||
c.mIgain = 65536;
|
|
||||||
if (level > 1.0f)
|
|
||||||
level = 1.0f;
|
|
||||||
else
|
|
||||||
if (level < 0.5f)
|
|
||||||
level = 0.5f;
|
|
||||||
|
|
||||||
c.mIpeak = (int)(SHRT_MAX * level * 65536);
|
|
||||||
|
|
||||||
c.mSilenceCounter = 0;
|
|
||||||
mChannelList.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AgcFilter::~AgcFilter()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgcFilter::process(void *pcm, int length)
|
|
||||||
{
|
|
||||||
for (size_t i=0; i<mChannelList.size(); i++)
|
|
||||||
processChannel((short*)pcm, length / (sizeof(short) * mChannelList.size()), i);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AgcFilter::processChannel(short* pcm, int nrOfSamples, int channelIndex)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for(i=0; i<nrOfSamples; i++)
|
|
||||||
{
|
|
||||||
long gain_new;
|
|
||||||
int sample;
|
|
||||||
int sampleIndex = mChannelList.size() * i + channelIndex;
|
|
||||||
Channel& channel = mChannelList[channelIndex];
|
|
||||||
|
|
||||||
/* get the abs of buffer[i] */
|
|
||||||
sample = pcm[sampleIndex];
|
|
||||||
sample = (sample < 0 ? -(sample):sample);
|
|
||||||
|
|
||||||
if(sample > (int)channel.mSampleMax)
|
|
||||||
{
|
|
||||||
/* update the max */
|
|
||||||
channel.mSampleMax = (unsigned int)sample;
|
|
||||||
}
|
|
||||||
channel.mCounter ++;
|
|
||||||
|
|
||||||
/* Will we get an overflow with the current gain factor? */
|
|
||||||
if (((sample * channel.mIgain) >> 16) > channel.mIpeak)
|
|
||||||
{
|
|
||||||
/* Yes: Calculate new gain. */
|
|
||||||
channel.mIgain = ((channel.mIpeak / channel.mSampleMax) * 62259) >> 16;
|
|
||||||
channel.mSilenceCounter = 0;
|
|
||||||
pcm[sampleIndex] = (short) ((pcm[sampleIndex] * channel.mIgain) >> 16);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate new gain factor 10x per second */
|
|
||||||
if (channel.mCounter >= AUDIO_SAMPLERATE / 10)
|
|
||||||
{
|
|
||||||
if (channel.mSampleMax > AUDIO_SAMPLERATE / 10) /* speaking? */
|
|
||||||
{
|
|
||||||
gain_new = ((channel.mIpeak / channel.mSampleMax) * 62259) >> 16;
|
|
||||||
|
|
||||||
if (channel.mSilenceCounter > 40) /* pause -> speaking */
|
|
||||||
channel.mIgain += (gain_new - channel.mIgain) >> 2;
|
|
||||||
else
|
|
||||||
channel.mIgain += (gain_new - channel.mIgain) / 20;
|
|
||||||
|
|
||||||
channel.mSilenceCounter = 0;
|
|
||||||
}
|
|
||||||
else /* silence */
|
|
||||||
{
|
|
||||||
channel.mSilenceCounter++;
|
|
||||||
/* silence > 2 seconds: reduce gain */
|
|
||||||
if ((channel.mIgain > 65536) && (channel.mSilenceCounter >= 20))
|
|
||||||
channel.mIgain = (channel.mIgain * 62259) >> 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.mCounter = 0;
|
|
||||||
channel.mSampleMax = 1;
|
|
||||||
}
|
|
||||||
pcm[sampleIndex] = (short) ((pcm[sampleIndex] * channel.mIgain) >> 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- AecFilter ---
|
|
||||||
#ifdef USE_SPEEX_AEC
|
|
||||||
# include "speex/speex_echo.h"
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
|
|
||||||
#if !defined(TARGET_WIN)
|
|
||||||
# include <alloca.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_AEC
|
|
||||||
# include "aec/echo_cancellation.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_AEC
|
|
||||||
static void CheckWRACode(unsigned errorcode)
|
|
||||||
{
|
|
||||||
if (errorcode)
|
|
||||||
throw Exception(ERR_WEBRTC, errorcode);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AecFilter::AecFilter(int tailTime, int frameTime, int rate)
|
|
||||||
:mCtx(nullptr), mFrameTime(frameTime), mRate(rate)
|
|
||||||
{
|
|
||||||
#ifdef USE_SPEEX_AEC
|
|
||||||
if (AUDIO_CHANNELS == 2)
|
|
||||||
mCtx = speex_echo_state_init_mc(frameTime * (mRate / 1000), tailTime * (mRate / 1000), AUDIO_CHANNELS, AUDIO_CHANNELS );
|
|
||||||
else
|
|
||||||
mCtx = speex_echo_state_init(frameTime * (mRate / 1000), tailTime * (mRate / 1000));
|
|
||||||
int tmp = rate;
|
|
||||||
speex_echo_ctl((SpeexEchoState*)mCtx, SPEEX_ECHO_SET_SAMPLING_RATE, &tmp);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_AEC
|
|
||||||
CheckWRACode(WebRtcAec_Create(&mCtx));
|
|
||||||
CheckWRACode(WebRtcAec_Init(mCtx, rate, rate));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
AecFilter::~AecFilter()
|
|
||||||
{
|
|
||||||
#ifdef USE_SPEEX_AEC
|
|
||||||
if (mCtx)
|
|
||||||
{
|
|
||||||
//speex_echo_state_destroy((SpeexEchoState*)mCtx);
|
|
||||||
mCtx = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_AEC
|
|
||||||
CheckWRACode(WebRtcAec_Free(mCtx));
|
|
||||||
mCtx = NULL;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void AecFilter::fromMic(void *data)
|
|
||||||
{
|
|
||||||
#ifdef USE_SPEEX_AEC
|
|
||||||
short* output = (short*)alloca(Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH));
|
|
||||||
speex_echo_capture((SpeexEchoState*)mCtx, (short*)data, (short*)output);
|
|
||||||
memmove(data, output, AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_AEC
|
|
||||||
short* inputframe = (short*)ALLOCA(framesize);
|
|
||||||
memcpy(inputframe, (char*)data+framesize*i, framesize);
|
|
||||||
CheckWRACode(WebRtcAec_Process(mCtx, (short*)inputframe, NULL, (short*)data+framesize/2*i, NULL, mFrameTime * mRate / 1000, 0,0));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void AecFilter::toSpeaker(void *data)
|
|
||||||
{
|
|
||||||
#ifdef USE_SPEEX_AEC
|
|
||||||
speex_echo_playback((SpeexEchoState*)mCtx, (short*)data);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_AEC
|
|
||||||
CheckWRACode(WebRtcAec_BufferFarend(mCtx, (short*)data, length / 2 / AUDIO_CHANNELS));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int AecFilter::frametime()
|
|
||||||
{
|
|
||||||
return mFrameTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
DenoiseFilter::DenoiseFilter(int rate)
|
|
||||||
:mRate(rate)
|
|
||||||
{
|
|
||||||
mCtx = speex_preprocess_state_init(mRate/100, mRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
DenoiseFilter::~DenoiseFilter()
|
|
||||||
{
|
|
||||||
if (mCtx)
|
|
||||||
speex_preprocess_state_destroy((SpeexPreprocessState*)mCtx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DenoiseFilter::fromMic(void* data, int timelength)
|
|
||||||
{
|
|
||||||
assert(timelength % 10 == 0);
|
|
||||||
|
|
||||||
// Process by 10-ms blocks
|
|
||||||
spx_int16_t* in = (spx_int16_t*)data;
|
|
||||||
|
|
||||||
for (int blockIndex=0; blockIndex<timelength/10; blockIndex++)
|
|
||||||
{
|
|
||||||
spx_int16_t* block = in + blockIndex * (mRate / 100) * AUDIO_CHANNELS;
|
|
||||||
speex_preprocess_run((SpeexPreprocessState*)mCtx, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int DenoiseFilter::rate()
|
|
||||||
{
|
|
||||||
return mRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __AUDIO_QUALITY_H
|
|
||||||
#define __AUDIO_QUALITY_H
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class AgcFilter
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
struct Channel
|
|
||||||
{
|
|
||||||
unsigned int mSampleMax;
|
|
||||||
int mCounter;
|
|
||||||
long mIgain;
|
|
||||||
int mIpeak;
|
|
||||||
int mSilenceCounter;
|
|
||||||
};
|
|
||||||
std::vector<Channel> mChannelList;
|
|
||||||
void processChannel(short* pcm, int nrOfSamples, int channelIndex);
|
|
||||||
public:
|
|
||||||
AgcFilter(int channels);
|
|
||||||
~AgcFilter();
|
|
||||||
|
|
||||||
void process(void* pcm, int length);
|
|
||||||
};
|
|
||||||
|
|
||||||
class AecFilter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AecFilter(int tailTime, int frameTime, int rate);
|
|
||||||
~AecFilter();
|
|
||||||
|
|
||||||
// These methods accept input block with timelength "frameTime" used in constructor.
|
|
||||||
void toSpeaker(void* data);
|
|
||||||
void fromMic(void* data);
|
|
||||||
int frametime();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void* mCtx; /// The echo canceller context's pointer.
|
|
||||||
Mutex mGuard; /// Mutex to protect this instance.
|
|
||||||
int mFrameTime; /// Duration of single audio frame (in milliseconds)
|
|
||||||
int mRate;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DenoiseFilter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DenoiseFilter(int rate);
|
|
||||||
~DenoiseFilter();
|
|
||||||
|
|
||||||
void fromMic(void* data, int timelength);
|
|
||||||
int rate();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Mutex mGuard; /// Mutex to protect this instance.
|
|
||||||
void* mCtx; /// The denoiser context pointer.
|
|
||||||
int mRate; /// Duration of single audio frame (in milliseconds)
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,286 +0,0 @@
|
||||||
/* Copyright(C) 2007-2018 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/. */
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <memory.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include "speex/speex_resampler.h"
|
|
||||||
|
|
||||||
#define IS_FRACTIONAL_RATE(X) (((X) % 8000) != 0)
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
SpeexResampler::SpeexResampler()
|
|
||||||
:mContext(NULL), mErrorCode(0), mSourceRate(0), mDestRate(0), mLastSample(0), mChannels(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpeexResampler::start(int channels, int sourceRate, int destRate)
|
|
||||||
{
|
|
||||||
if (mSourceRate == sourceRate && mDestRate == destRate && mContext)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (mContext)
|
|
||||||
stop();
|
|
||||||
|
|
||||||
mSourceRate = sourceRate;
|
|
||||||
mDestRate = destRate;
|
|
||||||
mChannels = channels;
|
|
||||||
|
|
||||||
if (sourceRate != destRate)
|
|
||||||
{
|
|
||||||
// Defer context creation until first request
|
|
||||||
//mContext = speex_resampler_init(channels, sourceRate, destRate, AUDIO_RESAMPLER_QUALITY, &mErrorCode);
|
|
||||||
//assert(mContext != NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpeexResampler::stop()
|
|
||||||
{
|
|
||||||
if (mContext)
|
|
||||||
{
|
|
||||||
speex_resampler_destroy((SpeexResamplerState*)mContext);
|
|
||||||
mContext = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SpeexResampler::~SpeexResampler()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SpeexResampler::processBuffer(const void* src, size_t sourceLength, size_t& sourceProcessed,
|
|
||||||
void* dest, size_t destCapacity)
|
|
||||||
{
|
|
||||||
assert(mSourceRate != 0 && mDestRate != 0);
|
|
||||||
|
|
||||||
if (mDestRate == mSourceRate)
|
|
||||||
{
|
|
||||||
assert(destCapacity >= sourceLength);
|
|
||||||
memcpy(dest, src, sourceLength);
|
|
||||||
sourceProcessed = sourceLength;
|
|
||||||
return sourceLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mContext)
|
|
||||||
{
|
|
||||||
mContext = speex_resampler_init(mChannels, mSourceRate, mDestRate,
|
|
||||||
AUDIO_RESAMPLER_QUALITY, &mErrorCode);
|
|
||||||
if (!mContext)
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there is zero samples passed
|
|
||||||
if (sourceLength / (sizeof(short) * mChannels) == 0)
|
|
||||||
{
|
|
||||||
// Consume all data
|
|
||||||
sourceProcessed = sourceLength;
|
|
||||||
|
|
||||||
// But no output
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t outLen = getDestLength(sourceLength);
|
|
||||||
if (outLen > destCapacity)
|
|
||||||
return 0; // Skip resampling if not enough space
|
|
||||||
|
|
||||||
assert(destCapacity >= outLen);
|
|
||||||
|
|
||||||
// Calculate number of samples - input length is in bytes
|
|
||||||
unsigned inLen = sourceLength / (sizeof(short) * mChannels);
|
|
||||||
outLen /= sizeof(short) * mChannels;
|
|
||||||
assert(mContext != NULL);
|
|
||||||
spx_uint32_t in_len = static_cast<spx_uint32_t>(inLen),
|
|
||||||
out_len = static_cast<spx_uint32_t>(outLen);
|
|
||||||
|
|
||||||
int speexCode = speex_resampler_process_interleaved_int((SpeexResamplerState *)mContext,
|
|
||||||
(spx_int16_t*)src, &in_len,
|
|
||||||
(spx_int16_t*)dest, &out_len);
|
|
||||||
assert(speexCode == RESAMPLER_ERR_SUCCESS);
|
|
||||||
|
|
||||||
inLen = static_cast<size_t>(in_len);
|
|
||||||
outLen = static_cast<size_t>(out_len);
|
|
||||||
|
|
||||||
// Return results in bytes
|
|
||||||
sourceProcessed = inLen * sizeof(short) * mChannels;
|
|
||||||
return outLen * sizeof(short) * mChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SpeexResampler::sourceRate()
|
|
||||||
{
|
|
||||||
return mSourceRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SpeexResampler::destRate()
|
|
||||||
{
|
|
||||||
return mDestRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SpeexResampler::getDestLength(size_t sourceLen)
|
|
||||||
{
|
|
||||||
return size_t(sourceLen * (float(mDestRate) / mSourceRate) + 0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t SpeexResampler::getSourceLength(size_t destLen)
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
size_t SpeexResampler::getSize() const
|
|
||||||
{
|
|
||||||
return sizeof(*this) + 200; // 200 is approximate size of speex resample structure
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------- ChannelConverter --------------------
|
|
||||||
int ChannelConverter::stereoToMono(const void *source, int sourceLength, void *dest, int destLength)
|
|
||||||
{
|
|
||||||
assert(destLength == sourceLength / 2);
|
|
||||||
const short* input = (const short*)source;
|
|
||||||
short* output = (short*)dest;
|
|
||||||
for (int sampleIndex = 0; sampleIndex < destLength/2; sampleIndex++)
|
|
||||||
{
|
|
||||||
output[sampleIndex] = (input[sampleIndex*2] + input[sampleIndex*2+1]) >> 1;
|
|
||||||
}
|
|
||||||
return sourceLength / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ChannelConverter::monoToStereo(const void *source, int sourceLength, void *dest, int destLength)
|
|
||||||
{
|
|
||||||
assert (destLength == sourceLength * 2);
|
|
||||||
const short* input = (const short*)source;
|
|
||||||
short* output = (short*)dest;
|
|
||||||
// Convert starting from the end of buffer to allow inplace conversion
|
|
||||||
for (int sampleIndex = sourceLength/2 - 1; sampleIndex >= 0; sampleIndex--)
|
|
||||||
{
|
|
||||||
output[2*sampleIndex] = output[2*sampleIndex+1] = input[sampleIndex];
|
|
||||||
}
|
|
||||||
return sourceLength * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(USE_WEBRTC_RESAMPLER)
|
|
||||||
Resampler48kTo16k::Resampler48kTo16k()
|
|
||||||
{
|
|
||||||
WebRtcSpl_ResetResample48khzTo16khz(&mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
Resampler48kTo16k::~Resampler48kTo16k()
|
|
||||||
{
|
|
||||||
WebRtcSpl_ResetResample48khzTo16khz(&mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Resampler48kTo16k::process(const void *source, int sourceLen, void *dest, int destLen)
|
|
||||||
{
|
|
||||||
const short* input = (const short*)source; int inputLen = sourceLen / 2;
|
|
||||||
short* output = (short*)dest; //int outputCapacity = destLen / 2;
|
|
||||||
assert(inputLen % 480 == 0);
|
|
||||||
int frames = inputLen / 480;
|
|
||||||
for (int i=0; i<frames; i++)
|
|
||||||
WebRtcSpl_Resample48khzTo16khz(input + i * 480, output + i * 160, &mContext, mTemp);
|
|
||||||
|
|
||||||
return sourceLen / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Resampler16kto48k::Resampler16kto48k()
|
|
||||||
{
|
|
||||||
WebRtcSpl_ResetResample16khzTo48khz(&mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
Resampler16kto48k::~Resampler16kto48k()
|
|
||||||
{
|
|
||||||
WebRtcSpl_ResetResample16khzTo48khz(&mContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Resampler16kto48k::process(const void *source, int sourceLen, void *dest, int destLen)
|
|
||||||
{
|
|
||||||
const WebRtc_Word16* input = (const WebRtc_Word16*)source; int inputLen = sourceLen / 2;
|
|
||||||
WebRtc_Word16* output = (WebRtc_Word16*)dest; //int outputCapacity = destLen / 2;
|
|
||||||
assert(inputLen % 160 == 0);
|
|
||||||
int frames = inputLen / 160;
|
|
||||||
for (int i=0; i<frames; i++)
|
|
||||||
WebRtcSpl_Resample16khzTo48khz(input + i * 160, output + i * 480, &mContext, mTemp);
|
|
||||||
|
|
||||||
return sourceLen * 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// ---------------- UniversalResampler -------------------
|
|
||||||
UniversalResampler::UniversalResampler()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
UniversalResampler::~UniversalResampler()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t UniversalResampler::resample(int sourceRate, const void *sourceBuffer, size_t sourceLength,
|
|
||||||
size_t& sourceProcessed, int destRate, void *destBuffer, size_t destCapacity)
|
|
||||||
{
|
|
||||||
assert(destBuffer && sourceBuffer);
|
|
||||||
size_t result;
|
|
||||||
if (sourceRate == destRate)
|
|
||||||
{
|
|
||||||
assert(destCapacity >= sourceLength);
|
|
||||||
memcpy(destBuffer, sourceBuffer, sourceLength);
|
|
||||||
sourceProcessed = sourceLength;
|
|
||||||
result = sourceLength;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PResampler r = findResampler(sourceRate, destRate);
|
|
||||||
result = r->processBuffer(sourceBuffer, sourceLength, sourceProcessed, destBuffer, destCapacity);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UniversalResampler::preload()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t UniversalResampler::getDestLength(int sourceRate, int destRate, size_t sourceLength)
|
|
||||||
{
|
|
||||||
if (sourceRate == destRate)
|
|
||||||
return sourceLength;
|
|
||||||
else
|
|
||||||
return findResampler(sourceRate, destRate)->getDestLength(sourceLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t UniversalResampler::getSourceLength(int sourceRate, int destRate, size_t destLength)
|
|
||||||
{
|
|
||||||
if (sourceRate == destRate)
|
|
||||||
return destLength;
|
|
||||||
else
|
|
||||||
return findResampler(sourceRate, destRate)->getSourceLength(destLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
PResampler UniversalResampler::findResampler(int sourceRate, int destRate)
|
|
||||||
{
|
|
||||||
assert(sourceRate != destRate);
|
|
||||||
ResamplerMap::iterator resamplerIter = mResamplerMap.find(RatePair(sourceRate, destRate));
|
|
||||||
PResampler r;
|
|
||||||
if (resamplerIter == mResamplerMap.end())
|
|
||||||
{
|
|
||||||
r = std::make_shared<Resampler>();
|
|
||||||
r->start(AUDIO_CHANNELS, sourceRate, destRate);
|
|
||||||
mResamplerMap[RatePair(sourceRate, destRate)] = r;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
r = resamplerIter->second;
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end of namespace
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
/* Copyright(C) 2007-2018 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 __AUDIO_RESAMPLER_H
|
|
||||||
#define __AUDIO_RESAMPLER_H
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_RESAMPLER
|
|
||||||
# include "signal_processing_library/signal_processing_library.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
class SpeexResampler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SpeexResampler();
|
|
||||||
~SpeexResampler();
|
|
||||||
|
|
||||||
void start(int channels, int sourceRate, int destRate);
|
|
||||||
void stop();
|
|
||||||
size_t processBuffer(const void* source, size_t sourceLength, size_t& sourceProcessed,
|
|
||||||
void* dest, size_t destCapacity);
|
|
||||||
int sourceRate();
|
|
||||||
int destRate();
|
|
||||||
size_t getDestLength(size_t sourceLen);
|
|
||||||
size_t getSourceLength(size_t destLen);
|
|
||||||
|
|
||||||
// Returns instance + speex encoder size in bytes
|
|
||||||
size_t getSize() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void* mContext;
|
|
||||||
int mErrorCode;
|
|
||||||
int mSourceRate,
|
|
||||||
mDestRate,
|
|
||||||
mChannels;
|
|
||||||
short mLastSample;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef SpeexResampler Resampler;
|
|
||||||
typedef std::shared_ptr<Resampler> PResampler;
|
|
||||||
|
|
||||||
class ChannelConverter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static int stereoToMono(const void* source, int sourceLength, void* dest, int destLength);
|
|
||||||
static int monoToStereo(const void* source, int sourceLength, void* dest, int destLength);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Operates with AUDIO_CHANNELS number of channels
|
|
||||||
class UniversalResampler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
UniversalResampler();
|
|
||||||
~UniversalResampler();
|
|
||||||
|
|
||||||
size_t resample(int sourceRate, const void* sourceBuffer, size_t sourceLength, size_t& sourceProcessed,
|
|
||||||
int destRate, void* destBuffer, size_t destCapacity);
|
|
||||||
size_t getDestLength(int sourceRate, int destRate, size_t sourceLength);
|
|
||||||
size_t getSourceLength(int sourceRate, int destRate, size_t destLength);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
typedef std::pair<int, int> RatePair;
|
|
||||||
typedef std::map<RatePair, PResampler> ResamplerMap;
|
|
||||||
ResamplerMap mResamplerMap;
|
|
||||||
PResampler findResampler(int sourceRate, int destRate);
|
|
||||||
|
|
||||||
void preload();
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef USE_WEBRTC_RESAMPLER
|
|
||||||
// n*10 milliseconds buffers required!
|
|
||||||
class Resampler48kTo16k
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Resampler48kTo16k();
|
|
||||||
~Resampler48kTo16k();
|
|
||||||
int process(const void* source, int sourceLen, void* dest, int destLen);
|
|
||||||
protected:
|
|
||||||
WebRtc_Word32 mTemp[496];
|
|
||||||
WebRtcSpl_State48khzTo16khz mContext;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Resampler16kto48k
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Resampler16kto48k();
|
|
||||||
~Resampler16kto48k();
|
|
||||||
int process(const void* source, int sourceLen, void* dest, int destLen);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
WebRtc_Word32 mTemp[336];
|
|
||||||
WebRtcSpl_State16khzTo48khz mContext;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
} // end of namespace
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,431 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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/. */
|
|
||||||
|
|
||||||
#include "Audio_WavFile.h"
|
|
||||||
#include "helper/HL_Exception.h"
|
|
||||||
#include "helper/HL_String.h"
|
|
||||||
#include "helper/HL_Log.h"
|
|
||||||
#include "../engine_config.h"
|
|
||||||
|
|
||||||
#include <memory.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#ifndef WORD
|
|
||||||
# define WORD unsigned short
|
|
||||||
#endif
|
|
||||||
#ifndef DWORD
|
|
||||||
# define DWORD unsigned int
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
WORD wFormatTag;
|
|
||||||
WORD nChannels;
|
|
||||||
DWORD nSamplesPerSec;
|
|
||||||
DWORD nAvgBytesPerSec;
|
|
||||||
WORD nBlockAlign;
|
|
||||||
WORD wBitsPerSample;
|
|
||||||
WORD cbSize;
|
|
||||||
}
|
|
||||||
WaveFormatEx;
|
|
||||||
|
|
||||||
#define WAVE_FORMAT_PCM 1
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "WavFileReader"
|
|
||||||
|
|
||||||
#define LOCK std::unique_lock<std::recursive_mutex> lock(mFileMtx);
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
// ---------------------- WavFileReader -------------------------
|
|
||||||
WavFileReader::WavFileReader()
|
|
||||||
:mSamplerate(0), mLastError(0), mChannels(0), mBits(0), mDataLength(0)
|
|
||||||
{
|
|
||||||
mDataOffset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
WavFileReader::~WavFileReader()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#define THROW_READERROR throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
std::string WavFileReader::readChunk()
|
|
||||||
{
|
|
||||||
char name[5] = {0};
|
|
||||||
readBuffer(name, 4);
|
|
||||||
|
|
||||||
std::string result = name;
|
|
||||||
uint32_t size = 0;
|
|
||||||
readBuffer(&size, 4);
|
|
||||||
|
|
||||||
if (result == "fact")
|
|
||||||
{
|
|
||||||
uint32_t dataLength = 0;
|
|
||||||
readBuffer(&dataLength, sizeof dataLength);
|
|
||||||
mDataLength = dataLength;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (result != "data")
|
|
||||||
mInput->seekg(size, std::ios_base::beg);
|
|
||||||
else
|
|
||||||
mDataLength = size;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mPath = p;
|
|
||||||
mInput = std::make_unique<std::ifstream>(p, std::ios::binary | std::ios::in);
|
|
||||||
if (!mInput->is_open())
|
|
||||||
{
|
|
||||||
#if defined(TARGET_ANDROID) || defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
mLastError = errno;
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
mLastError = GetLastError();
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mLastError = 0;
|
|
||||||
|
|
||||||
// Read the .WAV header
|
|
||||||
char riff[4];
|
|
||||||
readBuffer(riff, sizeof riff);
|
|
||||||
|
|
||||||
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
|
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
// Read the file size
|
|
||||||
uint32_t filesize = 0;
|
|
||||||
readBuffer(&filesize, sizeof(filesize));
|
|
||||||
|
|
||||||
char wavefmt[9] = {0};
|
|
||||||
readBuffer(wavefmt, 8);
|
|
||||||
if (strcmp(wavefmt, "WAVEfmt ") != 0)
|
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
uint32_t fmtSize = 0;
|
|
||||||
readBuffer(&fmtSize, sizeof(fmtSize));
|
|
||||||
|
|
||||||
auto fmtStart = mInput->tellg();
|
|
||||||
|
|
||||||
uint16_t formattag = 0;
|
|
||||||
readBuffer(&formattag, sizeof(formattag));
|
|
||||||
|
|
||||||
if (formattag != 1/*WAVE_FORMAT_PCM*/)
|
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
mChannels = 0;
|
|
||||||
readBuffer(&mChannels, sizeof(mChannels));
|
|
||||||
|
|
||||||
mSamplerate = 0;
|
|
||||||
readBuffer(&mSamplerate, sizeof(mSamplerate));
|
|
||||||
|
|
||||||
uint32_t avgbytespersec = 0;
|
|
||||||
readBuffer(&avgbytespersec, sizeof(avgbytespersec));
|
|
||||||
|
|
||||||
uint16_t blockalign = 0;
|
|
||||||
readBuffer(&blockalign, sizeof(blockalign));
|
|
||||||
|
|
||||||
mBits = 0;
|
|
||||||
readBuffer(&mBits, sizeof(mBits));
|
|
||||||
|
|
||||||
if (mBits !=8 && mBits != 16)
|
|
||||||
THROW_READERROR;
|
|
||||||
|
|
||||||
// Look for the chunk 'data'
|
|
||||||
mInput->seekg(fmtStart + std::streampos(fmtSize));
|
|
||||||
|
|
||||||
mDataLength = 0;
|
|
||||||
while (readChunk() != "data")
|
|
||||||
;
|
|
||||||
|
|
||||||
mDataOffset = mInput->tellg();
|
|
||||||
mResampler.start(AUDIO_CHANNELS, mSamplerate, AUDIO_SAMPLERATE);
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{
|
|
||||||
mInput.reset();
|
|
||||||
mLastError = static_cast<unsigned>(-1);
|
|
||||||
}
|
|
||||||
return isOpened();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WavFileReader::close()
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
mInput.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
int WavFileReader::samplerate() const
|
|
||||||
{
|
|
||||||
return mSamplerate;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WavFileReader::channels() const
|
|
||||||
{
|
|
||||||
return mChannels;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavFileReader::read(void* buffer, size_t bytes)
|
|
||||||
{
|
|
||||||
return read((short*)buffer, bytes / (AUDIO_CHANNELS * 2)) * AUDIO_CHANNELS * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavFileReader::readRaw(void* buffer, size_t bytes)
|
|
||||||
{
|
|
||||||
return readRaw((short*)buffer, bytes / channels() / sizeof(short)) * channels() * sizeof(short);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavFileReader::read(short* buffer, size_t samples)
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
|
|
||||||
if (!mInput)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Get number of samples that must be read from source file
|
|
||||||
size_t requiredBytes = mResampler.getSourceLength(samples) * mChannels * mBits / 8;
|
|
||||||
bool useHeap = requiredBytes > sizeof mTempBuffer;
|
|
||||||
void* temp;
|
|
||||||
if (useHeap)
|
|
||||||
temp = malloc(requiredBytes);
|
|
||||||
else
|
|
||||||
temp = mTempBuffer;
|
|
||||||
|
|
||||||
memset(temp, 0, requiredBytes);
|
|
||||||
|
|
||||||
// Find required size of input buffer
|
|
||||||
if (mDataLength)
|
|
||||||
{
|
|
||||||
auto filePosition = mInput->tellg();
|
|
||||||
|
|
||||||
// Check how much data we can read
|
|
||||||
size_t fileAvailable = mDataLength + mDataOffset - filePosition;
|
|
||||||
requiredBytes = fileAvailable < requiredBytes ? fileAvailable : requiredBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t readBytes = tryReadBuffer(temp, requiredBytes);
|
|
||||||
|
|
||||||
size_t processedBytes = 0;
|
|
||||||
size_t result = mResampler.processBuffer(temp, readBytes, processedBytes,
|
|
||||||
buffer, samples * 2 * AUDIO_CHANNELS);
|
|
||||||
|
|
||||||
if (useHeap)
|
|
||||||
free(temp);
|
|
||||||
return result / 2 / AUDIO_CHANNELS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t WavFileReader::readRaw(short* buffer, size_t samples)
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
|
|
||||||
if (!mInput)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Get number of samples that must be read from source file
|
|
||||||
size_t requiredBytes = samples * channels() * sizeof(short);
|
|
||||||
|
|
||||||
// Find required size of input buffer
|
|
||||||
if (mDataLength)
|
|
||||||
{
|
|
||||||
auto filePosition = mInput->tellg();
|
|
||||||
|
|
||||||
// Check how much data we can read
|
|
||||||
size_t fileAvailable = mDataLength + mDataOffset - filePosition;
|
|
||||||
requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t readBytes = tryReadBuffer(buffer, requiredBytes);
|
|
||||||
return readBytes / channels() / sizeof(short);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WavFileReader::isOpened()
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
if (!mInput)
|
|
||||||
return false;
|
|
||||||
return mInput->is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WavFileReader::rewind()
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
if (mInput)
|
|
||||||
mInput->seekg(mDataOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path WavFileReader::path() const
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
return mPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavFileReader::size() const
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
return mDataLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned WavFileReader::lastError() const
|
|
||||||
{
|
|
||||||
return mLastError;
|
|
||||||
}
|
|
||||||
// ------------------------- WavFileWriter -------------------------
|
|
||||||
#define LOG_SUBSYTEM "WavFileWriter"
|
|
||||||
|
|
||||||
#define BITS_PER_CHANNEL 16
|
|
||||||
|
|
||||||
WavFileWriter::WavFileWriter()
|
|
||||||
:mLengthOffset(0), mSamplerate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
WavFileWriter::~WavFileWriter()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WavFileWriter::checkWriteResult(int result)
|
|
||||||
{
|
|
||||||
if (result < 1)
|
|
||||||
throw Exception(ERR_WAVFILE_FAILED, errno);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
close();
|
|
||||||
mSamplerate = samplerate;
|
|
||||||
mChannels = channels;
|
|
||||||
|
|
||||||
mOutput = std::make_unique<std::ofstream>(p, std::ios::binary | std::ios::trunc);
|
|
||||||
if (!mOutput)
|
|
||||||
{
|
|
||||||
int errorcode = errno;
|
|
||||||
ICELogError(<< "Failed to create .wav file: filename = " << p << " , error = " << errorcode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the .WAV header
|
|
||||||
const char* riff = "RIFF";
|
|
||||||
writeBuffer(riff, 4);
|
|
||||||
|
|
||||||
// Write the file size
|
|
||||||
uint32_t filesize = 0;
|
|
||||||
writeBuffer(&filesize, sizeof filesize);
|
|
||||||
|
|
||||||
const char* wavefmt = "WAVEfmt ";
|
|
||||||
writeBuffer(wavefmt, 8);
|
|
||||||
|
|
||||||
// Set the format description
|
|
||||||
uint32_t dwFmtSize = 16; /*= 16L*/;
|
|
||||||
writeBuffer(&dwFmtSize, sizeof(dwFmtSize));
|
|
||||||
|
|
||||||
WaveFormatEx format;
|
|
||||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
|
||||||
writeBuffer(&format.wFormatTag, sizeof(format.wFormatTag));
|
|
||||||
|
|
||||||
format.nChannels = mChannels;
|
|
||||||
writeBuffer(&format.nChannels, sizeof(format.nChannels));
|
|
||||||
|
|
||||||
format.nSamplesPerSec = mSamplerate;
|
|
||||||
writeBuffer(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec));
|
|
||||||
|
|
||||||
format.nAvgBytesPerSec = mSamplerate * 2 * mChannels;
|
|
||||||
writeBuffer(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec));
|
|
||||||
|
|
||||||
format.nBlockAlign = 2 * mChannels;
|
|
||||||
writeBuffer(&format.nBlockAlign, sizeof(format.nBlockAlign));
|
|
||||||
|
|
||||||
format.wBitsPerSample = BITS_PER_CHANNEL;
|
|
||||||
writeBuffer(&format.wBitsPerSample, sizeof(format.wBitsPerSample));
|
|
||||||
|
|
||||||
const char* data = "data";
|
|
||||||
writeBuffer(data, 4);
|
|
||||||
|
|
||||||
mPath = p;
|
|
||||||
mWritten = 0;
|
|
||||||
|
|
||||||
mLengthOffset = mOutput->tellp();
|
|
||||||
writeBuffer(&mWritten, sizeof mWritten);
|
|
||||||
|
|
||||||
return isOpened();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WavFileWriter::close()
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
mOutput.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t WavFileWriter::write(const void* buffer, size_t bytes)
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
|
|
||||||
if (!mOutput)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Seek the end of file - here new data will be written
|
|
||||||
mOutput->seekp(0, std::ios_base::end);
|
|
||||||
mWritten += bytes;
|
|
||||||
|
|
||||||
// Write the data
|
|
||||||
writeBuffer(buffer, bytes);
|
|
||||||
|
|
||||||
// Write file length
|
|
||||||
mOutput->seekp(4, std::ios_base::beg);
|
|
||||||
uint32_t fl = mWritten + 36;
|
|
||||||
writeBuffer(&fl, sizeof(fl));
|
|
||||||
|
|
||||||
// Write data length
|
|
||||||
mOutput->seekp(mLengthOffset, std::ios_base::beg);
|
|
||||||
writeBuffer(&mWritten, sizeof(mWritten));
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WavFileWriter::isOpened() const
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
return mOutput.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::filesystem::path WavFileWriter::path() const
|
|
||||||
{
|
|
||||||
LOCK;
|
|
||||||
return mPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
/* Copyright(C) 2007-2018 VoIPobjects (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 __AUDIO_WAVFILE_H
|
|
||||||
#define __AUDIO_WAVFILE_H
|
|
||||||
|
|
||||||
#include "helper/HL_Types.h"
|
|
||||||
#include "Audio_Resampler.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
|
|
||||||
class WavFileReader
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
uint16_t mChannels = 0;
|
|
||||||
uint16_t mBits = 0;
|
|
||||||
int mSamplerate = 0;
|
|
||||||
std::filesystem::path mPath;
|
|
||||||
mutable std::recursive_mutex mFileMtx;
|
|
||||||
size_t mDataOffset = 0;
|
|
||||||
size_t mDataLength = 0;
|
|
||||||
Resampler mResampler;
|
|
||||||
unsigned mLastError = 0;
|
|
||||||
std::unique_ptr<std::ifstream> mInput;
|
|
||||||
uint8_t mTempBuffer[16384];
|
|
||||||
|
|
||||||
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:
|
|
||||||
WavFileReader();
|
|
||||||
~WavFileReader();
|
|
||||||
|
|
||||||
bool open(const std::filesystem::path& p);
|
|
||||||
void close();
|
|
||||||
bool isOpened();
|
|
||||||
void rewind();
|
|
||||||
int samplerate() const;
|
|
||||||
int channels() const;
|
|
||||||
|
|
||||||
// This method returns number of read bytes
|
|
||||||
size_t read(void* buffer, size_t bytes);
|
|
||||||
size_t readRaw(void* buffer, size_t bytes);
|
|
||||||
|
|
||||||
// This method returns number of read samples
|
|
||||||
size_t read(short* buffer, size_t samples);
|
|
||||||
size_t readRaw(short* buffer, size_t samples);
|
|
||||||
|
|
||||||
std::filesystem::path path() const;
|
|
||||||
size_t size() const;
|
|
||||||
|
|
||||||
unsigned lastError() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<WavFileReader> PWavFileReader;
|
|
||||||
|
|
||||||
class WavFileWriter
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
std::unique_ptr<std::ofstream> mOutput; /// Handle of audio file.
|
|
||||||
std::filesystem::path mPath; /// Path to requested audio file.
|
|
||||||
mutable std::recursive_mutex mFileMtx; /// Mutex to protect this instance.
|
|
||||||
size_t mWritten = 0; /// Amount of written data (in bytes)
|
|
||||||
size_t mLengthOffset = 0; /// Position of length field.
|
|
||||||
int mSamplerate = 0,
|
|
||||||
mChannels = 0;
|
|
||||||
|
|
||||||
void checkWriteResult(int result);
|
|
||||||
void writeBuffer(const void* buffer, size_t sz);
|
|
||||||
public:
|
|
||||||
WavFileWriter();
|
|
||||||
~WavFileWriter();
|
|
||||||
|
|
||||||
bool open(const std::filesystem::path& p, int samplerate, int channels);
|
|
||||||
void close();
|
|
||||||
bool isOpened() const;
|
|
||||||
size_t write(const void* buffer, size_t bytes);
|
|
||||||
std::filesystem::path path() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<WavFileWriter> PWavFileWriter;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,555 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
|
|
||||||
#include "Audio_Wmme.h"
|
|
||||||
#include "Audio_Helper.h"
|
|
||||||
#include "../Helper/HL_Exception.h"
|
|
||||||
|
|
||||||
#include <process.h>
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
|
|
||||||
WmmeInputDevice::Buffer::Buffer()
|
|
||||||
{
|
|
||||||
// Do not use WAVEHDR allocated on stack!
|
|
||||||
mHeaderHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof WAVEHDR);
|
|
||||||
if (!mHeaderHandle)
|
|
||||||
throw Exception(ERR_WMME_FAILED, GetLastError());
|
|
||||||
mHeader = (WAVEHDR*)GlobalLock(mHeaderHandle);
|
|
||||||
|
|
||||||
mDataHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
if (!mDataHandle)
|
|
||||||
throw Exception(ERR_WMME_FAILED, GetLastError());
|
|
||||||
mData = GlobalLock(mDataHandle);
|
|
||||||
|
|
||||||
memset(mHeader, 0, sizeof *mHeader);
|
|
||||||
mHeader->dwBufferLength = AUDIO_MIC_BUFFER_SIZE;
|
|
||||||
mHeader->dwFlags = 0;
|
|
||||||
mHeader->lpData = (LPSTR)mData;
|
|
||||||
}
|
|
||||||
|
|
||||||
WmmeInputDevice::Buffer::~Buffer()
|
|
||||||
{
|
|
||||||
if (mDataHandle)
|
|
||||||
{
|
|
||||||
GlobalUnlock(mDataHandle);
|
|
||||||
GlobalFree(mDataHandle);
|
|
||||||
}
|
|
||||||
if (mHeaderHandle)
|
|
||||||
{
|
|
||||||
GlobalUnlock(mHeaderHandle);
|
|
||||||
GlobalFree(mHeaderHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::Buffer::prepare(HWAVEIN device)
|
|
||||||
{
|
|
||||||
MMRESULT resCode = MMSYSERR_NOERROR;
|
|
||||||
mHeader->dwFlags = 0;
|
|
||||||
mHeader->dwBufferLength = AUDIO_MIC_BUFFER_SIZE;
|
|
||||||
mHeader->lpData = (LPSTR)mData;
|
|
||||||
|
|
||||||
resCode = waveInPrepareHeader(device, mHeader, sizeof *mHeader);
|
|
||||||
//if (resCode != MMSYSERR_NOERROR)
|
|
||||||
// LogCritical("Audio", << "Failed to prepare source header. Error code " << resCode << ".");
|
|
||||||
|
|
||||||
return resCode == MMSYSERR_NOERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::Buffer::unprepare(HWAVEIN device)
|
|
||||||
{
|
|
||||||
if (mHeader->dwFlags & WHDR_PREPARED)
|
|
||||||
{
|
|
||||||
MMRESULT resCode = waveInUnprepareHeader(device, mHeader, sizeof *mHeader);
|
|
||||||
//if (resCode != MMSYSERR_NOERROR)
|
|
||||||
// LogCritical("Audio", << "Failed to unprepare source header. Error code " << resCode << ".");
|
|
||||||
return resCode == MMSYSERR_NOERROR;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::Buffer::isFinished()
|
|
||||||
{
|
|
||||||
return (mHeader->dwFlags & WHDR_DONE) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::Buffer::addToDevice(HWAVEIN device)
|
|
||||||
{
|
|
||||||
MMRESULT resCode = waveInAddBuffer(device, mHeader, sizeof(*mHeader));
|
|
||||||
//if (resCode != MMSYSERR_NOERROR)
|
|
||||||
// LogCritical("Audio", << "Failed to add buffer to source audio device. Error code is " << resCode << ".");
|
|
||||||
return resCode == MMSYSERR_NOERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* WmmeInputDevice::Buffer::data()
|
|
||||||
{
|
|
||||||
return mData;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
WmmeInputDevice::WmmeInputDevice(int deviceId)
|
|
||||||
:mDevHandle(NULL), mDoneSignal(INVALID_HANDLE_VALUE), mFakeMode(false),
|
|
||||||
mBufferIndex(0), mDeviceIndex(deviceId), mThreadHandle(0)
|
|
||||||
{
|
|
||||||
mDoneSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
mShutdownSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
mRefCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
WmmeInputDevice::~WmmeInputDevice()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
::CloseHandle(mDoneSignal);
|
|
||||||
::CloseHandle(mShutdownSignal);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::fakeMode()
|
|
||||||
{
|
|
||||||
return mFakeMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CALLBACK WmmeInputDevice::callbackProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
|
|
||||||
{
|
|
||||||
WmmeInputDevice* impl;
|
|
||||||
switch(uMsg)
|
|
||||||
{
|
|
||||||
case WIM_DATA:
|
|
||||||
impl = (WmmeInputDevice*)dwInstance;
|
|
||||||
SetEvent(impl->mDoneSignal);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WIM_CLOSE:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WIM_OPEN:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeInputDevice::openDevice()
|
|
||||||
{
|
|
||||||
// Build WAVEFORMATEX structure
|
|
||||||
WAVEFORMATEX wfx;
|
|
||||||
memset(&wfx, 0, sizeof(wfx));
|
|
||||||
|
|
||||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
|
||||||
wfx.nChannels = AUDIO_CHANNELS;
|
|
||||||
wfx.nSamplesPerSec = AUDIO_SAMPLERATE;
|
|
||||||
wfx.wBitsPerSample = 16;
|
|
||||||
wfx.cbSize = 0;
|
|
||||||
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
|
|
||||||
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
|
|
||||||
|
|
||||||
// Open wavein
|
|
||||||
|
|
||||||
MMRESULT mmres = waveInOpen(&mDevHandle, mDeviceIndex, &wfx, (DWORD_PTR)callbackProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
|
||||||
if (mmres != MMSYSERR_NOERROR)
|
|
||||||
{
|
|
||||||
mFakeMode = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mFakeMode = false;
|
|
||||||
|
|
||||||
// Create the buffers for running
|
|
||||||
mBufferIndex = 0;
|
|
||||||
for (int i=0; i<AUDIO_MIC_BUFFER_COUNT; i++)
|
|
||||||
mBufferList[i].prepare(mDevHandle);
|
|
||||||
|
|
||||||
for (int i=0; i<AUDIO_MIC_BUFFER_COUNT; i++)
|
|
||||||
mBufferList[i].addToDevice(mDevHandle);
|
|
||||||
|
|
||||||
/*mmres = */waveInStart(mDevHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::open()
|
|
||||||
{
|
|
||||||
Lock lock(mGuard);
|
|
||||||
|
|
||||||
mRefCount++;
|
|
||||||
if (mRefCount > 1)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
mThreadHandle = (HANDLE)_beginthread(&threadProc, 0, this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeInputDevice::closeDevice()
|
|
||||||
{
|
|
||||||
// Stop device
|
|
||||||
if (mDevHandle)
|
|
||||||
{
|
|
||||||
MMRESULT mmres = MMSYSERR_NOERROR;
|
|
||||||
waveInReset(mDevHandle);
|
|
||||||
waveInStop(mDevHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close buffers
|
|
||||||
for (int i=0; i<AUDIO_MIC_BUFFER_COUNT; i++)
|
|
||||||
mBufferList[i].unprepare(mDevHandle);
|
|
||||||
|
|
||||||
// Close device
|
|
||||||
if (mDevHandle)
|
|
||||||
{
|
|
||||||
waveInClose(mDevHandle);
|
|
||||||
mDevHandle = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeInputDevice::close()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
|
|
||||||
mRefCount--;
|
|
||||||
if (mRefCount != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Set shutdown signal
|
|
||||||
if (!mThreadHandle)
|
|
||||||
return;
|
|
||||||
|
|
||||||
::SetEvent(mShutdownSignal);
|
|
||||||
::WaitForSingleObject(mThreadHandle, INFINITE);
|
|
||||||
mThreadHandle = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeInputDevice::tryReadBuffer(void* buffer)
|
|
||||||
{
|
|
||||||
Buffer& devBuffer = mBufferList[mBufferIndex];
|
|
||||||
|
|
||||||
if (!devBuffer.isFinished())
|
|
||||||
return false;
|
|
||||||
memcpy(buffer, devBuffer.data(), AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
devBuffer.unprepare(mDevHandle);
|
|
||||||
devBuffer.prepare(mDevHandle);
|
|
||||||
if (!devBuffer.addToDevice(mDevHandle))
|
|
||||||
setFakeMode(true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
}
|
|
||||||
mBufferIndex = (mBufferIndex + 1) % AUDIO_MIC_BUFFER_COUNT;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeInputDevice::setFakeMode(bool fakeMode)
|
|
||||||
{
|
|
||||||
mFakeMode = fakeMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int WmmeInputDevice::readBuffer(void* buffer)
|
|
||||||
{
|
|
||||||
//Lock lock(mGuard);
|
|
||||||
|
|
||||||
if (mRefCount <= 0 || mFakeMode)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Check for finished buffer
|
|
||||||
while (!tryReadBuffer(buffer))
|
|
||||||
WaitForSingleObject(mDoneSignal, 50);
|
|
||||||
|
|
||||||
return AUDIO_MIC_BUFFER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
HWAVEIN WmmeInputDevice::handle()
|
|
||||||
{
|
|
||||||
Lock lock(mGuard);
|
|
||||||
return mDevHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeInputDevice::threadProc(void* arg)
|
|
||||||
{
|
|
||||||
WmmeInputDevice* impl = (WmmeInputDevice*)arg;
|
|
||||||
impl->openDevice();
|
|
||||||
void* buffer = _alloca(AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
|
|
||||||
DWORD waitResult = 0;
|
|
||||||
HANDLE waitArray[2] = {impl->mDoneSignal, impl->mShutdownSignal};
|
|
||||||
DWORD wr;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
wr = ::WaitForMultipleObjects(2, waitArray, FALSE, INFINITE);
|
|
||||||
|
|
||||||
if (wr == WAIT_OBJECT_0)
|
|
||||||
{
|
|
||||||
impl->readBuffer(buffer);
|
|
||||||
if (impl->connection())
|
|
||||||
impl->connection()->onMicData(Format(), buffer, AUDIO_MIC_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
} while (wr == WAIT_OBJECT_0);
|
|
||||||
|
|
||||||
impl->closeDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- WmmeOutputDevice ---
|
|
||||||
WmmeOutputDevice::Buffer::Buffer()
|
|
||||||
:mHeaderHandle(NULL), mDataHandle(NULL), mData(NULL), mHeader(NULL)
|
|
||||||
{
|
|
||||||
mHeaderHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
if (!mHeaderHandle)
|
|
||||||
throw Exception(ERR_NOMEM);
|
|
||||||
|
|
||||||
mDataHandle = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
if (!mDataHandle)
|
|
||||||
throw Exception(ERR_NOMEM);
|
|
||||||
|
|
||||||
mHeader = (WAVEHDR*)GlobalLock(mHeaderHandle);
|
|
||||||
mData = GlobalLock(mDataHandle);
|
|
||||||
memset(mHeader, 0, sizeof *mHeader);
|
|
||||||
mHeader->dwBufferLength = AUDIO_SPK_BUFFER_SIZE;
|
|
||||||
mHeader->lpData = (LPSTR)mData;
|
|
||||||
}
|
|
||||||
|
|
||||||
WmmeOutputDevice::Buffer::~Buffer()
|
|
||||||
{
|
|
||||||
if (mHeaderHandle)
|
|
||||||
{
|
|
||||||
GlobalUnlock(mHeaderHandle);
|
|
||||||
GlobalFree(mHeaderHandle);
|
|
||||||
}
|
|
||||||
if (mDataHandle)
|
|
||||||
{
|
|
||||||
GlobalUnlock(mDataHandle);
|
|
||||||
GlobalFree(mDataHandle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::Buffer::prepare(HWAVEOUT device)
|
|
||||||
{
|
|
||||||
MMRESULT result;
|
|
||||||
result = ::waveOutPrepareHeader(device, mHeader, sizeof *mHeader);
|
|
||||||
return result == MMSYSERR_NOERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::Buffer::unprepare(HWAVEOUT device)
|
|
||||||
{
|
|
||||||
MMRESULT result;
|
|
||||||
result = ::waveOutUnprepareHeader(device, mHeader, sizeof *mHeader);
|
|
||||||
return result == MMSYSERR_NOERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::Buffer::write(HWAVEOUT device)
|
|
||||||
{
|
|
||||||
MMRESULT result;
|
|
||||||
result = ::waveOutWrite(device, mHeader, sizeof *mHeader);
|
|
||||||
return result == MMSYSERR_NOERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
WmmeOutputDevice::WmmeOutputDevice(int index)
|
|
||||||
:mDevice(NULL), mDeviceIndex(index), mPlayedTime(0), mPlayedCount(0), mBufferIndex(0), mThreadHandle(NULL),
|
|
||||||
mFailed(false), mShutdownMarker(false)
|
|
||||||
{
|
|
||||||
mDoneSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
mShutdownSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
WmmeOutputDevice::~WmmeOutputDevice()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
|
|
||||||
// Destroy used signals
|
|
||||||
CloseHandle(mDoneSignal); CloseHandle(mShutdownSignal);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::open()
|
|
||||||
{
|
|
||||||
// Start thread
|
|
||||||
mThreadHandle = (HANDLE)_beginthread(&threadProc, 0, this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeOutputDevice::close()
|
|
||||||
{
|
|
||||||
// Tell the thread to exit
|
|
||||||
SetEvent(mShutdownSignal);
|
|
||||||
mShutdownMarker = true;
|
|
||||||
|
|
||||||
// Wait for thread
|
|
||||||
if (mThreadHandle)
|
|
||||||
WaitForSingleObject(mThreadHandle, INFINITE);
|
|
||||||
mThreadHandle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeOutputDevice::openDevice()
|
|
||||||
{
|
|
||||||
mClosing = false;
|
|
||||||
MMRESULT mmres = 0;
|
|
||||||
WAVEFORMATEX wfx;
|
|
||||||
memset(&wfx, 0, sizeof(wfx));
|
|
||||||
wfx.wFormatTag = 0x0001;
|
|
||||||
wfx.nChannels = AUDIO_CHANNELS;
|
|
||||||
wfx.nSamplesPerSec = AUDIO_SAMPLERATE;
|
|
||||||
wfx.wBitsPerSample = 16;
|
|
||||||
wfx.cbSize = 0;
|
|
||||||
wfx.nBlockAlign = wfx.wBitsPerSample * wfx.nChannels / 8;
|
|
||||||
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
|
|
||||||
|
|
||||||
mmres = waveOutOpen(&mDevice, mDeviceIndex, &wfx, (DWORD_PTR)&callbackProc, (DWORD_PTR)this, CALLBACK_FUNCTION);
|
|
||||||
if (mmres != MMSYSERR_NOERROR)
|
|
||||||
throw Exception(ERR_WMME_FAILED, mmres);
|
|
||||||
|
|
||||||
// Prebuffer silence
|
|
||||||
for (unsigned i=0; i<AUDIO_SPK_BUFFER_COUNT; i++)
|
|
||||||
{
|
|
||||||
//bool dumb = false;
|
|
||||||
//mCallback(mBufferList[i].mData, SPK_BUFFER_SIZE, dumb, dumb);
|
|
||||||
memset(mBufferList[i].mData, 0, AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
mBufferList[i].prepare(mDevice);
|
|
||||||
mBufferList[i].write(mDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeOutputDevice::closeDevice()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
|
|
||||||
mClosing = true;
|
|
||||||
bool finished = false;
|
|
||||||
while (!finished)
|
|
||||||
{
|
|
||||||
WaitForSingleObject(mDoneSignal, 10);
|
|
||||||
finished = areBuffersFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mDevice)
|
|
||||||
{
|
|
||||||
waveOutReset(mDevice);
|
|
||||||
waveOutClose(mDevice);
|
|
||||||
}
|
|
||||||
|
|
||||||
mDevice = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::areBuffersFinished()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
bool result = true;
|
|
||||||
for (unsigned i=0; i<AUDIO_SPK_BUFFER_COUNT && result; i++)
|
|
||||||
{
|
|
||||||
bool finished = mBufferList[i].mHeader->dwFlags & WHDR_DONE ||
|
|
||||||
!mBufferList[i].mHeader->dwFlags;
|
|
||||||
if (finished)
|
|
||||||
{
|
|
||||||
/* if (mBufferList[i].mHeader->dwFlags & WHDR_PREPARED)
|
|
||||||
mBufferList[i].Unprepare(mDevice); */
|
|
||||||
}
|
|
||||||
result &= finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeOutputDevice::threadProc(void* arg)
|
|
||||||
{
|
|
||||||
WmmeOutputDevice* impl = (WmmeOutputDevice*)arg;
|
|
||||||
impl->openDevice();
|
|
||||||
|
|
||||||
DWORD waitResult = 0;
|
|
||||||
HANDLE waitArray[2] = {impl->mDoneSignal, impl->mShutdownSignal};
|
|
||||||
unsigned index, i;
|
|
||||||
unsigned exitCount = 0;
|
|
||||||
bool exitSignal = false;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
// Poll for exit signal
|
|
||||||
if (!exitSignal)
|
|
||||||
exitSignal = impl->mShutdownMarker;
|
|
||||||
|
|
||||||
// Wait for played buffer
|
|
||||||
WaitForSingleObject(impl->mDoneSignal, 500);
|
|
||||||
|
|
||||||
// Iterate buffers to find played
|
|
||||||
for (i=0; i<AUDIO_SPK_BUFFER_COUNT; i++)
|
|
||||||
{
|
|
||||||
index = (impl->mBufferIndex + i) % AUDIO_SPK_BUFFER_COUNT;
|
|
||||||
Buffer& buffer = impl->mBufferList[index];
|
|
||||||
if (!(buffer.mHeader->dwFlags & WHDR_DONE))
|
|
||||||
break;
|
|
||||||
|
|
||||||
buffer.unprepare(impl->mDevice);
|
|
||||||
if (!exitSignal)
|
|
||||||
{
|
|
||||||
bool useAEC = true;
|
|
||||||
if (impl->connection())
|
|
||||||
impl->connection()->onSpkData(Format(), buffer.mData, AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
else
|
|
||||||
memset(buffer.mData, 0, AUDIO_SPK_BUFFER_SIZE);
|
|
||||||
|
|
||||||
buffer.prepare(impl->mDevice);
|
|
||||||
buffer.write(impl->mDevice);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
exitCount++;
|
|
||||||
}
|
|
||||||
impl->mBufferIndex = (impl->mBufferIndex + i) % AUDIO_SPK_BUFFER_COUNT;
|
|
||||||
}
|
|
||||||
while (!exitSignal || exitCount < AUDIO_SPK_BUFFER_COUNT);
|
|
||||||
impl->closeDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
HWAVEOUT WmmeOutputDevice::handle()
|
|
||||||
{
|
|
||||||
return mDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned WmmeOutputDevice::playedTime()
|
|
||||||
{
|
|
||||||
if (!mDevice)
|
|
||||||
return 0;
|
|
||||||
unsigned result = 0;
|
|
||||||
|
|
||||||
MMTIME mmt;
|
|
||||||
memset(&mmt, 0, sizeof(mmt));
|
|
||||||
mmt.wType = TIME_SAMPLES;
|
|
||||||
MMRESULT rescode = waveOutGetPosition(mDevice, &mmt, sizeof(mmt));
|
|
||||||
if (rescode != MMSYSERR_NOERROR || mmt.wType != TIME_SAMPLES)
|
|
||||||
closeDevice();
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (mmt.u.ms < mPlayedTime)
|
|
||||||
result = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = mmt.u.ms - mPlayedTime;
|
|
||||||
mPlayedTime = mmt.u.ms - result % 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result / 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WmmeOutputDevice::setFakeMode(bool fakemode)
|
|
||||||
{
|
|
||||||
closeDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::fakeMode()
|
|
||||||
{
|
|
||||||
return mFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool WmmeOutputDevice::closing()
|
|
||||||
{
|
|
||||||
return mClosing;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CALLBACK WmmeOutputDevice::callbackProc(HWAVEOUT hwo, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
|
|
||||||
{
|
|
||||||
WmmeOutputDevice* impl;
|
|
||||||
|
|
||||||
if (msg == WOM_DONE)
|
|
||||||
{
|
|
||||||
impl = (WmmeOutputDevice*)dwInstance;
|
|
||||||
InterlockedIncrement(&impl->mPlayedCount);
|
|
||||||
SetEvent(impl->mDoneSignal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __AUDIO_WMME_H
|
|
||||||
#define __AUDIO_WMME_H
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
|
|
||||||
#include <winsock2.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <mmsystem.h>
|
|
||||||
|
|
||||||
#include "../Helper/HL_Sync.h"
|
|
||||||
#include "Audio_Interface.h"
|
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <EndpointVolume.h>
|
|
||||||
#include <MMDeviceAPI.h>
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#include <Functiondiscoverykeys_devpkey.h>
|
|
||||||
#endif
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
namespace Audio
|
|
||||||
{
|
|
||||||
|
|
||||||
class WmmeInputDevice: public InputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WmmeInputDevice(int index);
|
|
||||||
~WmmeInputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
|
|
||||||
bool fakeMode();
|
|
||||||
void setFakeMode(bool fakeMode);
|
|
||||||
|
|
||||||
int readBuffer(void* buffer);
|
|
||||||
HWAVEIN handle();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
class Buffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Buffer();
|
|
||||||
~Buffer();
|
|
||||||
bool prepare(HWAVEIN device);
|
|
||||||
bool unprepare(HWAVEIN device);
|
|
||||||
bool isFinished();
|
|
||||||
bool addToDevice(HWAVEIN device);
|
|
||||||
void* data();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
HGLOBAL mDataHandle;
|
|
||||||
void* mData;
|
|
||||||
HGLOBAL mHeaderHandle;
|
|
||||||
WAVEHDR* mHeader;
|
|
||||||
};
|
|
||||||
|
|
||||||
Mutex mGuard; /// Mutex to protect this instance.
|
|
||||||
HWAVEIN mDevHandle; /// Handle of opened capture device.
|
|
||||||
HANDLE mThreadHandle;
|
|
||||||
HANDLE mShutdownSignal;
|
|
||||||
HANDLE mDoneSignal; /// Event handle to signal about finished capture.
|
|
||||||
Buffer mBufferList[AUDIO_MIC_BUFFER_COUNT];
|
|
||||||
unsigned mBufferIndex;
|
|
||||||
int mDeviceIndex; /// Index of capture device.
|
|
||||||
volatile bool mFakeMode; /// Marks if fake mode is active.
|
|
||||||
int mRefCount;
|
|
||||||
|
|
||||||
bool tryReadBuffer(void* buffer);
|
|
||||||
void openDevice();
|
|
||||||
void closeDevice();
|
|
||||||
|
|
||||||
static void CALLBACK callbackProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
|
||||||
static void threadProc(void* arg);
|
|
||||||
};
|
|
||||||
|
|
||||||
class WmmeOutputDevice: public OutputDevice
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WmmeOutputDevice(int index);
|
|
||||||
~WmmeOutputDevice();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
void close();
|
|
||||||
|
|
||||||
HWAVEOUT handle();
|
|
||||||
unsigned playedTime();
|
|
||||||
void setFakeMode(bool fakemode);
|
|
||||||
bool fakeMode();
|
|
||||||
bool closing();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
class Buffer
|
|
||||||
{
|
|
||||||
friend class WmmeOutputDevice;
|
|
||||||
public:
|
|
||||||
Buffer();
|
|
||||||
~Buffer();
|
|
||||||
bool prepare(HWAVEOUT device);
|
|
||||||
bool unprepare(HWAVEOUT device);
|
|
||||||
bool write(HWAVEOUT device);
|
|
||||||
protected:
|
|
||||||
WAVEHDR* mHeader;
|
|
||||||
void* mData;
|
|
||||||
HGLOBAL mHeaderHandle;
|
|
||||||
HGLOBAL mDataHandle;
|
|
||||||
};
|
|
||||||
|
|
||||||
Mutex mGuard; /// Mutex to protect this instance
|
|
||||||
int mDeviceIndex;
|
|
||||||
HWAVEOUT mDevice; /// Handle of opened audio device
|
|
||||||
Buffer mBufferList[AUDIO_SPK_BUFFER_COUNT];
|
|
||||||
unsigned mPlayedTime; /// Amount of played time in milliseconds
|
|
||||||
bool mClosing;
|
|
||||||
HANDLE mDoneSignal,
|
|
||||||
mShutdownSignal,
|
|
||||||
mThreadHandle;
|
|
||||||
volatile bool mShutdownMarker;
|
|
||||||
|
|
||||||
volatile LONG mPlayedCount;
|
|
||||||
unsigned mBufferIndex;
|
|
||||||
bool mFailed;
|
|
||||||
|
|
||||||
void openDevice();
|
|
||||||
void closeDevice();
|
|
||||||
bool areBuffersFinished();
|
|
||||||
|
|
||||||
static void CALLBACK callbackProc(HWAVEOUT hwo, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
|
|
||||||
static void threadProc(void* arg);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
#include "Audio_iOS.h"
|
|
||||||
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#ifndef __AUDIO_IOS
|
|
||||||
#define __AUDIO_IOS
|
|
||||||
|
|
||||||
class IosInputDevice: public InputDevice
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
|
|
||||||
public:
|
|
||||||
IosInputDevice();
|
|
||||||
~IosInputDevice();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void open();
|
|
||||||
void close();
|
|
||||||
};
|
|
||||||
|
|
||||||
class IosOutputDevice: public OutputDevice
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
public:
|
|
||||||
IosOutputDevice();
|
|
||||||
~IosOutputDevice();
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Receiver,
|
|
||||||
Speaker,
|
|
||||||
Bluetooth
|
|
||||||
};
|
|
||||||
|
|
||||||
int route();
|
|
||||||
void setRoute(int route);
|
|
||||||
|
|
||||||
void open();
|
|
||||||
void close();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
project (audio_lib)
|
|
||||||
|
|
||||||
# Rely on C++ 11
|
|
||||||
set (CMAKE_CXX_STANDARD 20)
|
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
set (AUDIOLIB_SOURCES
|
|
||||||
Audio_Resampler.cpp
|
|
||||||
Audio_Resampler.h
|
|
||||||
Audio_Quality.cpp
|
|
||||||
Audio_Quality.h
|
|
||||||
Audio_Mixer.cpp
|
|
||||||
Audio_Mixer.h
|
|
||||||
Audio_Interface.cpp
|
|
||||||
Audio_Interface.h
|
|
||||||
Audio_Helper.cpp
|
|
||||||
Audio_Helper.h
|
|
||||||
Audio_DataWindow.cpp
|
|
||||||
Audio_DataWindow.h
|
|
||||||
Audio_DevicePair.cpp
|
|
||||||
Audio_DevicePair.h
|
|
||||||
Audio_Player.cpp
|
|
||||||
Audio_Player.h
|
|
||||||
Audio_Null.cpp
|
|
||||||
Audio_Null.h
|
|
||||||
Audio_CoreAudio.cpp
|
|
||||||
Audio_CoreAudio.h
|
|
||||||
Audio_DirectSound.cpp
|
|
||||||
Audio_DirectSound.h
|
|
||||||
Audio_AndroidOboe.cpp
|
|
||||||
Audio_AndroidOboe.h
|
|
||||||
Audio_WavFile.cpp
|
|
||||||
Audio_WavFile.h
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(audio_lib ${AUDIOLIB_SOURCES})
|
|
||||||
set_property(TARGET audio_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
|
||||||
|
|
||||||
##
|
|
||||||
target_include_directories(audio_lib
|
|
||||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
|
||||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/speex/include
|
|
||||||
../../libs
|
|
||||||
../)
|
|
||||||
|
|
@ -1,746 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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/. */
|
|
||||||
|
|
||||||
#include "EP_Engine.h"
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
#include "../helper/HL_Exception.h"
|
|
||||||
#include <resip/stack/ExtensionHeader.hxx>
|
|
||||||
#include <resip/stack/Pidf.hxx>
|
|
||||||
#include <resip/stack/PlainContents.hxx>
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "Account"
|
|
||||||
|
|
||||||
#define CONFIG(X) mConfig->at(X)
|
|
||||||
#define CONFIG_EXISTS(X) mConfig->exists(X)
|
|
||||||
|
|
||||||
//#define MODIFY_VIA_BEHIND_NAT
|
|
||||||
|
|
||||||
// NAT decorator
|
|
||||||
class NATDecorator: public resip::MessageDecorator
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
UserAgent& mUserAgent;
|
|
||||||
resip::SipMessage mMessage;
|
|
||||||
|
|
||||||
resip::Data mViaHost;
|
|
||||||
unsigned short mViaPort;
|
|
||||||
|
|
||||||
resip::Data mContactsHost;
|
|
||||||
resip::Data mContactsScheme;
|
|
||||||
unsigned short mContactsPort;
|
|
||||||
|
|
||||||
public:
|
|
||||||
NATDecorator(UserAgent& endpoint);
|
|
||||||
virtual ~NATDecorator();
|
|
||||||
|
|
||||||
virtual void decorateMessage(resip::SipMessage &msg, const resip::Tuple &source, const resip::Tuple &destination, const resip::Data& sigcompId);
|
|
||||||
virtual void rollbackMessage(resip::SipMessage& msg);
|
|
||||||
virtual MessageDecorator* clone() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
NATDecorator::NATDecorator(UserAgent& ua)
|
|
||||||
:mUserAgent(ua), mViaPort(0), mContactsPort(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NATDecorator::~NATDecorator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void NATDecorator::decorateMessage(resip::SipMessage &msg, const resip::Tuple &source, const resip::Tuple &destination, const resip::Data& sigcompId)
|
|
||||||
{
|
|
||||||
// Make a copy to allow rollback
|
|
||||||
mMessage = msg;
|
|
||||||
|
|
||||||
std::stringstream dump;
|
|
||||||
mMessage.encode(dump);
|
|
||||||
//ICELogDebug(<< "Decorating message: \n" << dump.str());
|
|
||||||
|
|
||||||
// Check From: header and find the account
|
|
||||||
resip::NameAddr from;
|
|
||||||
if (msg.isRequest())
|
|
||||||
from = msg.header(resip::h_From);
|
|
||||||
else
|
|
||||||
from = msg.header(resip::h_To);
|
|
||||||
|
|
||||||
PAccount account = mUserAgent.getAccount(from);
|
|
||||||
if (!account)
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "Bad from header " << from.uri().getAor().c_str() << ". Will skip it");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!account->mConfig->at(CONFIG_EXTERNALIP).asBool())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!account->mExternalAddress.isEmpty())
|
|
||||||
{
|
|
||||||
#ifdef MODIFY_VIA_BEHIND_NAT
|
|
||||||
if (msg.header(resip::h_Vias).size() > 0)
|
|
||||||
{
|
|
||||||
resip::Via& via = msg.header(resip::h_Vias).front();
|
|
||||||
mViaHost = via.sentHost();
|
|
||||||
mViaPort = via.sentPort();
|
|
||||||
|
|
||||||
via.sentHost() = resip::Data(account->mExternalAddress.ip());
|
|
||||||
via.sentPort() = account->mExternalAddress.port();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (msg.header(resip::h_Contacts).size() > 0)
|
|
||||||
{
|
|
||||||
resip::Uri& uri = msg.header(resip::h_Contacts).front().uri();
|
|
||||||
mContactsHost = uri.host();
|
|
||||||
mContactsPort = uri.port();
|
|
||||||
mContactsScheme = uri.scheme();
|
|
||||||
|
|
||||||
uri.host() = resip::Data(account->mExternalAddress.ip());
|
|
||||||
uri.port() = account->mExternalAddress.port();
|
|
||||||
if (account->mConfig->at(CONFIG_SIPS).asBool())
|
|
||||||
{
|
|
||||||
//uri.scheme() = "sips";
|
|
||||||
//uri.param(resip::p_transport) = "tls";
|
|
||||||
}
|
|
||||||
|
|
||||||
//uri.scheme() = account->mConfig->at(CONFIG_SIPS).asBool() ? "sips" : "sip";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NATDecorator::rollbackMessage(resip::SipMessage& msg)
|
|
||||||
{
|
|
||||||
// Check From: header and find the account
|
|
||||||
resip::NameAddr from = msg.header(resip::h_From);
|
|
||||||
PAccount account = mUserAgent.getAccount(from);
|
|
||||||
if (!account)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!account->mExternalAddress.isEmpty())
|
|
||||||
{
|
|
||||||
#ifdef MODIFY_VIA_BEHIND_NAT
|
|
||||||
if (msg.header(resip::h_Vias).size() > 0)
|
|
||||||
{
|
|
||||||
resip::Via& via = msg.header(resip::h_Vias).front();
|
|
||||||
if ((via.sentHost() == resip::Data(account->mExternalAddress.ip())) &&
|
|
||||||
(via.sentPort() == account->mExternalAddress.port()))
|
|
||||||
{
|
|
||||||
via.sentHost() = mViaHost;
|
|
||||||
via.sentPort() = mViaPort;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (msg.header(resip::h_Contacts).size() > 0)
|
|
||||||
{
|
|
||||||
resip::Uri& uri = msg.header(resip::h_Contacts).front().uri();
|
|
||||||
if ((uri.host() == resip::Data(account->mExternalAddress.ip())) &&
|
|
||||||
(uri.port() == account->mExternalAddress.port()))
|
|
||||||
{
|
|
||||||
uri.host() = mContactsHost;
|
|
||||||
uri.port() = mContactsPort;
|
|
||||||
//uri.scheme() = mContactsScheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resip::MessageDecorator* NATDecorator::clone() const
|
|
||||||
{
|
|
||||||
return new NATDecorator(mUserAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Account::Account(PVariantMap config, UserAgent& agent)
|
|
||||||
:mAgent(agent), mId(0), mConfig(config), mRegistrationState(RegistrationState::None),
|
|
||||||
mRegistration(NULL)
|
|
||||||
{
|
|
||||||
mProfile = std::make_shared<resip::UserProfile>(agent.mProfile);
|
|
||||||
mId = Account::generateId();
|
|
||||||
setup(*config);
|
|
||||||
}
|
|
||||||
|
|
||||||
Account::~Account()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::setup(VariantMap &config)
|
|
||||||
{
|
|
||||||
// Credentials
|
|
||||||
|
|
||||||
if (!config.exists(CONFIG_USERNAME) || !config.exists(CONFIG_PASSWORD) || !config.exists(CONFIG_DOMAIN))
|
|
||||||
throw Exception(ERR_NO_CREDENTIALS);
|
|
||||||
|
|
||||||
mProfile->clearDigestCredentials();
|
|
||||||
mProfile->setDigestCredential(resip::Data(config[CONFIG_DOMAIN].asStdString()),
|
|
||||||
resip::Data(config[CONFIG_USERNAME].asStdString()),
|
|
||||||
resip::Data(config[CONFIG_PASSWORD].asStdString()));
|
|
||||||
ICELogInfo( << "Credentials are set to domain " << config[CONFIG_DOMAIN].asStdString() <<
|
|
||||||
", username to " << config[CONFIG_USERNAME].asStdString());
|
|
||||||
|
|
||||||
// Proxy
|
|
||||||
mProfile->unsetOutboundProxy();
|
|
||||||
if (config.exists(CONFIG_PROXY))
|
|
||||||
{
|
|
||||||
if (!config[CONFIG_PROXY].asStdString().empty())
|
|
||||||
{
|
|
||||||
resip::Uri proxyAddr;
|
|
||||||
proxyAddr.host() = resip::Data(config[CONFIG_PROXY].asStdString());
|
|
||||||
proxyAddr.port() = 5060;
|
|
||||||
if (config.exists(CONFIG_PROXYPORT))
|
|
||||||
{
|
|
||||||
if (config[CONFIG_PROXYPORT].asInt())
|
|
||||||
proxyAddr.port() = config[CONFIG_PROXYPORT].asInt();
|
|
||||||
}
|
|
||||||
if (config[CONFIG_SIPS].asBool())
|
|
||||||
proxyAddr.param(resip::p_transport) = "tls";
|
|
||||||
|
|
||||||
mProfile->setOutboundProxy(proxyAddr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NAT decorator
|
|
||||||
mProfile->setOutboundDecorator(std::make_shared<NATDecorator>(mAgent));
|
|
||||||
|
|
||||||
// Rinstance
|
|
||||||
if (config.exists(CONFIG_INSTANCE_ID))
|
|
||||||
{
|
|
||||||
if (!config[CONFIG_INSTANCE_ID].asStdString().empty())
|
|
||||||
mProfile->setInstanceId(config[CONFIG_INSTANCE_ID].asStdString().c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mProfile->setInstanceId(resip::Data::Empty);
|
|
||||||
|
|
||||||
if (config.exists(CONFIG_REGID))
|
|
||||||
mProfile->setRegId(config[CONFIG_REGID].asInt());
|
|
||||||
|
|
||||||
if (config.exists(CONFIG_RINSTANCE))
|
|
||||||
mProfile->setRinstanceEnabled(config[CONFIG_RINSTANCE].asBool());
|
|
||||||
else
|
|
||||||
mProfile->setRinstanceEnabled(true);
|
|
||||||
|
|
||||||
if (config.exists(CONFIG_RPORT))
|
|
||||||
mProfile->setRportEnabled(config[CONFIG_RPORT].asBool());
|
|
||||||
else
|
|
||||||
mProfile->setRportEnabled(true);
|
|
||||||
|
|
||||||
// From header
|
|
||||||
resip::NameAddr from;
|
|
||||||
if (config.exists(CONFIG_DISPLAYNAME))
|
|
||||||
from.displayName() = resip::Data(config[CONFIG_DISPLAYNAME].asStdString().c_str());
|
|
||||||
|
|
||||||
from.uri().scheme() = config[CONFIG_SIPS].asBool() ? "sips" : "sip";
|
|
||||||
if (config[CONFIG_DOMAINPORT].asInt() != 0)
|
|
||||||
from.uri().port() = config[CONFIG_DOMAINPORT].asInt();
|
|
||||||
else
|
|
||||||
from.uri().port();// = 5060;
|
|
||||||
|
|
||||||
from.uri().user() = resip::Data(config[CONFIG_USERNAME].asStdString());
|
|
||||||
from.uri().host() = resip::Data(config[CONFIG_DOMAIN].asStdString());
|
|
||||||
|
|
||||||
mProfile->setDefaultFrom(from);
|
|
||||||
|
|
||||||
if (!CONFIG_EXISTS(CONFIG_REGISTERDURATION))
|
|
||||||
CONFIG(CONFIG_REGISTERDURATION) = UA_REGISTRATION_TIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Account::id() const
|
|
||||||
{
|
|
||||||
return mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::start()
|
|
||||||
{
|
|
||||||
ICELogInfo(<< "Starting account " << this->name());
|
|
||||||
if (mRegistrationState != RegistrationState::None)
|
|
||||||
{
|
|
||||||
ICELogInfo(<< "Registration is active or in progress already.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!mAgent.mDum)
|
|
||||||
{
|
|
||||||
ICELogInfo(<< "DUM is not started yet.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create registration
|
|
||||||
mRegistration = new ResipSession(*mAgent.mDum);
|
|
||||||
auto regmessage = mAgent.mDum->makeRegistration(mProfile->getDefaultFrom(), mProfile, mConfig->at(CONFIG_REGISTERDURATION).asInt(), mRegistration);
|
|
||||||
|
|
||||||
for (UserInfo::const_iterator iter = mUserInfo.begin(); iter != mUserInfo.end(); iter++)
|
|
||||||
regmessage->header(resip::ExtensionHeader(iter->first.c_str())).push_back(resip::StringCategory(iter->second.c_str()));
|
|
||||||
|
|
||||||
mRegistrationState = RegistrationState::Registering;
|
|
||||||
|
|
||||||
// Send packet here
|
|
||||||
mAgent.mDum->send(regmessage);
|
|
||||||
|
|
||||||
// Check if STUN IP is required
|
|
||||||
bool noStunServerIp = !CONFIG_EXISTS(CONFIG_STUNSERVER_IP);
|
|
||||||
//bool hasStunServerName = !CONFIG(CONFIG_STUNSERVER_NAME).asStdString().empty();
|
|
||||||
if (noStunServerIp)
|
|
||||||
{
|
|
||||||
ICELogInfo(<<"No STUN server name or IP is not specified. Has to resolve/discover STUN server IP.");
|
|
||||||
mRefreshStunServerIpTimer.start(CONFIG(CONFIG_DNS_CACHE_TIME).asInt() * 1000);
|
|
||||||
mRefreshStunServerIpTimer.isTimeToSend();
|
|
||||||
queryStunServerIp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::stop()
|
|
||||||
{
|
|
||||||
// Close presence publication
|
|
||||||
if (mPublication.isValid())
|
|
||||||
{
|
|
||||||
mPublication->end();
|
|
||||||
mPublication = resip::ClientPublicationHandle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close client subscriptions
|
|
||||||
|
|
||||||
// Close registration
|
|
||||||
if (mRegistrationHandle.isValid())
|
|
||||||
{
|
|
||||||
mRegistrationHandle->removeAll();
|
|
||||||
mRegistrationHandle = resip::ClientRegistrationHandle();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (mRegistration)
|
|
||||||
{
|
|
||||||
mRegistration->end();
|
|
||||||
}
|
|
||||||
mRegistration = NULL;
|
|
||||||
mRegistrationState = RegistrationState::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::refresh()
|
|
||||||
{
|
|
||||||
if (mRegistrationHandle.isValid())
|
|
||||||
{
|
|
||||||
mRegistrationState = RegistrationState::Registering;
|
|
||||||
mRegistrationHandle->requestRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mPublication.isValid())
|
|
||||||
{
|
|
||||||
mPublication->refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Account::active()
|
|
||||||
{
|
|
||||||
return mRegistrationState == RegistrationState::Registered ||
|
|
||||||
mRegistrationState == RegistrationState::Registering ||
|
|
||||||
mRegistrationState == RegistrationState::Reregistering;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Account::name()
|
|
||||||
{
|
|
||||||
return contact(SecureScheme::Nothing).uri().toString().c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
Account::RegistrationState Account::registrationState()
|
|
||||||
{
|
|
||||||
return mRegistrationState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::publishPresence(bool online, const std::string& content, int seconds)
|
|
||||||
{
|
|
||||||
if (online == mPresenceOnline && content == mPresenceContent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mPresenceOnline = online;
|
|
||||||
mPresenceContent = content;
|
|
||||||
|
|
||||||
resip::Pidf p;
|
|
||||||
p.setEntity(contact(SecureScheme::Nothing).uri());
|
|
||||||
p.setSimpleId(resip::Data(CONFIG(CONFIG_PRESENCE_ID).asStdString()));
|
|
||||||
p.setSimpleStatus(online, resip::Data(content));
|
|
||||||
|
|
||||||
if (mPublication.isValid())
|
|
||||||
mPublication->update(&p);
|
|
||||||
else
|
|
||||||
mAgent.mDum->send(mAgent.mDum->makePublication(contact(SecureScheme::TlsOnly), mProfile, p, resip::Symbols::Presence, seconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::stopPublish()
|
|
||||||
{
|
|
||||||
if (mPublication.isValid())
|
|
||||||
mPublication->end();
|
|
||||||
}
|
|
||||||
|
|
||||||
PClientObserver Account::observe(const std::string& target, const std::string& package, void* tag)
|
|
||||||
{
|
|
||||||
// Add subscription functionality
|
|
||||||
PClientObserver observer(new ClientObserver());
|
|
||||||
observer->mSession = new ResipSession(*mAgent.mDum);
|
|
||||||
observer->mSession->setRemoteAddress(target);
|
|
||||||
observer->mSession->setTag(tag);
|
|
||||||
observer->mSessionId = observer->mSession->sessionId();
|
|
||||||
observer->mPeer = target;
|
|
||||||
|
|
||||||
std::shared_ptr<resip::SipMessage> msg;
|
|
||||||
int expires = DEFAULT_SUBSCRIPTION_TIME, refresh = DEFAULT_SUBSCRIPTION_REFRESHTIME;
|
|
||||||
if (mConfig->exists(CONFIG_SUBSCRIPTION_TIME))
|
|
||||||
expires = CONFIG(CONFIG_SUBSCRIPTION_TIME).asInt();
|
|
||||||
if (mConfig->exists(CONFIG_SUBSCRIPTION_REFRESHTIME))
|
|
||||||
refresh = CONFIG(CONFIG_SUBSCRIPTION_REFRESHTIME).asInt();
|
|
||||||
|
|
||||||
msg = mAgent.mDum->makeSubscription(resip::NameAddr(resip::Data(target)), mProfile,
|
|
||||||
resip::Data(package), expires, refresh, observer->mSession);
|
|
||||||
msg->header(resip::h_Accepts) = mAgent.mDum->getMasterProfile()->getSupportedMimeTypes(resip::NOTIFY);
|
|
||||||
|
|
||||||
mAgent.mClientObserverMap[observer->mSessionId] = observer;
|
|
||||||
mAgent.mDum->send(msg);
|
|
||||||
|
|
||||||
return observer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Queues message to peer with specified mime type. Returns ID of message. */
|
|
||||||
int Account::sendMsg(const std::string& peer, const void* ptr, unsigned length, const std::string& mime, void* tag)
|
|
||||||
{
|
|
||||||
ResipSession* s = new ResipSession(*mAgent.mDum);
|
|
||||||
s->setUa(&mAgent);
|
|
||||||
s->setTag(tag);
|
|
||||||
s->setRemoteAddress(peer);
|
|
||||||
|
|
||||||
// Find MIME type
|
|
||||||
resip::Mime type;
|
|
||||||
std::string::size_type p = mime.find('/');
|
|
||||||
if (p != std::string::npos)
|
|
||||||
type = resip::Mime(resip::Data(mime.substr(0, p)), resip::Data(mime.substr(p+1)));
|
|
||||||
else
|
|
||||||
type = resip::Mime(resip::Data(mime), resip::Data());
|
|
||||||
|
|
||||||
resip::ClientPagerMessageHandle msgHandle = mAgent.mDum->makePagerMessage(resip::NameAddr(resip::Data(peer)), mProfile, s);
|
|
||||||
unique_ptr<resip::Contents> contentPtr(new resip::PlainContents(resip::Data(std::string((const char*)ptr, length)),type));
|
|
||||||
int result = s->sessionId();
|
|
||||||
msgHandle->page(std::move(contentPtr));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
resip::NameAddr Account::contact(SecureScheme ss)
|
|
||||||
{
|
|
||||||
resip::NameAddr result;
|
|
||||||
switch (ss)
|
|
||||||
{
|
|
||||||
case SecureScheme::Nothing:
|
|
||||||
break;
|
|
||||||
case SecureScheme::SipsOnly:
|
|
||||||
result.uri().scheme() = mConfig->at(CONFIG_SIPS).asBool() ? "sips" : "sip";
|
|
||||||
break;
|
|
||||||
case SecureScheme::SipsAndTls:
|
|
||||||
result.uri().scheme() = mConfig->at(CONFIG_SIPS).asBool() ? "sips" : "sip";
|
|
||||||
if (mConfig->at(CONFIG_SIPS).asBool())
|
|
||||||
result.uri().param(resip::p_transport) = "tls";
|
|
||||||
break;
|
|
||||||
case SecureScheme::TlsOnly:
|
|
||||||
if (mConfig->at(CONFIG_SIPS).asBool())
|
|
||||||
result.uri().param(resip::p_transport) = "tls";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.uri().user() = resip::Data(mConfig->at(CONFIG_USERNAME).asStdString());
|
|
||||||
result.uri().host() = resip::Data(mConfig->at(CONFIG_DOMAIN).asStdString());
|
|
||||||
result.uri().port() = mConfig->at(CONFIG_DOMAINPORT).asInt();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::queryStunServerIp()
|
|
||||||
{
|
|
||||||
ICELogInfo(<<"Looking for STUN/TURN server IP");
|
|
||||||
|
|
||||||
if (!mConfig->exists(CONFIG_STUNSERVER_NAME))
|
|
||||||
{
|
|
||||||
// Send request to find STUN or TURN service
|
|
||||||
std::string target = std::string(mConfig->at(CONFIG_RELAY).asBool() ? "_turn" : "_stun") + "._udp." + mConfig->at(CONFIG_DOMAIN).asStdString();
|
|
||||||
|
|
||||||
// Start lookup
|
|
||||||
mAgent.mStack->getDnsStub().lookup<resip::RR_SRV>(resip::Data(target), this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Check if host name is ip already
|
|
||||||
std::string server = mConfig->at(CONFIG_STUNSERVER_NAME).asStdString();
|
|
||||||
if (ice::NetworkAddress::isIp(server))
|
|
||||||
{
|
|
||||||
mConfig->at(CONFIG_STUNSERVER_IP) = server;
|
|
||||||
mRefreshStunServerIpTimer.stop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mAgent.mStack->getDnsStub().lookup<resip::RR_A>(resip::Data(server), this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::prepareIceStack(Session *session, ice::AgentRole icerole)
|
|
||||||
{
|
|
||||||
ice::ServerConfig config;
|
|
||||||
ice::NetworkAddress addr;
|
|
||||||
addr.setIp(mConfig->at(CONFIG_STUNSERVER_IP).asStdString());
|
|
||||||
if (mConfig->at(CONFIG_STUNSERVER_PORT).asInt())
|
|
||||||
addr.setPort(mConfig->at(CONFIG_STUNSERVER_PORT).asInt());
|
|
||||||
else
|
|
||||||
addr.setPort(3478);
|
|
||||||
|
|
||||||
config.mServerList4.push_back(addr);
|
|
||||||
config.mRelay = mConfig->at(CONFIG_RELAY).asBool();
|
|
||||||
if (mConfig->exists(CONFIG_ICETIMEOUT))
|
|
||||||
config.mTimeout = mConfig->at(CONFIG_ICETIMEOUT).asInt();
|
|
||||||
|
|
||||||
config.mUsername = mConfig->at(CONFIG_ICEUSERNAME).asStdString();
|
|
||||||
config.mPassword = mConfig->at(CONFIG_ICEPASSWORD).asStdString();
|
|
||||||
|
|
||||||
config.mUseIPv4 = mAgent.config()[CONFIG_IPV4].asBool();
|
|
||||||
config.mUseIPv6 = mAgent.config()[CONFIG_IPV6].asBool();
|
|
||||||
//config.mDetectNetworkChange = true;
|
|
||||||
//config.mNetworkCheckInterval = 5000;
|
|
||||||
|
|
||||||
session->mIceStack = std::shared_ptr<ice::Stack>(ice::Stack::makeICEBox(config));
|
|
||||||
session->mIceStack->setEventHandler(session, this);
|
|
||||||
session->mIceStack->setRole(icerole);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::process()
|
|
||||||
{
|
|
||||||
if (mRefreshStunServerIpTimer.isTimeToSend())
|
|
||||||
queryStunServerIp();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onSuccess(resip::ClientRegistrationHandle h, const resip::SipMessage &response)
|
|
||||||
{
|
|
||||||
// Save registration handle
|
|
||||||
mRegistrationHandle = h;
|
|
||||||
|
|
||||||
// Copy user info to registration handle
|
|
||||||
for (UserInfo::iterator iter = mUserInfo.begin(); iter != mUserInfo.end(); iter++)
|
|
||||||
mRegistrationHandle->setCustomHeader(resip::Data(iter->first.c_str()), resip::Data(iter->second.c_str()));
|
|
||||||
|
|
||||||
// Get the Via
|
|
||||||
const resip::Via& via = response.header(resip::h_Vias).front();
|
|
||||||
|
|
||||||
// Get the sent host
|
|
||||||
const resip::Data& sentHost = via.sentHost();//response.header(h_Contacts).front().uri().host();
|
|
||||||
|
|
||||||
// Get the sentPort
|
|
||||||
int sentPort = via.sentPort();
|
|
||||||
|
|
||||||
const resip::Data& sourceHost = response.getSource().toData(resip::UDP);
|
|
||||||
int rport = 0;
|
|
||||||
if (via.exists(resip::p_rport))
|
|
||||||
rport = via.param(resip::p_rport).port();
|
|
||||||
|
|
||||||
resip::Data received = "";
|
|
||||||
if (via.exists(resip::p_received))
|
|
||||||
received = via.param(resip::p_received);
|
|
||||||
|
|
||||||
bool hostChanged = sentHost != received && received.size() > 0;
|
|
||||||
bool portChanged = sentPort != rport && rport != 0;
|
|
||||||
|
|
||||||
// Save external port and IP address
|
|
||||||
if (received.size() > 0 /*&& mConfig->at(CONFIG_EXTERNALIP).asBool()*/)
|
|
||||||
{
|
|
||||||
mExternalAddress.setIp(received.c_str());
|
|
||||||
mExternalAddress.setPort(rport ? rport : sentPort);
|
|
||||||
|
|
||||||
// Add new external address to domain list
|
|
||||||
if (mAgent.mStack)
|
|
||||||
mAgent.mStack->addAlias(resip::Data(mExternalAddress.ip()), mExternalAddress.port());
|
|
||||||
if (mAgent.mDum)
|
|
||||||
mAgent.mDum->addDomain(resip::Data(mExternalAddress.ip()));
|
|
||||||
}
|
|
||||||
|
|
||||||
mUsedTransport = response.getReceivedTransportTuple().getType();
|
|
||||||
//bool streamTransport = mUsedTransport == resip::TCP || mUsedTransport == resip::TLS;
|
|
||||||
|
|
||||||
// Retry registration for stream based transport too
|
|
||||||
if ( (hostChanged || portChanged) && mRegistrationState == RegistrationState::Registering /*&& !streamTransport*/ && mConfig->at(CONFIG_EXTERNALIP).asBool())
|
|
||||||
{
|
|
||||||
//mRegistrationHandle->requestRefresh();
|
|
||||||
// Unregister at first
|
|
||||||
mRegistrationHandle->removeAll();
|
|
||||||
mRegistrationState = RegistrationState::Reregistering;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// So here we registered ok
|
|
||||||
mRegistrationState = RegistrationState::Registered;
|
|
||||||
mAgent.onAccountStart(mAgent.getAccount(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onRemoved(resip::ClientRegistrationHandle h, const resip::SipMessage &response)
|
|
||||||
{
|
|
||||||
// Check if this unregistering is a part of rport pr
|
|
||||||
if (mRegistrationState == RegistrationState::Reregistering)
|
|
||||||
{
|
|
||||||
//if (/*this->mUseExternalIP && */response.getSource().getType() == resip::UDP)
|
|
||||||
{
|
|
||||||
resip::Uri hostport(contact(SecureScheme::TlsOnly).uri());
|
|
||||||
hostport.host() = resip::Data(mExternalAddress.ip());
|
|
||||||
hostport.port() = mExternalAddress.port();
|
|
||||||
if (mUsedTransport != resip::UDP)
|
|
||||||
{
|
|
||||||
const char* transportName = nullptr;
|
|
||||||
switch (mUsedTransport)
|
|
||||||
{
|
|
||||||
case resip::TCP: transportName = "tcp"; break;
|
|
||||||
case resip::TLS: transportName = "tls"; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
hostport.param(resip::p_transport) = resip::Data(transportName);
|
|
||||||
}
|
|
||||||
mProfile->setOverrideHostAndPort(hostport);
|
|
||||||
//mProfile->setDefaultFrom(from);
|
|
||||||
}
|
|
||||||
mProfile->setRegId(mConfig->at(CONFIG_REGID).asInt());
|
|
||||||
auto regmessage = mAgent.mDum->makeRegistration(mProfile->getDefaultFrom(), mProfile, UA_REGISTRATION_TIME);
|
|
||||||
for (UserInfo::const_iterator iter = mUserInfo.begin(); iter != mUserInfo.end(); iter++)
|
|
||||||
regmessage->header(resip::ExtensionHeader(iter->first.c_str())).push_back(resip::StringCategory(iter->second.c_str()));
|
|
||||||
|
|
||||||
mAgent.mDum->send(regmessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mRegistration = NULL;
|
|
||||||
mRegistrationState = RegistrationState::None;
|
|
||||||
mRegistrationHandle = resip::ClientRegistrationHandle();
|
|
||||||
mAgent.onAccountStop(mAgent.getAccount(this), response.header(resip::h_StatusLine).statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onFailure(resip::ClientRegistrationHandle h, const resip::SipMessage& response)
|
|
||||||
{
|
|
||||||
// Reset registration handle
|
|
||||||
mRegistrationHandle = resip::ClientRegistrationHandle();
|
|
||||||
mRegistrationState = RegistrationState::None;
|
|
||||||
mRegistration = NULL;
|
|
||||||
mAgent.onAccountStop(mAgent.getAccount(this), response.header(resip::h_StatusLine).statusCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onDnsResult(const resip::DNSResult<resip::DnsHostRecord>& result)
|
|
||||||
{
|
|
||||||
if (result.status == 0)
|
|
||||||
{
|
|
||||||
resip::Data foundAddress = result.records.front().host();
|
|
||||||
ICELogInfo( << "Success to resolve STUN/TURN address to " << foundAddress.c_str());
|
|
||||||
mConfig->at(CONFIG_STUNSERVER_IP) = std::string(foundAddress.c_str());
|
|
||||||
|
|
||||||
// Here the IP address of STUN/TURN server is found. If account is registered already - it means account is ready.
|
|
||||||
if (mRegistrationState == RegistrationState::Registered)
|
|
||||||
mAgent.onAccountStart(mAgent.getAccount(this));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ICELogError( << "Failed to resolve STUN or TURN server IP address.");
|
|
||||||
if (mRegistrationState == RegistrationState::Registered)
|
|
||||||
{
|
|
||||||
int startCode = mConfig->at(CONFIG_STUNSERVER_NAME).asStdString().empty() ? 0 : 503;
|
|
||||||
mAgent.onAccountStop(mAgent.getAccount(this), startCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onDnsResult(const resip::DNSResult<resip::DnsAAAARecord>&)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onDnsResult(const resip::DNSResult<resip::DnsSrvRecord>& result)
|
|
||||||
{
|
|
||||||
if (result.status == 0)
|
|
||||||
{
|
|
||||||
// Find lowest priority
|
|
||||||
int priority = 0x7FFFFFFF;
|
|
||||||
for (size_t i=0; i<result.records.size(); i++)
|
|
||||||
if (result.records[i].priority() < priority)
|
|
||||||
priority = result.records[i].priority();
|
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
int weight = 0;
|
|
||||||
|
|
||||||
for (size_t i=0; i<result.records.size(); i++)
|
|
||||||
{
|
|
||||||
if (result.records[i].priority() == priority && result.records[i].weight() >= weight)
|
|
||||||
{
|
|
||||||
index = i;
|
|
||||||
weight = result.records[i].weight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mConfig->at(CONFIG_STUNSERVER_PORT) = result.records[index].port();
|
|
||||||
|
|
||||||
const char* host = result.records[index].target().c_str();
|
|
||||||
|
|
||||||
ICELogInfo( << "Success to find STUN/TURN server on " << result.records[index].target().c_str() <<
|
|
||||||
":" << (int)result.records[index].port());
|
|
||||||
|
|
||||||
|
|
||||||
if (inet_addr(host) == INADDR_NONE)
|
|
||||||
{
|
|
||||||
// Try to resolve domain name now
|
|
||||||
mAgent.mStack->getDnsStub().lookup<resip::RR_A>(result.records[index].target(), this);
|
|
||||||
//mStack->getDnsStub().lookup<resip::RR_AAAA>(result.records[index].target(), this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mConfig->at(CONFIG_STUNSERVER_IP) = std::string(host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ICELogError( << "Failed to find STUN or TURN service for specified domain.");
|
|
||||||
//mAgent::shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onDnsResult(const resip::DNSResult<resip::DnsNaptrRecord>&)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::onDnsResult(const resip::DNSResult<resip::DnsCnameRecord>&)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Account::isResponsibleFor(const resip::NameAddr &addr)
|
|
||||||
{
|
|
||||||
std::string user = addr.uri().user().c_str();
|
|
||||||
std::string domain = addr.uri().host().c_str();
|
|
||||||
int p = addr.uri().port();
|
|
||||||
if (mConfig->at(CONFIG_USERNAME).asStdString() == user && mConfig->at(CONFIG_DOMAIN).asStdString() == domain)
|
|
||||||
{
|
|
||||||
// Check if ports are the same or port is not specified at all
|
|
||||||
if (mConfig->exists(CONFIG_DOMAINPORT))
|
|
||||||
return mConfig->at(CONFIG_DOMAINPORT).asInt() == p || !p;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::setUserInfo(const UserInfo &info)
|
|
||||||
{
|
|
||||||
mUserInfo = info;
|
|
||||||
if (mRegistrationHandle.isValid())
|
|
||||||
{
|
|
||||||
for (UserInfo::iterator iter = mUserInfo.begin(); iter != mUserInfo.end(); iter++)
|
|
||||||
mRegistrationHandle->setCustomHeader(resip::Data(iter->first.c_str()), resip::Data(iter->second.c_str()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Account::UserInfo Account::getUserInfo() const
|
|
||||||
{
|
|
||||||
return mUserInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::atomic_int Account::IdGenerator;
|
|
||||||
int Account::generateId()
|
|
||||||
{
|
|
||||||
return ++IdGenerator;
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 EP_ACCOUNT_H
|
|
||||||
#define EP_ACCOUNT_H
|
|
||||||
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../helper/HL_VariantMap.h"
|
|
||||||
#include "ice/ICEAddress.h"
|
|
||||||
#include "ice/ICETime.h"
|
|
||||||
#include "ice/ICEBox.h"
|
|
||||||
#include "resip/dum/UserProfile.hxx"
|
|
||||||
#include "resip/dum/ClientRegistration.hxx"
|
|
||||||
#include "resip/dum/ClientPublication.hxx"
|
|
||||||
#include "resip/stack/DnsInterface.hxx"
|
|
||||||
#include "resip/stack/NameAddr.hxx"
|
|
||||||
|
|
||||||
#include "EP_Observer.h"
|
|
||||||
|
|
||||||
class UserAgent;
|
|
||||||
class Session;
|
|
||||||
|
|
||||||
class Account: public resip::DnsResultSink
|
|
||||||
{
|
|
||||||
friend class UserAgent;
|
|
||||||
friend class NATDecorator;
|
|
||||||
public:
|
|
||||||
Account(PVariantMap config, UserAgent& agent);
|
|
||||||
~Account();
|
|
||||||
|
|
||||||
void start();
|
|
||||||
void stop();
|
|
||||||
void refresh();
|
|
||||||
bool active();
|
|
||||||
int id() const;
|
|
||||||
|
|
||||||
enum class RegistrationState
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Registering,
|
|
||||||
Reregistering,
|
|
||||||
Registered,
|
|
||||||
Unregistering
|
|
||||||
};
|
|
||||||
RegistrationState registrationState();
|
|
||||||
|
|
||||||
/* Publishes new presence information */
|
|
||||||
void publishPresence(bool online, const std::string& content, int seconds = 600);
|
|
||||||
|
|
||||||
/* Stops publishing of presence */
|
|
||||||
void stopPublish();
|
|
||||||
|
|
||||||
/* Starts observing on specified target / package */
|
|
||||||
PClientObserver observe(const std::string& target, const std::string& package, void* tag);
|
|
||||||
|
|
||||||
/* Queues message to peer with specified mime type. Returns ID of message. */
|
|
||||||
int sendMsg(const std::string& peer, const void* ptr, unsigned length, const std::string& mime, void* tag);
|
|
||||||
|
|
||||||
/* Returns name of account - <sip:user@domain> */
|
|
||||||
std::string name();
|
|
||||||
|
|
||||||
/* Updates account with configuration */
|
|
||||||
void setup(VariantMap& config);
|
|
||||||
|
|
||||||
/* Returns corresponding resiprocate profile */
|
|
||||||
std::shared_ptr<resip::UserProfile> getUserProfile() const { return mProfile; }
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> UserInfo;
|
|
||||||
void setUserInfo(const UserInfo& info);
|
|
||||||
UserInfo getUserInfo() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
PVariantMap mConfig;
|
|
||||||
|
|
||||||
// Registration
|
|
||||||
ResipSession* mRegistration;
|
|
||||||
resip::ClientRegistrationHandle mRegistrationHandle;
|
|
||||||
resip::ClientPublicationHandle mPublication;
|
|
||||||
resip::TransportType mUsedTransport;
|
|
||||||
|
|
||||||
RegistrationState mRegistrationState;
|
|
||||||
|
|
||||||
ice::NetworkAddress mExternalAddress;
|
|
||||||
std::shared_ptr<resip::UserProfile> mProfile;
|
|
||||||
UserAgent& mAgent;
|
|
||||||
bool mPresenceOnline;
|
|
||||||
std::string mPresenceContent;
|
|
||||||
|
|
||||||
// Timer to refresh STUN server IP
|
|
||||||
ice::ICEScheduleTimer mRefreshStunServerIpTimer;
|
|
||||||
|
|
||||||
// Cached auth
|
|
||||||
resip::Auth mCachedAuth;
|
|
||||||
|
|
||||||
// Id of account
|
|
||||||
int mId;
|
|
||||||
|
|
||||||
// User info about current state
|
|
||||||
UserInfo mUserInfo;
|
|
||||||
|
|
||||||
// List of client subscriptions sent from this account
|
|
||||||
typedef std::set<PClientObserver> ClientObserverSet;
|
|
||||||
ClientObserverSet mClientObserverSet;
|
|
||||||
|
|
||||||
|
|
||||||
void process();
|
|
||||||
// Method queries new stun server ip from dns (if stun server is specified as dns name)
|
|
||||||
void queryStunServerIp();
|
|
||||||
|
|
||||||
bool isResponsibleFor(const resip::NameAddr& addr);
|
|
||||||
enum class SecureScheme
|
|
||||||
{
|
|
||||||
SipsAndTls,
|
|
||||||
SipsOnly,
|
|
||||||
TlsOnly,
|
|
||||||
Nothing
|
|
||||||
};
|
|
||||||
|
|
||||||
resip::NameAddr contact(SecureScheme ss = SecureScheme::SipsOnly);
|
|
||||||
|
|
||||||
// This method prepares configuration, creates ice stack and sets ownership to session
|
|
||||||
void prepareIceStack(Session* session, ice::AgentRole role);
|
|
||||||
void onSuccess(resip::ClientRegistrationHandle h, const resip::SipMessage& response);
|
|
||||||
void onRemoved(resip::ClientRegistrationHandle h, const resip::SipMessage& response);
|
|
||||||
void onFailure(resip::ClientRegistrationHandle, const resip::SipMessage& response);
|
|
||||||
|
|
||||||
#pragma region DnsResultSink implementation
|
|
||||||
void onDnsResult(const resip::DNSResult<resip::DnsHostRecord>&);
|
|
||||||
void onDnsResult(const resip::DNSResult<resip::DnsAAAARecord>&);
|
|
||||||
void onDnsResult(const resip::DNSResult<resip::DnsSrvRecord>&);
|
|
||||||
void onDnsResult(const resip::DNSResult<resip::DnsNaptrRecord>&);
|
|
||||||
void onDnsResult(const resip::DNSResult<resip::DnsCnameRecord>&);
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
static int generateId();
|
|
||||||
static std::atomic_int IdGenerator;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<Account> PAccount;
|
|
||||||
|
|
||||||
#endif // EP_ACCOUNT_H
|
|
||||||
|
|
@ -1,367 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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/. */
|
|
||||||
|
|
||||||
#include "EP_AudioProvider.h"
|
|
||||||
#include "EP_Engine.h"
|
|
||||||
#include "../media/MT_Box.h"
|
|
||||||
#include "../media/MT_AudioStream.h"
|
|
||||||
#include "../media/MT_SrtpHelper.h"
|
|
||||||
#include "../media/MT_Stream.h"
|
|
||||||
#include "../helper/HL_Rtp.h"
|
|
||||||
#include "../helper/HL_StreamState.h"
|
|
||||||
#include "../helper/HL_Log.h"
|
|
||||||
#include "../helper/HL_String.h"
|
|
||||||
|
|
||||||
#define LOG_SUBSYSTEM "AudioProvider"
|
|
||||||
|
|
||||||
AudioProvider::AudioProvider(UserAgent& agent, MT::Terminal& terminal)
|
|
||||||
:mUserAgent(agent), mTerminal(terminal), mState(0),
|
|
||||||
mRemoteTelephoneCodec(0), mRemoteNoSdp(false)
|
|
||||||
{
|
|
||||||
mActive = mfActive;
|
|
||||||
mRemoteState = msSendRecv;
|
|
||||||
mActiveStream = mTerminal.createStream(MT::Stream::Audio, mUserAgent.config());
|
|
||||||
if (mUserAgent.config().exists(CONFIG_CODEC_PRIORITY))
|
|
||||||
mCodecPriority.setupFrom(mUserAgent.config()[CONFIG_CODEC_PRIORITY].asVMap());
|
|
||||||
mSrtpSuite = SRTP_NONE;
|
|
||||||
setStateImpl((int)StreamState::SipRecv | (int)StreamState::SipSend | (int)StreamState::Receiving | (int)StreamState::Sending);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioProvider::~AudioProvider()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AudioProvider::streamName()
|
|
||||||
{
|
|
||||||
return "audio";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AudioProvider::streamProfile()
|
|
||||||
{
|
|
||||||
if (mState & (int)StreamState::Srtp)
|
|
||||||
return "RTP/SAVP";
|
|
||||||
else
|
|
||||||
return "RTP/AVP";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets destination IP address
|
|
||||||
void AudioProvider::setDestinationAddress(const RtpPair<InternetAddress>& addr)
|
|
||||||
{
|
|
||||||
if (!mActiveStream)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mActiveStream->setDestination(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::configureMediaObserver(MT::Stream::MediaObserver *observer, void* userTag)
|
|
||||||
{
|
|
||||||
mMediaObserver = observer;
|
|
||||||
mMediaObserverTag = userTag;
|
|
||||||
if (mActiveStream)
|
|
||||||
mActiveStream->configureMediaObserver(observer, userTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes incoming data
|
|
||||||
void AudioProvider::processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& source)
|
|
||||||
{
|
|
||||||
if (!mActiveStream)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (RtpHelper::isRtpOrRtcp(dataBuffer, dataSize))
|
|
||||||
{
|
|
||||||
ICELogMedia(<<"Adding new data to stream processing");
|
|
||||||
mActiveStream->dataArrived(s, dataBuffer, dataSize, source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is called by user agent to send ICE packet from mediasocket
|
|
||||||
void AudioProvider::sendData(const PDatagramSocket& s, InternetAddress& destination, const void* buffer, unsigned int size)
|
|
||||||
{
|
|
||||||
s->sendDatagram(destination, buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create SDP offer
|
|
||||||
void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction)
|
|
||||||
{
|
|
||||||
if (mRemoteNoSdp)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (mState & (int)StreamState::Srtp)
|
|
||||||
{
|
|
||||||
// Check if SRTP suite is found already or not
|
|
||||||
if (mSrtpSuite == SRTP_NONE)
|
|
||||||
{
|
|
||||||
for (int suite = SRTP_AES_128_AUTH_80; suite <= SRTP_LAST; suite++)
|
|
||||||
sdp.addAttribute("crypto", resip::Data(createCryptoAttribute((SrtpSuite)suite)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
sdp.addAttribute("crypto", resip::Data(createCryptoAttribute(mSrtpSuite)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use CodecListPriority mCodecPriority adapter to work with codec priorities
|
|
||||||
if (mAvailableCodecs.empty())
|
|
||||||
{
|
|
||||||
for (int i=0; i<mCodecPriority.count(mTerminal.codeclist()); i++)
|
|
||||||
mCodecPriority.codecAt(mTerminal.codeclist(), i).updateSdp(sdp.codecs(), direction);
|
|
||||||
sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mAvailableCodecs.front().mFactory->updateSdp(sdp.codecs(), direction);
|
|
||||||
if (mRemoteTelephoneCodec)
|
|
||||||
sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Publish stream state
|
|
||||||
const char* attr = nullptr;
|
|
||||||
switch (mActive)
|
|
||||||
{
|
|
||||||
case mfActive:
|
|
||||||
switch(mRemoteState)
|
|
||||||
{
|
|
||||||
case msSendonly: attr = "recvonly"; break;
|
|
||||||
case msInactive: attr = "recvonly"; break;
|
|
||||||
case msRecvonly:
|
|
||||||
case msSendRecv: break; // Do nothing here
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case mfPaused:
|
|
||||||
switch (mRemoteState)
|
|
||||||
{
|
|
||||||
case msRecvonly: attr = "sendonly"; break;
|
|
||||||
case msSendonly: attr = "inactive"; break;
|
|
||||||
case msInactive: attr = "inactive"; break;
|
|
||||||
case msSendRecv: attr = "sendonly"; break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (attr)
|
|
||||||
sdp.addAttribute(attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::sessionDeleted()
|
|
||||||
{
|
|
||||||
sessionTerminated();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::sessionTerminated()
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "sessionTerminated() for audio provider");
|
|
||||||
setState(state() & ~((int)StreamState::Sending | (int)StreamState::Receiving));
|
|
||||||
|
|
||||||
if (mActiveStream)
|
|
||||||
{
|
|
||||||
ICELogDebug(<< "Copy statistics from existing stream before freeing.");
|
|
||||||
|
|
||||||
// Copy statistics - maybe it will be requested later
|
|
||||||
mBackupStats = mActiveStream->statistics();
|
|
||||||
|
|
||||||
ICELogDebug(<< "Remove stream from terminal");
|
|
||||||
mTerminal.freeStream(mActiveStream);
|
|
||||||
|
|
||||||
// Retrieve final statistics
|
|
||||||
MT::AudioStream* audio_stream = dynamic_cast<MT::AudioStream*>(mActiveStream.get());
|
|
||||||
if (audio_stream)
|
|
||||||
audio_stream->setFinalStatisticsOutput(&mBackupStats);
|
|
||||||
|
|
||||||
ICELogDebug(<< "Reset reference to stream.");
|
|
||||||
mActiveStream.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::sessionEstablished(int conntype)
|
|
||||||
{
|
|
||||||
// Start media streams
|
|
||||||
setState(state() | (int)StreamState::Receiving | (int)StreamState::Sending);
|
|
||||||
|
|
||||||
// Available codec list can be empty in case of no-sdp offers.
|
|
||||||
if (conntype == EV_SIP && !mAvailableCodecs.empty() && mActiveStream)
|
|
||||||
{
|
|
||||||
RemoteCodec& rc = mAvailableCodecs.front();
|
|
||||||
mActiveStream->setTransmittingCodec(*rc.mFactory, rc.mRemotePayloadType);
|
|
||||||
auto codec = dynamic_cast<MT::AudioStream*>(mActiveStream.get())->transmittingCodec();
|
|
||||||
dynamic_cast<MT::AudioStream*>(mActiveStream.get())->setTelephoneCodec(mRemoteTelephoneCodec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::setSocket(const RtpPair<PDatagramSocket>& p4, const RtpPair<PDatagramSocket>& p6)
|
|
||||||
{
|
|
||||||
mSocket4 = p4;
|
|
||||||
mSocket6 = p6;
|
|
||||||
mActiveStream->setSocket(p4);
|
|
||||||
}
|
|
||||||
|
|
||||||
RtpPair<PDatagramSocket>& AudioProvider::socket(int family)
|
|
||||||
{
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
return mSocket4;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
return mSocket6;
|
|
||||||
}
|
|
||||||
return mSocket4;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool AudioProvider::processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection)
|
|
||||||
{
|
|
||||||
// Check if there is compatible codec
|
|
||||||
mAvailableCodecs.clear();
|
|
||||||
mRemoteTelephoneCodec = 0;
|
|
||||||
|
|
||||||
// Check if there is SDP at all
|
|
||||||
mRemoteNoSdp = media.codecs().empty();
|
|
||||||
if (mRemoteNoSdp)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Update RFC2833 related information
|
|
||||||
findRfc2833(media.codecs());
|
|
||||||
|
|
||||||
// Use CodecListPriority mCodecPriority to work with codec priorities
|
|
||||||
int pt;
|
|
||||||
for (int localIndex=0; localIndex<mCodecPriority.count(mTerminal.codeclist()); localIndex++)
|
|
||||||
{
|
|
||||||
MT::Codec::Factory& factory = mCodecPriority.codecAt(mTerminal.codeclist(), localIndex);
|
|
||||||
if ((pt = factory.processSdp(media.codecs(), sdpDirection)) != -1)
|
|
||||||
mAvailableCodecs.push_back(RemoteCodec(&factory, pt));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mAvailableCodecs.size())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Iterate SRTP crypto: attributes
|
|
||||||
if (media.exists("crypto"))
|
|
||||||
{
|
|
||||||
// Find the most strong crypt suite
|
|
||||||
const std::list<resip::Data>& vl = media.getValues("crypto");
|
|
||||||
SrtpSuite ss = SRTP_NONE;
|
|
||||||
ByteBuffer key;
|
|
||||||
for (std::list<resip::Data>::const_iterator attrIter = vl.begin(); attrIter != vl.end(); attrIter++)
|
|
||||||
{
|
|
||||||
const resip::Data& attr = *attrIter;
|
|
||||||
ByteBuffer tempkey;
|
|
||||||
SrtpSuite suite = processCryptoAttribute(attr, tempkey);
|
|
||||||
if (suite > ss)
|
|
||||||
{
|
|
||||||
ss = suite;
|
|
||||||
mSrtpSuite = suite;
|
|
||||||
key = tempkey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If SRTP suite is agreed
|
|
||||||
if (ss != SRTP_NONE)
|
|
||||||
{
|
|
||||||
ICELogInfo(<< "Found SRTP suite " << ss);
|
|
||||||
mActiveStream->srtp().open(key, ss);
|
|
||||||
setState(state() | (int)StreamState::Srtp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ICELogInfo(<< "Did not find valid SRTP suite");
|
|
||||||
}
|
|
||||||
|
|
||||||
DataProvider::processSdpOffer(media, sdpDirection);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AudioProvider::setState(unsigned state)
|
|
||||||
{
|
|
||||||
setStateImpl(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned AudioProvider::state()
|
|
||||||
{
|
|
||||||
return mState;
|
|
||||||
}
|
|
||||||
|
|
||||||
MT::Statistics AudioProvider::getStatistics()
|
|
||||||
{
|
|
||||||
if (mActiveStream)
|
|
||||||
return mActiveStream->statistics();
|
|
||||||
else
|
|
||||||
return mBackupStats;
|
|
||||||
}
|
|
||||||
|
|
||||||
MT::PStream AudioProvider::activeStream()
|
|
||||||
{
|
|
||||||
return mActiveStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string AudioProvider::createCryptoAttribute(SrtpSuite suite)
|
|
||||||
{
|
|
||||||
if (!mActiveStream)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// Print key to base64 string
|
|
||||||
PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first;
|
|
||||||
resip::Data d(keyBuffer->data(), keyBuffer->size());
|
|
||||||
resip::Data keyText = d.base64encode();
|
|
||||||
|
|
||||||
return std::format("{} {} inline:{}", 1, toString(suite), keyText.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key)
|
|
||||||
{
|
|
||||||
int srtpTag = 0;
|
|
||||||
char suite[64], keyChunk[256];
|
|
||||||
int components = sscanf(value.c_str(), "%d %63s inline: %255s", &srtpTag, suite, keyChunk);
|
|
||||||
if (components != 3)
|
|
||||||
return SRTP_NONE;
|
|
||||||
|
|
||||||
const char* delimiter = strchr(keyChunk, '|');
|
|
||||||
resip::Data keyText;
|
|
||||||
if (delimiter)
|
|
||||||
keyText = resip::Data(keyChunk, delimiter - keyChunk);
|
|
||||||
else
|
|
||||||
keyText = resip::Data(keyChunk);
|
|
||||||
|
|
||||||
resip::Data rawkey = keyText.base64decode();
|
|
||||||
key = ByteBuffer(rawkey.c_str(), rawkey.size());
|
|
||||||
|
|
||||||
return toSrtpSuite(suite);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs)
|
|
||||||
{
|
|
||||||
resip::SdpContents::Session::Medium::CodecContainer::const_iterator codecIter;
|
|
||||||
for (codecIter = codecs.begin(); codecIter != codecs.end(); codecIter++)
|
|
||||||
{
|
|
||||||
if (strcmp("TELEPHONE-EVENT", codecIter->getName().c_str()) == 0 ||
|
|
||||||
strcmp("telephone-event", codecIter->getName().c_str()) == 0)
|
|
||||||
mRemoteTelephoneCodec = codecIter->payloadType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::readFile(const Audio::PWavFileReader& stream, MT::Stream::MediaDirection direction)
|
|
||||||
{
|
|
||||||
// Iterate stream list
|
|
||||||
if (mActiveStream)
|
|
||||||
mActiveStream->readFile(stream, direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::writeFile(const Audio::PWavFileWriter& stream, MT::Stream::MediaDirection direction)
|
|
||||||
{
|
|
||||||
if (mActiveStream)
|
|
||||||
mActiveStream->writeFile(stream, direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::setupMirror(bool enable)
|
|
||||||
{
|
|
||||||
if (mActiveStream)
|
|
||||||
mActiveStream->setupMirror(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioProvider::setStateImpl(unsigned int state) {
|
|
||||||
mState = state;
|
|
||||||
if (mActiveStream)
|
|
||||||
mActiveStream->setState(state);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
/* 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 __AUDIO_PROVIDER_H
|
|
||||||
#define __AUDIO_PROVIDER_H
|
|
||||||
|
|
||||||
#include "EP_DataProvider.h"
|
|
||||||
#include "../helper/HL_InternetAddress.h"
|
|
||||||
#include "../helper/HL_Rtp.h"
|
|
||||||
#include "../media/MT_Box.h"
|
|
||||||
#include "../media/MT_Stream.h"
|
|
||||||
#include "../media/MT_Codec.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class UserAgent;
|
|
||||||
|
|
||||||
class AudioProvider: public DataProvider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
AudioProvider(UserAgent& agent, MT::Terminal& terminal);
|
|
||||||
virtual ~AudioProvider();
|
|
||||||
|
|
||||||
// Returns provider RTP name
|
|
||||||
std::string streamName() override;
|
|
||||||
|
|
||||||
// Returns provider RTP profile name
|
|
||||||
std::string streamProfile() override;
|
|
||||||
|
|
||||||
// Sets destination IP address
|
|
||||||
void setDestinationAddress(const RtpPair<InternetAddress>& addr) override;
|
|
||||||
|
|
||||||
// Processes incoming data
|
|
||||||
void processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& source) override;
|
|
||||||
|
|
||||||
// This method is called by user agent to send ICE packet from mediasocket
|
|
||||||
void sendData(const PDatagramSocket& s, InternetAddress& destination, const void* dataBuffer, unsigned int datasize) override;
|
|
||||||
|
|
||||||
// Updates SDP offer
|
|
||||||
void updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction) override;
|
|
||||||
|
|
||||||
// Called by user agent when session is deleted.
|
|
||||||
void sessionDeleted() override;
|
|
||||||
|
|
||||||
// Called by user agent when session is terminated.
|
|
||||||
void sessionTerminated() override;
|
|
||||||
|
|
||||||
// Called by user agent when session is started.
|
|
||||||
void sessionEstablished(int conntype) override;
|
|
||||||
|
|
||||||
// Called by user agent to save media socket for this provider
|
|
||||||
void setSocket(const RtpPair<PDatagramSocket>& p4, const RtpPair<PDatagramSocket>& p6) override;
|
|
||||||
|
|
||||||
// Called by user agent to get media socket for this provider
|
|
||||||
RtpPair<PDatagramSocket>& socket(int family) override;
|
|
||||||
|
|
||||||
// Called by user agent to process media stream description from remote peer.
|
|
||||||
// Returns true if description is processed succesfully. Otherwise method returns false.
|
|
||||||
// myAnswer sets if the answer will be sent after.
|
|
||||||
bool processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection) override;
|
|
||||||
|
|
||||||
|
|
||||||
void setState(unsigned state) override;
|
|
||||||
unsigned state() override;
|
|
||||||
MT::Statistics getStatistics() override;
|
|
||||||
MT::PStream activeStream();
|
|
||||||
|
|
||||||
void readFile(const Audio::PWavFileReader& stream, MT::Stream::MediaDirection direction);
|
|
||||||
void writeFile(const Audio::PWavFileWriter& stream, MT::Stream::MediaDirection direction);
|
|
||||||
void setupMirror(bool enable);
|
|
||||||
|
|
||||||
void configureMediaObserver(MT::Stream::MediaObserver* observer, void* userTag);
|
|
||||||
static SrtpSuite processCryptoAttribute(const resip::Data& value, ByteBuffer& key);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// SDP's stream name
|
|
||||||
std::string mStreamName;
|
|
||||||
|
|
||||||
// Socket handles to operate
|
|
||||||
RtpPair<PDatagramSocket> mSocket4, mSocket6;
|
|
||||||
|
|
||||||
// Destination IP4/6 address
|
|
||||||
RtpPair<InternetAddress> mDestination;
|
|
||||||
|
|
||||||
MT::PStream mActiveStream;
|
|
||||||
UserAgent& mUserAgent;
|
|
||||||
MT::Terminal& mTerminal;
|
|
||||||
MT::Statistics mBackupStats;
|
|
||||||
|
|
||||||
unsigned mState;
|
|
||||||
SrtpSuite mSrtpSuite;
|
|
||||||
struct RemoteCodec
|
|
||||||
{
|
|
||||||
RemoteCodec(MT::Codec::Factory* factory, int payloadType)
|
|
||||||
:mFactory(factory), mRemotePayloadType(payloadType)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
MT::Codec::Factory* mFactory;
|
|
||||||
int mRemotePayloadType;
|
|
||||||
};
|
|
||||||
std::vector<RemoteCodec> mAvailableCodecs;
|
|
||||||
int mRemoteTelephoneCodec; // Payload type of remote rfc2833 codec
|
|
||||||
bool mRemoteNoSdp; // Marks if we got no-sdp offer
|
|
||||||
MT::CodecListPriority mCodecPriority;
|
|
||||||
MT::Stream::MediaObserver* mMediaObserver = nullptr;
|
|
||||||
void* mMediaObserverTag = nullptr;
|
|
||||||
|
|
||||||
std::string createCryptoAttribute(SrtpSuite suite);
|
|
||||||
void findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs);
|
|
||||||
|
|
||||||
// Implements setState() logic. This allows to be called from constructor (it is not virtual function)
|
|
||||||
void setStateImpl(unsigned state);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#include "EP_DataProvider.h"
|
|
||||||
#include "../helper/HL_StreamState.h"
|
|
||||||
|
|
||||||
bool DataProvider::isSupported(const char* name)
|
|
||||||
{
|
|
||||||
return !strcmp(name, "audio");
|
|
||||||
|
|
||||||
//return (!strcmp(name, "screen") || !strcmp(name, "data") || !strcmp(name, "audio") || !strcmp(name, "video"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataProvider::pause()
|
|
||||||
{
|
|
||||||
/*if (state() & STATE_SIPRECV)
|
|
||||||
setState( state() & ~STATE_SIPRECV );*/
|
|
||||||
|
|
||||||
// Stop receive RTP stream
|
|
||||||
if (state() & (int)StreamState::Receiving)
|
|
||||||
setState( state() & ~(int)StreamState::Receiving );
|
|
||||||
|
|
||||||
mActive = mfPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DataProvider::resume()
|
|
||||||
{
|
|
||||||
// Tell remote peer about resumed receiving in SDP
|
|
||||||
//setState( state() | STATE_SIPRECV );
|
|
||||||
|
|
||||||
// Start receive RTP stream
|
|
||||||
setState( state() | (int)StreamState::Receiving );
|
|
||||||
|
|
||||||
mActive = mfActive;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DataProvider::processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection)
|
|
||||||
{
|
|
||||||
// Process paused and inactive calls
|
|
||||||
if (media.exists("sendonly"))
|
|
||||||
{
|
|
||||||
mRemoteState = msSendonly;
|
|
||||||
setState(state() & ~(int)StreamState::Sending);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (media.exists("recvonly"))
|
|
||||||
{
|
|
||||||
mRemoteState = msRecvonly;
|
|
||||||
setState(state() & ~(int)StreamState::Receiving);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (media.exists("inactive"))
|
|
||||||
{
|
|
||||||
mRemoteState = msInactive;
|
|
||||||
setState(state() & ~((int)StreamState::Sending | (int)StreamState::Receiving) );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mRemoteState = msSendRecv;
|
|
||||||
switch (mActive)
|
|
||||||
{
|
|
||||||
case mfActive:
|
|
||||||
setState(state() | (int)StreamState::Sending | (int)StreamState::Receiving);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case mfPaused:
|
|
||||||
setState(state() | (int)StreamState::Sending );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
/* Copyright(C) 2007-2016 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 __DATA_PROVIDER_H
|
|
||||||
#define __DATA_PROVIDER_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "resip/stack/SdpContents.hxx"
|
|
||||||
|
|
||||||
#include "../helper/HL_InternetAddress.h"
|
|
||||||
#include "../helper/HL_NetworkSocket.h"
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../media/MT_Stream.h"
|
|
||||||
|
|
||||||
class DataProvider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum MediaFlow
|
|
||||||
{
|
|
||||||
mfActive,
|
|
||||||
mfPaused
|
|
||||||
};
|
|
||||||
|
|
||||||
enum MediaState
|
|
||||||
{
|
|
||||||
msSendRecv,
|
|
||||||
msSendonly,
|
|
||||||
msRecvonly,
|
|
||||||
msInactive
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool isSupported(const char* name);
|
|
||||||
|
|
||||||
// Returns provider RTP name
|
|
||||||
virtual std::string streamName() = 0;
|
|
||||||
|
|
||||||
// Returns provider RTP profile name
|
|
||||||
virtual std::string streamProfile() = 0;
|
|
||||||
|
|
||||||
// Sets destination IP address
|
|
||||||
virtual void setDestinationAddress(const RtpPair<InternetAddress>& addr) = 0;
|
|
||||||
|
|
||||||
// Processes incoming data
|
|
||||||
virtual void processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& address) = 0;
|
|
||||||
|
|
||||||
// This method is called by user agent to send ICE packet from mediasocket
|
|
||||||
virtual void sendData(const PDatagramSocket& s, InternetAddress& destination, const void* dataBuffer, unsigned int datasize) = 0;
|
|
||||||
|
|
||||||
// Updates SDP offer
|
|
||||||
virtual void updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction) = 0;
|
|
||||||
|
|
||||||
// Called by user agent when session is deleted. Comes after sessionTerminated().
|
|
||||||
virtual void sessionDeleted() = 0;
|
|
||||||
|
|
||||||
// Called by user agent when session is terminated.
|
|
||||||
virtual void sessionTerminated() = 0;
|
|
||||||
|
|
||||||
// Called by user agent when session is started.
|
|
||||||
virtual void sessionEstablished(int conntype) = 0;
|
|
||||||
|
|
||||||
// Called by user agent to save media socket for this provider
|
|
||||||
virtual void setSocket(const RtpPair<PDatagramSocket>& p4, const RtpPair<PDatagramSocket>& p6) = 0;
|
|
||||||
|
|
||||||
// Called by user agent to get media socket for this provider
|
|
||||||
virtual RtpPair<PDatagramSocket>& socket(int family) = 0;
|
|
||||||
|
|
||||||
// Called by user agent to process media stream description from remote peer.
|
|
||||||
// Returns true if description is processed succesfully. Otherwise method returns false.
|
|
||||||
virtual bool processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection) = 0;
|
|
||||||
|
|
||||||
virtual unsigned state() = 0;
|
|
||||||
virtual void setState(unsigned state) = 0;
|
|
||||||
|
|
||||||
virtual void pause();
|
|
||||||
virtual void resume();
|
|
||||||
|
|
||||||
virtual MT::Statistics getStatistics() = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
MediaFlow mActive;
|
|
||||||
MediaState mRemoteState;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<DataProvider> PDataProvider;
|
|
||||||
typedef std::vector<PDataProvider> DataProviderVector;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,493 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __ENGINE_H
|
|
||||||
#define __ENGINE_H
|
|
||||||
|
|
||||||
#include "resip/stack/SdpContents.hxx"
|
|
||||||
#include "resip/stack/SipMessage.hxx"
|
|
||||||
#include "resip/stack/ShutdownMessage.hxx"
|
|
||||||
#include "resip/stack/SipStack.hxx"
|
|
||||||
#include "resip/stack/InternalTransport.hxx"
|
|
||||||
#include "resip/dum/ClientAuthManager.hxx"
|
|
||||||
#include "resip/dum/ClientInviteSession.hxx"
|
|
||||||
#include "resip/dum/ClientRegistration.hxx"
|
|
||||||
#include "resip/dum/DialogUsageManager.hxx"
|
|
||||||
#include "resip/dum/DumShutdownHandler.hxx"
|
|
||||||
#include "resip/dum/InviteSessionHandler.hxx"
|
|
||||||
#include "resip/dum/MasterProfile.hxx"
|
|
||||||
#include "resip/dum/RegistrationHandler.hxx"
|
|
||||||
#include "resip/dum/ServerInviteSession.hxx"
|
|
||||||
#include "resip/dum/ServerOutOfDialogReq.hxx"
|
|
||||||
#include "resip/dum/OutOfDialogHandler.hxx"
|
|
||||||
#include "resip/dum/AppDialog.hxx"
|
|
||||||
#include "resip/dum/AppDialogSet.hxx"
|
|
||||||
#include "resip/dum/AppDialogSetFactory.hxx"
|
|
||||||
#include "resip/dum/ClientPublication.hxx"
|
|
||||||
#include "resip/dum/ClientSubscription.hxx"
|
|
||||||
#include "resip/dum/SubscriptionHandler.hxx"
|
|
||||||
#include "resip/dum/PagerMessageHandler.hxx"
|
|
||||||
#include "resip/dum/PublicationHandler.hxx"
|
|
||||||
#include "resip/dum/ClientPagerMessage.hxx"
|
|
||||||
#include "resip/dum/ServerPagerMessage.hxx"
|
|
||||||
|
|
||||||
#include "rutil/Log.hxx"
|
|
||||||
#include "rutil/Logger.hxx"
|
|
||||||
#include "rutil/Random.hxx"
|
|
||||||
#include "rutil/WinLeakCheck.hxx"
|
|
||||||
#include "rutil/DnsUtil.hxx"
|
|
||||||
#include "resip/stack/DnsResult.hxx"
|
|
||||||
#include "resip/stack/SipStack.hxx"
|
|
||||||
#include "rutil/dns/RRVip.hxx"
|
|
||||||
#include "rutil/dns/QueryTypes.hxx"
|
|
||||||
#include "rutil/dns/DnsStub.hxx"
|
|
||||||
#include "../ice/ICEBox.h"
|
|
||||||
#include "../ice/ICETime.h"
|
|
||||||
#include <sstream>
|
|
||||||
#include <time.h>
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "EP_Session.h"
|
|
||||||
#include "EP_Observer.h"
|
|
||||||
#include "EP_DataProvider.h"
|
|
||||||
#include "../helper/HL_VariantMap.h"
|
|
||||||
#include "../helper/HL_SocketHeap.h"
|
|
||||||
#include "../helper/HL_Sync.h"
|
|
||||||
#include "../helper/HL_ByteBuffer.h"
|
|
||||||
#include "../media/MT_Stream.h"
|
|
||||||
|
|
||||||
#define RESIPROCATE_SUBSYSTEM Subsystem::TEST
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
TransportType_Any,
|
|
||||||
TransportType_Udp,
|
|
||||||
TransportType_Tcp,
|
|
||||||
TransportType_Tls
|
|
||||||
};
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
CONFIG_IPV4 = 0, // Use IP4
|
|
||||||
CONFIG_IPV6, // Use IP6.
|
|
||||||
CONFIG_USERNAME, // Username. String value.
|
|
||||||
CONFIG_DOMAIN, // Domain. String value.
|
|
||||||
CONFIG_PASSWORD, // Password. String value.
|
|
||||||
CONFIG_RINSTANCE, // Determines if SIP rinstance field has to be used during registration. Boolean value.
|
|
||||||
CONFIG_INSTANCE_ID, // Instance id. It is alternative option to rinstance.
|
|
||||||
CONFIG_DISPLAYNAME, // Optional user display name. String value.
|
|
||||||
CONFIG_DOMAINPORT, // Optional domain port number. Integer value.
|
|
||||||
CONFIG_REGISTERDURATION, // Wanted duration for registration. Integer value. It is MANDATORY value.
|
|
||||||
CONFIG_RPORT, // Use SIP rport field. Recommended to set it to true. Boolean value.
|
|
||||||
CONFIG_KEEPALIVETIME, // Interval between UDP keep-alive messages. Boolean value.
|
|
||||||
CONFIG_RELAY, // Sets if TURN server must be used instead of STUN. Boolean value.
|
|
||||||
CONFIG_ICETIMEOUT, // Optional timeout for ICE connectivity checks and candidate gathering. Integer value.
|
|
||||||
CONFIG_ICEUSERNAME, // Optional username for TURN server. String value.
|
|
||||||
CONFIG_ICEPASSWORD, // Optional password for TURN server. String value.
|
|
||||||
CONFIG_SIPS, // Marks if account credentials are sips: scheme. Boolean value.
|
|
||||||
CONFIG_STUNSERVER_IP, // Optional IP address of STUN/TURN server. String value. It is better to use CONFIG_STUNSERVER_NAME.
|
|
||||||
CONFIG_STUNSERVER_NAME, // Host name of STUN/TURN server. stun.xten.com for example. String value.
|
|
||||||
CONFIG_STUNSERVER_PORT, // Port number of STUN/TURN server. Integer value.
|
|
||||||
CONFIG_USERAGENT, // Name of user agent in SIP headers. String value.
|
|
||||||
CONFIG_ICEREQUIRED, // ICE MUST be present in remote peer offers and answers. Boolean value.
|
|
||||||
CONFIG_TRANSPORT, // 0 - all transports, 1 - UDP, 2 - TCP, 3 - TLS,
|
|
||||||
CONFIG_SUBSCRIPTION_TIME, // Subscription time (in seconds)
|
|
||||||
CONFIG_SUBSCRIPTION_REFRESHTIME, // Refresh interval for subscriptions
|
|
||||||
CONFIG_DNS_CACHE_TIME, // DNS cache time; default is 86400 seconds
|
|
||||||
CONFIG_PRESENCE_ID, // Tuple ID used in presence publishing; determines source device
|
|
||||||
CONFIG_ROOTCERT, // Additional root cert in PEM format; string.
|
|
||||||
CONFIG_CACHECREDENTIALS, // Attempt to cache credentials that comes in response from PBX. Use them when possible to reduce number of steps of SIP transaction
|
|
||||||
CONFIG_RTCP_ATTR, // Use "rtcp" attribute in sdp. Default value is true.
|
|
||||||
CONFIG_MULTIPLEXING, // Do rtp/rtcp multiplexing
|
|
||||||
CONFIG_DEFERRELAYED, // Defer relayed media path
|
|
||||||
CONFIG_PROXY, // Proxy host name or IP address
|
|
||||||
CONFIG_PROXYPORT, // Proxy port number
|
|
||||||
CONFIG_CODEC_PRIORITY, // Another VariantMap with codec priorities,
|
|
||||||
CONFIG_ACCOUNT, // VariantMap with account configuration
|
|
||||||
CONFIG_EXTERNALIP, // Use external/public IP in outgoing requests
|
|
||||||
CONFIG_OWN_DNS, // Use predefined DNS servers
|
|
||||||
CONFIG_REGID // reg-id value from RFC5626
|
|
||||||
};
|
|
||||||
|
|
||||||
// Conntype parameter for OnSessionEstablished event
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
EV_SIP = 1,
|
|
||||||
EV_ICE = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserAgent;
|
|
||||||
|
|
||||||
// Define a type for asynchronous requests to user agent
|
|
||||||
class SIPAction
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void Run(UserAgent& ua) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::vector<SIPAction*> SIPActionVector;
|
|
||||||
|
|
||||||
// Session termination reason
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Error,
|
|
||||||
Timeout,
|
|
||||||
Replaced,
|
|
||||||
LocalBye,
|
|
||||||
RemoteBye,
|
|
||||||
LocalCancel,
|
|
||||||
RemoteCancel,
|
|
||||||
Rejected, //Only as UAS, UAC has distinct onFailure callback
|
|
||||||
Referred
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserAgent: public resip::ClientRegistrationHandler,
|
|
||||||
public resip::InviteSessionHandler,
|
|
||||||
public resip::DumShutdownHandler,
|
|
||||||
public resip::ExternalLogger,
|
|
||||||
public resip::DnsResultSink,
|
|
||||||
public resip::ClientSubscriptionHandler,
|
|
||||||
public resip::ServerSubscriptionHandler,
|
|
||||||
public resip::ClientPagerMessageHandler,
|
|
||||||
public resip::ServerPagerMessageHandler,
|
|
||||||
public resip::ClientPublicationHandler
|
|
||||||
//public resip::InternalTransport::TransportLogger
|
|
||||||
{
|
|
||||||
friend class Account;
|
|
||||||
friend class Session;
|
|
||||||
friend class ResipSession;
|
|
||||||
friend class NATDecorator;
|
|
||||||
friend class WatcherQueue;
|
|
||||||
public:
|
|
||||||
/* Compares two sip addresses. Returns true if they represent the same entity - user and domain are the same. Otherwise returns false. */
|
|
||||||
static bool compareSipAddresses(const std::string& sip1, const std::string& sip2);
|
|
||||||
static std::string formatSipAddress(const std::string& sip);
|
|
||||||
static bool isSipAddressValid(const std::string& sip);
|
|
||||||
struct SipAddress
|
|
||||||
{
|
|
||||||
bool mValid;
|
|
||||||
std::string mScheme;
|
|
||||||
std::string mUsername;
|
|
||||||
std::string mDomain;
|
|
||||||
std::string mDisplayname;
|
|
||||||
};
|
|
||||||
|
|
||||||
static SipAddress parseSipAddress(const std::string& sip);
|
|
||||||
|
|
||||||
UserAgent();
|
|
||||||
virtual ~UserAgent();
|
|
||||||
|
|
||||||
/* Brings user agent online. Basically it creates a signalling socket(s).
|
|
||||||
This is asynchronous method. */
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/* Shutdowns user agent. It closes all sessions, tries to unregister from server and disconnects from it.
|
|
||||||
This is asynchronous method. onStop() event will be called later */
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/* Emergency stop. Please always call shutdown() before this. Kills registration, sessions & presence - everything. onStop() is called in context of this method. */
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
/* Checks if user agent is active (started). */
|
|
||||||
bool active();
|
|
||||||
|
|
||||||
/* Used to refresh existing registration(s), publication, subscriptions. */
|
|
||||||
void refresh();
|
|
||||||
|
|
||||||
/* Runs sip & ice stacks. Event handlers are called in its context. */
|
|
||||||
void process();
|
|
||||||
|
|
||||||
/* Adds root cert in PEM format. Usable after start() call. */
|
|
||||||
void addRootCert(const ByteBuffer& data);
|
|
||||||
|
|
||||||
PAccount createAccount(PVariantMap config);
|
|
||||||
void deleteAccount(PAccount account);
|
|
||||||
|
|
||||||
/* Creates session. Returns session ID. */
|
|
||||||
PSession createSession(PAccount account);
|
|
||||||
|
|
||||||
// Must be called when IP interface list is changed
|
|
||||||
void updateInterfaceList();
|
|
||||||
|
|
||||||
// Called on new incoming session; providers shoukld
|
|
||||||
virtual PDataProvider onProviderNeeded(const std::string& name) = 0;
|
|
||||||
|
|
||||||
// Called on new session offer
|
|
||||||
virtual void onNewSession(PSession s) = 0;
|
|
||||||
|
|
||||||
// Called when session is terminated
|
|
||||||
virtual void onSessionTerminated(PSession s, int responsecode, int reason) = 0;
|
|
||||||
|
|
||||||
// Called when session is established ok i.e. after all ICE signalling is finished
|
|
||||||
// Conntype is type of establish event - EV_SIP or EV_ICE
|
|
||||||
virtual void onSessionEstablished(PSession s, int conntype, const RtpPair<InternetAddress>& p) = 0;
|
|
||||||
|
|
||||||
// Called when client session gets
|
|
||||||
virtual void onSessionProvisional(PSession s, int code) = 0;
|
|
||||||
|
|
||||||
// Called when user agent started
|
|
||||||
virtual void onStart(int errorcode) = 0;
|
|
||||||
|
|
||||||
// Called when user agent stopped
|
|
||||||
virtual void onStop() = 0;
|
|
||||||
|
|
||||||
// Called when account registered
|
|
||||||
virtual void onAccountStart(PAccount account) = 0;
|
|
||||||
|
|
||||||
// Called when account removed or failed (non zero error code)
|
|
||||||
virtual void onAccountStop(PAccount account, int error) = 0;
|
|
||||||
|
|
||||||
// Called when connectivity checks failed.
|
|
||||||
virtual void onConnectivityFailed(PSession s) = 0;
|
|
||||||
|
|
||||||
// Called when new candidate is gathered
|
|
||||||
virtual void onCandidateGathered(PSession s, const char* address);
|
|
||||||
|
|
||||||
// Called when network change detected
|
|
||||||
virtual void onNetworkChange(PSession s) = 0;
|
|
||||||
|
|
||||||
// Called when all candidates are gathered
|
|
||||||
virtual void onGathered(PSession s);
|
|
||||||
|
|
||||||
// Called when new connectivity check is finished
|
|
||||||
virtual void onCheckFinished(PSession s, const char* description);
|
|
||||||
|
|
||||||
// Called when log message must be recorded
|
|
||||||
virtual void onLog(const char* msg);
|
|
||||||
|
|
||||||
// Called when problem with SIP connection(s) detected
|
|
||||||
virtual void onSipConnectionFailed() = 0;
|
|
||||||
|
|
||||||
// Subscribe/publish presence methods
|
|
||||||
virtual void onPublicationSuccess(PAccount acc);
|
|
||||||
virtual void onPublicationTerminated(PAccount acc, int code);
|
|
||||||
virtual void onClientObserverStart(PClientObserver observer);
|
|
||||||
virtual void onServerObserverStart(PServerObserver observer);
|
|
||||||
virtual void onClientObserverStop(PClientObserver observer, int code);
|
|
||||||
virtual void onServerObserverStop(PServerObserver observer, int code);
|
|
||||||
|
|
||||||
virtual void onPresenceUpdate(PClientObserver observer, const std::string& peer, bool online, const std::string& content);
|
|
||||||
virtual void onMessageArrived(PAccount account, const std::string& peer, const void* ptr, unsigned length);
|
|
||||||
virtual void onMessageFailed(PAccount account, int id, const std::string& peer, int code, void* tag);
|
|
||||||
virtual void onMessageSent(PAccount account, int id, const std::string& peer, void* tag);
|
|
||||||
|
|
||||||
// Configuration methods
|
|
||||||
VariantMap& config();
|
|
||||||
|
|
||||||
public:
|
|
||||||
// InviteSessionHandler implementation
|
|
||||||
#pragma region InviteSessionHandler implementation
|
|
||||||
/// called when an initial INVITE or the intial response to an outoing invite
|
|
||||||
virtual void onNewSession(resip::ClientInviteSessionHandle, resip::InviteSession::OfferAnswerType oat, const resip::SipMessage& msg) override;
|
|
||||||
virtual void onNewSession(resip::ServerInviteSessionHandle, resip::InviteSession::OfferAnswerType oat, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// Received a failure response from UAS
|
|
||||||
virtual void onFailure(resip::ClientInviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when an in-dialog provisional response is received that contains an SDP body
|
|
||||||
virtual void onEarlyMedia(resip::ClientInviteSessionHandle, const resip::SipMessage&, const resip::SdpContents&) override;
|
|
||||||
|
|
||||||
/// called when dialog enters the Early state - typically after getting 18x
|
|
||||||
virtual void onProvisional(resip::ClientInviteSessionHandle, const resip::SipMessage&) override;
|
|
||||||
|
|
||||||
/// called when a dialog initiated as a UAC enters the connected state
|
|
||||||
virtual void onConnected(resip::ClientInviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when a dialog initiated as a UAS enters the connected state
|
|
||||||
virtual void onConnected(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
virtual void onTerminated(resip::InviteSessionHandle, resip::InviteSessionHandler::TerminatedReason reason, const resip::SipMessage* related=0) override;
|
|
||||||
|
|
||||||
/// called when a fork that was created through a 1xx never receives a 2xx
|
|
||||||
/// because another fork answered and this fork was canceled by a proxy.
|
|
||||||
virtual void onForkDestroyed(resip::ClientInviteSessionHandle) override;
|
|
||||||
|
|
||||||
/// called when a 3xx with valid targets is encountered in an early dialog
|
|
||||||
/// This is different then getting a 3xx in onTerminated, as another
|
|
||||||
/// request will be attempted, so the DialogSet will not be destroyed.
|
|
||||||
/// Basically an onTermintated that conveys more information.
|
|
||||||
/// checking for 3xx respones in onTerminated will not work as there may
|
|
||||||
/// be no valid targets.
|
|
||||||
virtual void onRedirected(resip::ClientInviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when an SDP answer is received - has nothing to do with user
|
|
||||||
/// answering the call
|
|
||||||
virtual void onAnswer(resip::InviteSessionHandle, const resip::SipMessage& msg, const resip::SdpContents&) override;
|
|
||||||
|
|
||||||
/// called when an SDP offer is received - must send an answer soon after this
|
|
||||||
virtual void onOffer(resip::InviteSessionHandle, const resip::SipMessage& msg, const resip::SdpContents&) override;
|
|
||||||
|
|
||||||
/// called when an Invite w/out SDP is sent, or any other context which
|
|
||||||
/// requires an SDP offer from the user
|
|
||||||
virtual void onOfferRequired(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called if an offer in a UPDATE or re-INVITE was rejected - not real
|
|
||||||
/// useful. A SipMessage is provided if one is available
|
|
||||||
virtual void onOfferRejected(resip::InviteSessionHandle, const resip::SipMessage* msg) override;
|
|
||||||
|
|
||||||
/// called when INFO message is received
|
|
||||||
virtual void onInfo(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when response to INFO message is received
|
|
||||||
virtual void onInfoSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
virtual void onInfoFailure(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when MESSAGE message is received
|
|
||||||
virtual void onMessage(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when response to MESSAGE message is received
|
|
||||||
virtual void onMessageSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
virtual void onMessageFailure(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when an REFER message is received. The refer is accepted or
|
|
||||||
/// rejected using the server subscription. If the offer is accepted,
|
|
||||||
/// DialogUsageManager::makeInviteSessionFromRefer can be used to create an
|
|
||||||
/// InviteSession that will send notify messages using the ServerSubscription
|
|
||||||
virtual void onRefer(resip::InviteSessionHandle, resip::ServerSubscriptionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
virtual void onReferNoSub(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when an REFER message receives a failure response
|
|
||||||
virtual void onReferRejected(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
|
|
||||||
|
|
||||||
/// called when an REFER message receives an accepted response
|
|
||||||
virtual void onReferAccepted(resip::InviteSessionHandle, resip::ClientSubscriptionHandle, const resip::SipMessage& msg) override;
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
// ClientRegistrationHandler implementation
|
|
||||||
#pragma region ClientRegistrationHandler implementation
|
|
||||||
/// Called when registraion succeeds or each time it is sucessfully
|
|
||||||
/// refreshed.
|
|
||||||
void onSuccess(resip::ClientRegistrationHandle, const resip::SipMessage& response) override;
|
|
||||||
|
|
||||||
// Called when all of my bindings have been removed
|
|
||||||
void onRemoved(resip::ClientRegistrationHandle, const resip::SipMessage& response) override;
|
|
||||||
|
|
||||||
/// call on Retry-After failure.
|
|
||||||
/// return values: -1 = fail, 0 = retry immediately, N = retry in N seconds
|
|
||||||
int onRequestRetry(resip::ClientRegistrationHandle, int retrySeconds, const resip::SipMessage& response) override;
|
|
||||||
|
|
||||||
/// Called if registration fails, usage will be destroyed (unless a
|
|
||||||
/// Registration retry interval is enabled in the Profile)
|
|
||||||
void onFailure(resip::ClientRegistrationHandle, const resip::SipMessage& response) override;
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region ExternalLogger implementation
|
|
||||||
/** return true to also do default logging, false to suppress default logging. */
|
|
||||||
virtual bool operator()(resip::Log::Level level,
|
|
||||||
const resip::Subsystem& subsystem,
|
|
||||||
const resip::Data& appName,
|
|
||||||
const char* file,
|
|
||||||
int line,
|
|
||||||
const resip::Data& message,
|
|
||||||
const resip::Data& messageWithHeaders,
|
|
||||||
const resip::Data& instanceName) override;
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region DnsResultSink implementation
|
|
||||||
|
|
||||||
virtual void onDnsResult(const resip::DNSResult<resip::DnsHostRecord>&) override;
|
|
||||||
virtual void onDnsResult(const resip::DNSResult<resip::DnsAAAARecord>&) override;
|
|
||||||
virtual void onDnsResult(const resip::DNSResult<resip::DnsSrvRecord>&) override;
|
|
||||||
virtual void onDnsResult(const resip::DNSResult<resip::DnsNaptrRecord>&) override;
|
|
||||||
virtual void onDnsResult(const resip::DNSResult<resip::DnsCnameRecord>&) override;
|
|
||||||
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region TransportLogger implementation
|
|
||||||
void onSipMessage(int flow, const char* msg, unsigned int length, const sockaddr* addr, unsigned int addrlen);
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region ClientPublicationHandler
|
|
||||||
void onSuccess(resip::ClientPublicationHandle, const resip::SipMessage& status) override;
|
|
||||||
void onRemove(resip::ClientPublicationHandle, const resip::SipMessage& status) override;
|
|
||||||
void onFailure(resip::ClientPublicationHandle, const resip::SipMessage& status) override;
|
|
||||||
int onRequestRetry(resip::ClientPublicationHandle, int retrySeconds, const resip::SipMessage& status) override;
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region SubscriptionHandler
|
|
||||||
void onUpdate(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify);
|
|
||||||
void onUpdatePending(resip::ClientSubscriptionHandle, const resip::SipMessage& notify, bool outOfOrder) override;
|
|
||||||
void onUpdateActive(resip::ClientSubscriptionHandle, const resip::SipMessage& notify, bool outOfOrder) override;
|
|
||||||
|
|
||||||
//unknown Subscription-State value
|
|
||||||
void onUpdateExtension(resip::ClientSubscriptionHandle, const resip::SipMessage& notify, bool outOfOrder) override;
|
|
||||||
int onRequestRetry(resip::ClientSubscriptionHandle, int retrySeconds, const resip::SipMessage& notify) override;
|
|
||||||
|
|
||||||
//subscription can be ended through a notify or a failure response.
|
|
||||||
void onTerminated(resip::ClientSubscriptionHandle, const resip::SipMessage* msg) override;
|
|
||||||
//not sure if this has any value.
|
|
||||||
void onNewSubscription(resip::ClientSubscriptionHandle, const resip::SipMessage& notify) override;
|
|
||||||
|
|
||||||
/// called to allow app to adorn a message.
|
|
||||||
void onReadyToSend(resip::ClientSubscriptionHandle, resip::SipMessage& msg) override;
|
|
||||||
void onNotifyNotReceived(resip::ClientSubscriptionHandle) override;
|
|
||||||
|
|
||||||
/// Called when a TCP or TLS flow to the server has terminated. This can be caused by socket
|
|
||||||
/// errors, or missing CRLF keep alives pong responses from the server.
|
|
||||||
// Called only if clientOutbound is enabled on the UserProfile and the first hop server
|
|
||||||
/// supports RFC5626 (outbound).
|
|
||||||
/// Default implementation is to re-form the subscription using a new flow
|
|
||||||
void onFlowTerminated(resip::ClientSubscriptionHandle) override;
|
|
||||||
void onNewSubscription(resip::ServerSubscriptionHandle, const resip::SipMessage& sub) override;
|
|
||||||
void onTerminated(resip::ServerSubscriptionHandle) override;
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
#pragma region PagerHandler
|
|
||||||
void onSuccess(resip::ClientPagerMessageHandle, const resip::SipMessage& status) override;
|
|
||||||
void onFailure(resip::ClientPagerMessageHandle, const resip::SipMessage& status, std::unique_ptr<resip::Contents> contents) override;
|
|
||||||
void onMessageArrived(resip::ServerPagerMessageHandle, const resip::SipMessage& message) override;
|
|
||||||
#pragma endregion
|
|
||||||
|
|
||||||
void onDumCanBeDeleted() override;
|
|
||||||
protected:
|
|
||||||
// Mutex to protect this instance
|
|
||||||
Mutex mGuard;
|
|
||||||
|
|
||||||
// Smart pointer to resiprocate's master profile instance. The stack configuration holds here.
|
|
||||||
std::shared_ptr<resip::MasterProfile> mProfile;
|
|
||||||
|
|
||||||
// Resiprocate's SIP stack object pointer
|
|
||||||
resip::SipStack* mStack;
|
|
||||||
|
|
||||||
// Resiprocate's dialog usage manager object pointer
|
|
||||||
resip::DialogUsageManager* mDum;
|
|
||||||
|
|
||||||
// List of available transports. They are owned by SipStack - so there is no need to delete instances in UserAgent.
|
|
||||||
std::vector<resip::InternalTransport*> mTransportList;
|
|
||||||
|
|
||||||
typedef std::map<int, PSession> SessionMap;
|
|
||||||
|
|
||||||
// Session's map
|
|
||||||
SessionMap mSessionMap;
|
|
||||||
|
|
||||||
// Used configuration
|
|
||||||
VariantMap mConfig;
|
|
||||||
|
|
||||||
// Action vector
|
|
||||||
SIPActionVector mActionVector;
|
|
||||||
|
|
||||||
typedef std::map<int, PClientObserver> ClientObserverMap;
|
|
||||||
ClientObserverMap mClientObserverMap;
|
|
||||||
|
|
||||||
typedef std::map<int, PServerObserver> ServerObserverMap;
|
|
||||||
ServerObserverMap mServerObserverMap;
|
|
||||||
|
|
||||||
typedef std::set<PAccount> AccountSet;
|
|
||||||
AccountSet mAccountSet;
|
|
||||||
|
|
||||||
// Constructs and sends INVITE to remote peer. Remote peer address is stored inside session object.
|
|
||||||
void sendOffer(Session* session);
|
|
||||||
void internalStopSession(Session& session);
|
|
||||||
void processWatchingList();
|
|
||||||
bool handleMultipartRelatedNotify(const resip::SipMessage& notify);
|
|
||||||
|
|
||||||
PSession getUserSession(int sessionId);
|
|
||||||
PAccount getAccount(const resip::NameAddr& myAddr);
|
|
||||||
PAccount getAccount(Account* account);
|
|
||||||
PAccount getAccount(int sessionId);
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
#include "EP_NetworkQueue.h"
|
|
||||||
#include "EP_Engine.h"
|
|
||||||
|
|
||||||
WatcherQueue::WatcherQueue(UserAgent& ua)
|
|
||||||
:mActiveId(0), mAgent(ua)
|
|
||||||
{}
|
|
||||||
|
|
||||||
WatcherQueue::~WatcherQueue()
|
|
||||||
{}
|
|
||||||
|
|
||||||
int WatcherQueue::add(std::string peer, std::string package, void* tag)
|
|
||||||
{
|
|
||||||
ice::Lock l(mGuard);
|
|
||||||
|
|
||||||
// Check if queue has similar item
|
|
||||||
for (unsigned i=0; i<mItemList.size(); i++)
|
|
||||||
{
|
|
||||||
Item& item = mItemList[i];
|
|
||||||
if (item.mTarget == peer && item.mPackage == package &&
|
|
||||||
item.mState != Item::State_Deleting)
|
|
||||||
return item.mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
Item item;
|
|
||||||
item.mTarget = peer;
|
|
||||||
item.mPackage = package;
|
|
||||||
item.mTag = tag;
|
|
||||||
item.mState = Item::State_ScheduledToAdd;
|
|
||||||
item.mSession = new ResipSession(*mAgent.mDum);
|
|
||||||
item.mSession->setUa(&mAgent);
|
|
||||||
item.mSession->setType(ResipSession::Type_Subscription);
|
|
||||||
item.mSession->setTag(tag);
|
|
||||||
item.mId = item.mSession->sessionId();
|
|
||||||
item.mSession->setRemoteAddress(peer);
|
|
||||||
item.mTag = tag;
|
|
||||||
mItemList.push_back(item);
|
|
||||||
process();
|
|
||||||
|
|
||||||
return item.mId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatcherQueue::remove(int id)
|
|
||||||
{
|
|
||||||
ice::Lock l(mGuard);
|
|
||||||
|
|
||||||
// Check if queue has similar item
|
|
||||||
for (unsigned i=0; i<mItemList.size(); i++)
|
|
||||||
{
|
|
||||||
Item& item = mItemList[i];
|
|
||||||
if (item.mId == id && !id)
|
|
||||||
{
|
|
||||||
if (item.mState != Item::State_Deleting)
|
|
||||||
item.mState = Item::State_ScheduledToDelete;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void WatcherQueue::refresh(int id)
|
|
||||||
{
|
|
||||||
ice::Lock l(mGuard);
|
|
||||||
|
|
||||||
// Check if queue has similar item
|
|
||||||
for (unsigned i=0; i<mItemList.size(); i++)
|
|
||||||
{
|
|
||||||
Item& item = mItemList[i];
|
|
||||||
if (item.mId == id && !id)
|
|
||||||
{
|
|
||||||
if (item.mState == Item::State_ScheduledToDelete || item.mState == Item::State_Active)
|
|
||||||
item.mState = Item::State_ScheduledToRefresh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatcherQueue::process()
|
|
||||||
{
|
|
||||||
while (!mActiveId)
|
|
||||||
{
|
|
||||||
// Find next item to process
|
|
||||||
ItemList::iterator i = mItemList.begin();
|
|
||||||
for (;i != mItemList.end() && !i->scheduled(); i++)
|
|
||||||
;
|
|
||||||
if (i == mItemList.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::shared_ptr<resip::SipMessage> msg;
|
|
||||||
int expires = DEFAULT_SUBSCRIPTION_TIME, refresh = DEFAULT_SUBSCRIPTION_REFRESHTIME;
|
|
||||||
|
|
||||||
switch (i->mState)
|
|
||||||
{
|
|
||||||
case Item::State_ScheduledToAdd:
|
|
||||||
if (mAgent.mConfig.exists(CONFIG_SUBSCRIPTION_TIME))
|
|
||||||
expires = mAgent.mConfig[CONFIG_SUBSCRIPTION_TIME].asInt();
|
|
||||||
if (mAgent.mConfig.exists(CONFIG_SUBSCRIPTION_REFRESHTIME))
|
|
||||||
refresh = mAgent.mConfig[CONFIG_SUBSCRIPTION_REFRESHTIME].asInt();
|
|
||||||
|
|
||||||
msg = mAgent.mDum->makeSubscription(resip::NameAddr(resip::Data(i->mTarget)), resip::Data(i->mPackage),
|
|
||||||
expires, refresh, i->mSession);
|
|
||||||
msg->header(resip::h_Accepts) = mAgent.mDum->getMasterProfile()->getSupportedMimeTypes(resip::NOTIFY);
|
|
||||||
mActiveId = i->mId;
|
|
||||||
i->mState = Item::State_Adding;
|
|
||||||
mAgent.mDum->send(msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Item::State_ScheduledToDelete:
|
|
||||||
i->mSession->runTerminatedEvent(ResipSession::Type_Subscription, 0, 0);
|
|
||||||
if (i->mHandle.isValid())
|
|
||||||
{
|
|
||||||
mActiveId = i->mId;
|
|
||||||
i->mHandle->end();
|
|
||||||
i->mState = Item::State_Deleting;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mItemList.erase(i);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Item::State_ScheduledToRefresh:
|
|
||||||
if (i->mHandle.isValid())
|
|
||||||
{
|
|
||||||
mActiveId = i->mId;
|
|
||||||
i->mState = Item::State_Refreshing;
|
|
||||||
i->mHandle->requestRefresh();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mItemList.erase(i);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatcherQueue::onTerminated(int id, int code)
|
|
||||||
{
|
|
||||||
ice::Lock l(mGuard);
|
|
||||||
ItemList::iterator i = findById(id);
|
|
||||||
if (i != mItemList.end())
|
|
||||||
{
|
|
||||||
if (i->mSession)
|
|
||||||
i->mSession->runTerminatedEvent(ResipSession::Type_Subscription, code, 0);
|
|
||||||
mItemList.erase(i);
|
|
||||||
if (i->mId == mActiveId)
|
|
||||||
mActiveId = 0;
|
|
||||||
}
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatcherQueue::onEstablished(int id, int code)
|
|
||||||
{
|
|
||||||
ice::Lock l(mGuard);
|
|
||||||
ItemList::iterator i = findById(id);
|
|
||||||
if (i != mItemList.end())
|
|
||||||
{
|
|
||||||
i->mState = Item::State_Active;
|
|
||||||
if (i->mId == mActiveId)
|
|
||||||
mActiveId = 0;
|
|
||||||
}
|
|
||||||
process();
|
|
||||||
}
|
|
||||||
|
|
||||||
WatcherQueue::ItemList::iterator WatcherQueue::findById(int id)
|
|
||||||
{
|
|
||||||
for (ItemList::iterator i=mItemList.begin(); i != mItemList.end(); i++)
|
|
||||||
if (i->mId == id)
|
|
||||||
return i;
|
|
||||||
return mItemList.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatcherQueue::clear()
|
|
||||||
{
|
|
||||||
ice::Lock l(mGuard);
|
|
||||||
for (ItemList::iterator i=mItemList.begin(); i != mItemList.end(); i++)
|
|
||||||
{
|
|
||||||
if (i->mHandle.isValid())
|
|
||||||
i->mHandle->end();
|
|
||||||
}
|
|
||||||
mItemList.clear();
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __NETWORK_QUEUE_H
|
|
||||||
#define __NETWORK_QUEUE_H
|
|
||||||
|
|
||||||
#include "EP_Session.h"
|
|
||||||
#include <resip/dum/ClientSubscription.hxx>
|
|
||||||
|
|
||||||
class UserAgent;
|
|
||||||
class WatcherQueue
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Item
|
|
||||||
{
|
|
||||||
enum State
|
|
||||||
{
|
|
||||||
State_None,
|
|
||||||
State_Active,
|
|
||||||
State_ScheduledToAdd,
|
|
||||||
State_Adding,
|
|
||||||
State_ScheduledToRefresh,
|
|
||||||
State_Refreshing,
|
|
||||||
State_ScheduledToDelete,
|
|
||||||
State_Deleting
|
|
||||||
};
|
|
||||||
|
|
||||||
resip::ClientSubscriptionHandle mHandle; // Subscription handle
|
|
||||||
ResipSession* mSession;
|
|
||||||
State mState;
|
|
||||||
std::string mTarget; // Target's address
|
|
||||||
std::string mPackage; // Event package
|
|
||||||
void* mTag; // User tag
|
|
||||||
int mId;
|
|
||||||
|
|
||||||
Item()
|
|
||||||
:mSession(NULL), mState(State_None), mTag(NULL), mId(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool scheduled()
|
|
||||||
{
|
|
||||||
return mState == State_ScheduledToAdd || mState == State_ScheduledToDelete || mState == State_ScheduledToRefresh;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
WatcherQueue(UserAgent& agent);
|
|
||||||
~WatcherQueue();
|
|
||||||
|
|
||||||
int add(std::string peer, std::string package, void* tag);
|
|
||||||
void remove(int id);
|
|
||||||
void refresh(int id);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
void onTerminated(int id, int code);
|
|
||||||
void onEstablished(int id, int code);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
typedef std::vector<Item> ItemList;
|
|
||||||
ItemList mItemList;
|
|
||||||
ice::Mutex mGuard;
|
|
||||||
UserAgent& mAgent;
|
|
||||||
int mActiveId;
|
|
||||||
|
|
||||||
void process();
|
|
||||||
ItemList::iterator findById(int id);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#include "EP_Observer.h"
|
|
||||||
#include "EP_Session.h"
|
|
||||||
|
|
||||||
#include <resip/stack/Pidf.hxx>
|
|
||||||
#include <resip/dum/ClientSubscription.hxx>
|
|
||||||
|
|
||||||
ClientObserver::ClientObserver()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientObserver::~ClientObserver()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientObserver::refresh()
|
|
||||||
{
|
|
||||||
if (mHandle.isValid())
|
|
||||||
mHandle->requestRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientObserver::stop()
|
|
||||||
{
|
|
||||||
if (mHandle.isValid())
|
|
||||||
mHandle->end();
|
|
||||||
else
|
|
||||||
if (mSession)
|
|
||||||
{
|
|
||||||
mSession->runTerminatedEvent(ResipSession::Type_Subscription);
|
|
||||||
if (mSession)
|
|
||||||
mSession->end();
|
|
||||||
}
|
|
||||||
mSession = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ClientObserver::peer()
|
|
||||||
{
|
|
||||||
return mPeer;
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerObserver::ServerObserver()
|
|
||||||
:mState(State_Incoming)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerObserver::~ServerObserver()
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ServerObserver::peer() const
|
|
||||||
{
|
|
||||||
return mPeer;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ServerObserver::package() const
|
|
||||||
{
|
|
||||||
return mPackage;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerObserver::update(std::string simpleId, bool online, std::string msg)
|
|
||||||
{
|
|
||||||
if (mState != State_Active)
|
|
||||||
return;
|
|
||||||
|
|
||||||
resip::Pidf p;
|
|
||||||
p.setEntity(mContact);
|
|
||||||
p.setSimpleId(resip::Data(simpleId));
|
|
||||||
p.setSimpleStatus(online, resip::Data(msg));
|
|
||||||
|
|
||||||
if (mHandle.isValid())
|
|
||||||
mHandle->send(mHandle->update(&p));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerObserver::accept()
|
|
||||||
{
|
|
||||||
if (mHandle.isValid() && mState == State_Incoming)
|
|
||||||
{
|
|
||||||
mState = State_Active;
|
|
||||||
mHandle->accept();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerObserver::stop()
|
|
||||||
{
|
|
||||||
if (!mHandle.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (mState)
|
|
||||||
{
|
|
||||||
case State_Incoming:
|
|
||||||
mHandle->reject(404);
|
|
||||||
break;
|
|
||||||
case State_Active:
|
|
||||||
mHandle->end();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mState = State_Closed;
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 EP_OBSERVER_H
|
|
||||||
#define EP_OBSERVER_H
|
|
||||||
|
|
||||||
#include "../helper/HL_Pointer.h"
|
|
||||||
#include "../helper/HL_VariantMap.h"
|
|
||||||
#include "ice/ICEAddress.h"
|
|
||||||
#include "ice/ICETime.h"
|
|
||||||
#include "resip/dum/UserProfile.hxx"
|
|
||||||
#include "resip/dum/ClientRegistration.hxx"
|
|
||||||
#include "resip/dum/ClientPublication.hxx"
|
|
||||||
#include "resip/stack/DnsInterface.hxx"
|
|
||||||
|
|
||||||
class UserAgent;
|
|
||||||
class Session;
|
|
||||||
class ResipSession;
|
|
||||||
|
|
||||||
class ClientObserver
|
|
||||||
{
|
|
||||||
friend class Account;
|
|
||||||
friend class UserAgent;
|
|
||||||
public:
|
|
||||||
ClientObserver();
|
|
||||||
~ClientObserver();
|
|
||||||
|
|
||||||
void refresh();
|
|
||||||
void stop();
|
|
||||||
std::string peer();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
resip::ClientSubscriptionHandle mHandle;
|
|
||||||
ResipSession* mSession;
|
|
||||||
int mSessionId;
|
|
||||||
std::string mPeer;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<ClientObserver> PClientObserver;
|
|
||||||
|
|
||||||
class ServerObserver
|
|
||||||
{
|
|
||||||
friend class UserAgent;
|
|
||||||
public:
|
|
||||||
ServerObserver();
|
|
||||||
~ServerObserver();
|
|
||||||
|
|
||||||
std::string peer() const;
|
|
||||||
std::string package() const;
|
|
||||||
|
|
||||||
void accept();
|
|
||||||
void update(std::string simpleId, bool online, std::string msg);
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
enum State
|
|
||||||
{
|
|
||||||
State_Incoming,
|
|
||||||
State_Active,
|
|
||||||
State_Closed
|
|
||||||
};
|
|
||||||
State mState;
|
|
||||||
resip::ServerSubscriptionHandle mHandle;
|
|
||||||
std::string mPeer, mPackage;
|
|
||||||
resip::Uri mContact;
|
|
||||||
int mSessionId;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<ServerObserver> PServerObserver;
|
|
||||||
|
|
||||||
#endif // EP_OBSERVER_H
|
|
||||||
|
|
@ -1,304 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2007-2012 Dmytro Bogovych <dmytro.bogovych@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*/
|
|
||||||
#ifdef _WIN32
|
|
||||||
# include <winsock2.h>
|
|
||||||
# include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include "EP_ReliableTunnel.h"
|
|
||||||
#include "EP_Engine.h"
|
|
||||||
#include "Log.h"
|
|
||||||
|
|
||||||
#include "../ICE/ICECRC32.h"
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
CONFIRMATION_PT = 1,
|
|
||||||
DATA_PT = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
#define CONFIRMATION_TIMEOUT 500
|
|
||||||
#define LOG_SUBSYSTEM "RT"
|
|
||||||
|
|
||||||
ReliableTunnel::ReliableTunnel(const char* streamname)
|
|
||||||
{
|
|
||||||
mStack.setEncryption(this);
|
|
||||||
mStreamName = streamname;
|
|
||||||
mBandwidth = 0;
|
|
||||||
mExitSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
mDataSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ReliableTunnel::~ReliableTunnel()
|
|
||||||
{
|
|
||||||
::CloseHandle(mDataSignal);
|
|
||||||
::CloseHandle(mExitSignal);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReliableTunnel::streamName()
|
|
||||||
{
|
|
||||||
return mStreamName;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ReliableTunnel::streamProfile()
|
|
||||||
{
|
|
||||||
return "RTP/DP";
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::setDestinationAddress(InternetAddress& addr)
|
|
||||||
{
|
|
||||||
mDestination = addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ReliableTunnel::queueData(const void* bufferptr, int buffersize)
|
|
||||||
{
|
|
||||||
assert(bufferptr != NULL);
|
|
||||||
assert(buffersize != 0);
|
|
||||||
|
|
||||||
resip::Lock l(mNewQueuedGuard);
|
|
||||||
mNewQueued.push_back(std::string((const char*)bufferptr, buffersize));
|
|
||||||
|
|
||||||
::SetEvent(mDataSignal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is called by user agent to send ICE packet from mediasocket
|
|
||||||
void ReliableTunnel::sendData(InternetAddress& addr, const void* dataBuffer, unsigned int datasize)
|
|
||||||
{
|
|
||||||
switch (addr.type())
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
mSocket4.sendDatagram(addr, dataBuffer, datasize);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
mSocket4.sendDatagram(addr, dataBuffer, datasize);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::sessionEstablished(int conntype)
|
|
||||||
{
|
|
||||||
// Start worker thread
|
|
||||||
if (conntype == EV_ICE)
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::sessionTerminated()
|
|
||||||
{
|
|
||||||
// Stop worker thread
|
|
||||||
::SetEvent(mExitSignal);
|
|
||||||
shutdown();
|
|
||||||
join();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::updateSdpOffer(resip::SdpContents::Session::Medium& sdp)
|
|
||||||
{
|
|
||||||
// Get new destination port
|
|
||||||
mDestination.setPort((unsigned short)sdp.port());
|
|
||||||
|
|
||||||
sdp.addCodec(resip::SdpContents::Session::Codec("rt", 104));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::setSocket(DatagramSocket& socket4, DatagramSocket& socket6)
|
|
||||||
{
|
|
||||||
mSocket4 = socket4;
|
|
||||||
mSocket6 = socket6;
|
|
||||||
}
|
|
||||||
|
|
||||||
DatagramSocket& ReliableTunnel::socket(int family)
|
|
||||||
{
|
|
||||||
switch (family)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
return mSocket4;
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
return mSocket4;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReliableTunnel::processSdpOffer(const resip::SdpContents::Session::Medium& media)
|
|
||||||
{
|
|
||||||
//check for default port number
|
|
||||||
mDestination.setPort(media.port());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::thread()
|
|
||||||
{
|
|
||||||
// Construct event array
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
HANDLE eventarray[2] = { mDataSignal, mExitSignal };
|
|
||||||
|
|
||||||
DWORD rescode = ::WaitForMultipleObjects(2, eventarray, FALSE, INFINITE);
|
|
||||||
if (rescode == WAIT_OBJECT_0)
|
|
||||||
{
|
|
||||||
resip::Lock l(mNewQueuedGuard);
|
|
||||||
for (unsigned i = 0; i<mNewQueued.size(); i++)
|
|
||||||
mStack.queueOutgoing(mNewQueued[i].c_str(), mNewQueued[i].size());
|
|
||||||
mNewQueued.clear();
|
|
||||||
|
|
||||||
sendOutgoing();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::setBandwidth(unsigned int bytesPerSecond)
|
|
||||||
{
|
|
||||||
mBandwidth = bytesPerSecond;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int ReliableTunnel::bandwidth()
|
|
||||||
{
|
|
||||||
return mBandwidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::processData(const void* dataptr, int datasize)
|
|
||||||
{
|
|
||||||
resip::Lock l(mStackGuard);
|
|
||||||
mStack.processIncoming(dataptr, datasize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReliableTunnel::hasData()
|
|
||||||
{
|
|
||||||
resip::Lock l(mStackGuard);
|
|
||||||
|
|
||||||
return mIncomingData.size() || mStack.hasAppData();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned ReliableTunnel::getData(void* ptr, unsigned capacity)
|
|
||||||
{
|
|
||||||
resip::Lock l(mStackGuard);
|
|
||||||
|
|
||||||
char* dataOut = (char*)ptr;
|
|
||||||
|
|
||||||
while (capacity && hasData())
|
|
||||||
{
|
|
||||||
// Check if mIncomingData is empty
|
|
||||||
if (!mIncomingData.size())
|
|
||||||
{
|
|
||||||
unsigned available = mStack.appData(NULL);
|
|
||||||
if (!available)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
mIncomingData.resize(available);
|
|
||||||
mIncomingData.rewind();
|
|
||||||
mStack.appData(mIncomingData.mutableData());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mIncomingData.size())
|
|
||||||
{
|
|
||||||
unsigned toCopy = min(capacity, mIncomingData.size());
|
|
||||||
mIncomingData.dequeueBuffer(dataOut, toCopy);
|
|
||||||
dataOut += toCopy;
|
|
||||||
capacity -= toCopy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataOut - (char*)ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns block size for encryption algorythm
|
|
||||||
int ReliableTunnel::blockSize()
|
|
||||||
{
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypts dataPtr buffer inplace. dataSize must be odd to GetBlockSize() returned value.
|
|
||||||
void ReliableTunnel::encrypt(void* dataPtr, int dataSize)
|
|
||||||
{
|
|
||||||
if (mEncryptionKey.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
#ifdef USE_OPENSSL
|
|
||||||
for (unsigned i=0; i<dataSize / blockSize(); i++)
|
|
||||||
BF_ecb_encrypt((unsigned char*)dataPtr + i * blockSize(), (unsigned char*)dataPtr + i * blockSize(), &mCipher, BF_ENCRYPT);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CRYPTOPP
|
|
||||||
for (unsigned i=0; i<dataSize / blockSize(); i++)
|
|
||||||
mEncryptor.ProcessBlock((unsigned char*)dataPtr + i * blockSize());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypts dataPtr buffer inplace. dataSize must be odd to GetBlockSize() returned value.
|
|
||||||
void ReliableTunnel::decrypt(void* dataPtr, int dataSize)
|
|
||||||
{
|
|
||||||
if (mEncryptionKey.empty())
|
|
||||||
return;
|
|
||||||
#ifdef USE_OPENSSL
|
|
||||||
for (unsigned i=0; i<dataSize / blockSize(); i++)
|
|
||||||
BF_ecb_encrypt((unsigned char*)dataPtr + i * blockSize(), (unsigned char*)dataPtr + i * blockSize(), &mCipher, BF_DECRYPT);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CRYPTOPP
|
|
||||||
for (unsigned i=0; i<dataSize / blockSize(); i++)
|
|
||||||
mDecryptor.ProcessBlock((unsigned char*)dataPtr + i * blockSize());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculates CRC
|
|
||||||
unsigned ReliableTunnel::crc(const void* dataptr, int datasize)
|
|
||||||
{
|
|
||||||
unsigned long result;
|
|
||||||
ICEImpl::CRC32 crc;
|
|
||||||
crc.fullCrc((const unsigned char*)dataptr, datasize, &result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::sendOutgoing()
|
|
||||||
{
|
|
||||||
// Check if stack has to send smth
|
|
||||||
if (mStack.hasPacketToSend())
|
|
||||||
{
|
|
||||||
// Get data to send
|
|
||||||
char buffer[2048];
|
|
||||||
int length = sizeof(buffer);
|
|
||||||
mStack.getPacketToSend(buffer, length);
|
|
||||||
|
|
||||||
// Send it over UDP
|
|
||||||
sendData(this->mDestination, buffer, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReliableTunnel::setEncryptionKey(void* ptr, unsigned length)
|
|
||||||
{
|
|
||||||
#ifdef USE_OPENSSL
|
|
||||||
BF_set_key(&mCipher, length, (const unsigned char*)ptr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_CRYPTOPP
|
|
||||||
mEncryptor.SetKey((unsigned char*)ptr, length);
|
|
||||||
mDecryptor.SetKey((unsigned char*)ptr, length);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Save key
|
|
||||||
mEncryptionKey = std::string((const char*)ptr, length);
|
|
||||||
}
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2007-2010 Dmytro Bogovych <dmytro.bogovych@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*/
|
|
||||||
#ifndef __RELIABLE_TUNNEL_H
|
|
||||||
#define __RELIABLE_TUNNEL_H
|
|
||||||
|
|
||||||
#include "DataProvider.h"
|
|
||||||
#include "InternetAddress.h"
|
|
||||||
#include "rutil/ThreadIf.hxx"
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "../ICE/ICEReliableTransport.h"
|
|
||||||
#ifdef USE_CRYPTOPP
|
|
||||||
# include "../Libs/CryptoPP/blowfish.h"
|
|
||||||
#endif
|
|
||||||
#ifdef USE_OPENSSL
|
|
||||||
# include "../Libs/openssl/include/openssl/blowfish.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ReliableTunnel: public DataProvider, public resip::ThreadIf, public ICEImpl::ReliableTransport::Encryption
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ReliableTunnel(const char* streamname);
|
|
||||||
virtual ~ReliableTunnel();
|
|
||||||
|
|
||||||
// Returns provider RTP name
|
|
||||||
virtual std::string streamName();
|
|
||||||
|
|
||||||
// Returns provider RTP profile name
|
|
||||||
virtual std::string streamProfile();
|
|
||||||
|
|
||||||
// Sets destination IP address
|
|
||||||
virtual void setDestinationAddress(InternetAddress& addr);
|
|
||||||
|
|
||||||
// Processes incoming data
|
|
||||||
virtual void processData(const void* dataBuffer, int dataSize);
|
|
||||||
|
|
||||||
// This method is called by user agent to send ICE packet from mediasocket
|
|
||||||
virtual void sendData(InternetAddress& destination, const void* dataBuffer, unsigned int datasize);
|
|
||||||
|
|
||||||
// Updates SDP offer
|
|
||||||
virtual void updateSdpOffer(resip::SdpContents::Session::Medium& sdp);
|
|
||||||
|
|
||||||
// Called by user agent when session is terminated.
|
|
||||||
virtual void sessionTerminated();
|
|
||||||
|
|
||||||
// Called by user agent when session is started.
|
|
||||||
virtual void sessionEstablished(int conntype);
|
|
||||||
|
|
||||||
// Called by user agent to save media socket for this provider
|
|
||||||
virtual void setSocket(DatagramSocket& socket4, DatagramSocket& socket6);
|
|
||||||
|
|
||||||
// Called by user agent to get media socket for this provider
|
|
||||||
virtual DatagramSocket& socket(int family);
|
|
||||||
|
|
||||||
// Called by user agent to process media stream description from remote peer.
|
|
||||||
// Returns true if description is processed succesfully. Otherwise method returns false.
|
|
||||||
virtual bool processSdpOffer(const resip::SdpContents::Session::Medium& media);
|
|
||||||
|
|
||||||
virtual void thread();
|
|
||||||
|
|
||||||
// Enqueues outgoing packet to sending queue
|
|
||||||
void queueData(const void* bufferPtr, int bufferSize);
|
|
||||||
|
|
||||||
void setBandwidth(unsigned int bytesPerSecond);
|
|
||||||
unsigned int bandwidth();
|
|
||||||
|
|
||||||
// Checks if there is any received application data
|
|
||||||
bool hasData();
|
|
||||||
|
|
||||||
// Reads received data. If ptr is NULL - the length of available data is returned.
|
|
||||||
unsigned getData(void* ptr, unsigned capacity);
|
|
||||||
|
|
||||||
void setEncryptionKey(void* ptr, unsigned length);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// SDP's stream name
|
|
||||||
std::string mStreamName;
|
|
||||||
|
|
||||||
// Transport stack
|
|
||||||
ICEImpl::ReliableTransport mStack;
|
|
||||||
|
|
||||||
// Socket handles to operate
|
|
||||||
DatagramSocket mSocket4;
|
|
||||||
DatagramSocket mSocket6;
|
|
||||||
|
|
||||||
// Destination IP4/6 address
|
|
||||||
InternetAddress mDestination;
|
|
||||||
|
|
||||||
// Win32 exit signal
|
|
||||||
HANDLE mExitSignal;
|
|
||||||
|
|
||||||
// Win32 "new outgoing data" signal
|
|
||||||
HANDLE mDataSignal;
|
|
||||||
|
|
||||||
// Mutex to protect queuing/sending outgoing data
|
|
||||||
resip::Mutex mOutgoingMtx;
|
|
||||||
|
|
||||||
std::vector<std::string>
|
|
||||||
mNewQueued;
|
|
||||||
resip::Mutex mNewQueuedGuard;
|
|
||||||
resip::Mutex mStackGuard;
|
|
||||||
|
|
||||||
unsigned int mBandwidth;
|
|
||||||
std::string mEncryptionKey;
|
|
||||||
|
|
||||||
#ifdef USE_CRYPTOPP
|
|
||||||
CryptoPP::BlowfishEncryption mEncryptor;
|
|
||||||
CryptoPP::BlowfishDecryption mDecryptor;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef USE_OPENSSL
|
|
||||||
BF_KEY mCipher;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ICEImpl::ICEByteBuffer mIncomingData;
|
|
||||||
|
|
||||||
// Returns block size for encryption algorythm
|
|
||||||
int blockSize();
|
|
||||||
|
|
||||||
// Encrypts dataPtr buffer inplace. dataSize must be odd to GetBlockSize() returned value.
|
|
||||||
void encrypt(void* dataPtr, int dataSize);
|
|
||||||
|
|
||||||
// Decrypts dataPtr buffer inplace. dataSize must be odd to GetBlockSize() returned value.
|
|
||||||
void decrypt(void* dataPtr, int dataSize);
|
|
||||||
|
|
||||||
// Calculates CRC
|
|
||||||
unsigned crc(const void* dataptr, int datasize);
|
|
||||||
|
|
||||||
void sendOutgoing();
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,411 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __SESSION_H
|
|
||||||
#define __SESSION_H
|
|
||||||
|
|
||||||
#include "resip/stack/SdpContents.hxx"
|
|
||||||
#include "resip/stack/SipMessage.hxx"
|
|
||||||
#include "resip/stack/ShutdownMessage.hxx"
|
|
||||||
#include "resip/stack/SipStack.hxx"
|
|
||||||
#include "resip/dum/ClientAuthManager.hxx"
|
|
||||||
#include "resip/dum/ClientInviteSession.hxx"
|
|
||||||
#include "resip/dum/ClientRegistration.hxx"
|
|
||||||
#include "resip/dum/DialogUsageManager.hxx"
|
|
||||||
#include "resip/dum/DumShutdownHandler.hxx"
|
|
||||||
#include "resip/dum/InviteSessionHandler.hxx"
|
|
||||||
#include "resip/dum/MasterProfile.hxx"
|
|
||||||
#include "resip/dum/RegistrationHandler.hxx"
|
|
||||||
#include "resip/dum/ServerInviteSession.hxx"
|
|
||||||
#include "resip/dum/ServerOutOfDialogReq.hxx"
|
|
||||||
#include "resip/dum/OutOfDialogHandler.hxx"
|
|
||||||
#include "resip/dum/AppDialog.hxx"
|
|
||||||
#include "resip/dum/AppDialogSet.hxx"
|
|
||||||
#include "resip/dum/AppDialogSetFactory.hxx"
|
|
||||||
#include "rutil/Log.hxx"
|
|
||||||
#include "rutil/Logger.hxx"
|
|
||||||
#include "rutil/Random.hxx"
|
|
||||||
#include "rutil/WinLeakCheck.hxx"
|
|
||||||
|
|
||||||
#include "../ice/ICEBox.h"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <atomic>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "EP_Account.h"
|
|
||||||
#include "EP_DataProvider.h"
|
|
||||||
#include "EP_AudioProvider.h"
|
|
||||||
#include "../helper/HL_VariantMap.h"
|
|
||||||
#include "../helper/HL_SocketHeap.h"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
class UserAgent;
|
|
||||||
class ResipSession;
|
|
||||||
|
|
||||||
enum SessionInfo
|
|
||||||
{
|
|
||||||
SessionInfo_RemoteSipAddress, // remote sip address
|
|
||||||
SessionInfo_ReceivedTraffic, // amount of received traffic in session in bytes
|
|
||||||
SessionInfo_SentTraffic, // amount of sent traffic in session in bytes
|
|
||||||
SessionInfo_PacketLoss, // lost packets counter; returns number of 1/1000 fractions (0.1%)
|
|
||||||
SessionInfo_AudioPeer, // remote peer rtp address in text
|
|
||||||
SessionInfo_AudioCodec, // selected audio codec as text
|
|
||||||
SessionInfo_DtmfInterface, // Pointer to DtmfQueue class; returned as void*
|
|
||||||
SessionInfo_IceState,
|
|
||||||
SessionInfo_NetworkMos,
|
|
||||||
SessionInfo_PvqaMos,
|
|
||||||
SessionInfo_PvqaReport,
|
|
||||||
SessionInfo_SentRtp,
|
|
||||||
SessionInfo_SentRtcp,
|
|
||||||
SessionInfo_ReceivedRtp,
|
|
||||||
SessionInfo_ReceivedRtcp,
|
|
||||||
SessionInfo_LostRtp,
|
|
||||||
SessionInfo_DroppedRtp,
|
|
||||||
SessionInfo_Duration,
|
|
||||||
SessionInfo_Jitter,
|
|
||||||
SessionInfo_Rtt,
|
|
||||||
SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only
|
|
||||||
SessionInfo_RemotePeer,
|
|
||||||
SessionInfo_SSRC,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Session :
|
|
||||||
public SocketSink,
|
|
||||||
public ice::StageHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
class Command
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void run(Session& s) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Describes ice stream/component
|
|
||||||
struct IceInfo
|
|
||||||
{
|
|
||||||
IceInfo()
|
|
||||||
:mStreamId(-1)
|
|
||||||
{
|
|
||||||
mPort4 = mPort6 = 0;
|
|
||||||
mComponentId.mRtp = mComponentId.mRtcp = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
RtpPair<int> mComponentId;
|
|
||||||
int mStreamId;
|
|
||||||
unsigned short mPort4;
|
|
||||||
unsigned short mPort6;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Describes media stream (audio/video) in session
|
|
||||||
class Stream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Stream();
|
|
||||||
~Stream();
|
|
||||||
|
|
||||||
void setProvider(PDataProvider provider);
|
|
||||||
PDataProvider provider();
|
|
||||||
|
|
||||||
void setSocket4(const RtpPair<PDatagramSocket>& socket);
|
|
||||||
RtpPair<PDatagramSocket>& socket4();
|
|
||||||
|
|
||||||
void setSocket6(const RtpPair<PDatagramSocket>& socket);
|
|
||||||
RtpPair<PDatagramSocket>& socket6();
|
|
||||||
|
|
||||||
void setIceInfo(const IceInfo& info);
|
|
||||||
IceInfo iceInfo() const;
|
|
||||||
|
|
||||||
// rtcpAttr/rtcpMuxAttr signals about corresponding sip attribute in offer/answer from remote peer
|
|
||||||
bool rtcpAttr() const;
|
|
||||||
void setRtcpAttr(bool value);
|
|
||||||
|
|
||||||
bool rtcpMuxAttr() const;
|
|
||||||
void setRtcpMuxAttr(bool value);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// Provider for corresponding stream
|
|
||||||
PDataProvider mProvider;
|
|
||||||
|
|
||||||
// Socket for stream
|
|
||||||
RtpPair<PDatagramSocket> mSocket4, mSocket6;
|
|
||||||
|
|
||||||
bool mRtcpAttr;
|
|
||||||
bool mRtcpMuxAttr;
|
|
||||||
IceInfo mIceInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
Session(PAccount account);
|
|
||||||
virtual ~Session();
|
|
||||||
|
|
||||||
// Starts call to specified peer
|
|
||||||
void start(const std::string& peer);
|
|
||||||
|
|
||||||
// Stops call
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
// Accepts call
|
|
||||||
void accept();
|
|
||||||
|
|
||||||
// Rejects call
|
|
||||||
void reject(int code);
|
|
||||||
|
|
||||||
enum class InfoOptions
|
|
||||||
{
|
|
||||||
Standard = 0,
|
|
||||||
Detailed = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
void getSessionInfo(InfoOptions options, VariantMap& result);
|
|
||||||
|
|
||||||
// Returns integer identifier of the session; it is unique amongst all session in application
|
|
||||||
int id() const;
|
|
||||||
|
|
||||||
// Returns owning account
|
|
||||||
PAccount account();
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> UserHeaders;
|
|
||||||
void setUserHeaders(const UserHeaders& headers);
|
|
||||||
|
|
||||||
// Called when new media data are available for this session
|
|
||||||
void onReceivedData(PDatagramSocket socket, InternetAddress& src, const void* receivedPtr, unsigned receivedSize);
|
|
||||||
|
|
||||||
// Called when new candidate is gathered
|
|
||||||
void onCandidateGathered(ice::Stack* stack, void* tag, const char* address);
|
|
||||||
|
|
||||||
// Called when connectivity check is finished
|
|
||||||
void onCheckFinished(ice::Stack* stack, void* tag, const char* checkDescription);
|
|
||||||
|
|
||||||
// Called when ICE candidates are gathered - with success or timeout.
|
|
||||||
void onGathered(ice::Stack* stack, void* tag);
|
|
||||||
|
|
||||||
// Called when ICE connectivity check is good at least for one of required streams
|
|
||||||
void onSuccess(ice::Stack* stack, void* tag);
|
|
||||||
|
|
||||||
// Called when ICE connectivity check is failed for all of required streams
|
|
||||||
void onFailed(ice::Stack* stack, void* tag);
|
|
||||||
|
|
||||||
// Called when ICE stack detects network change during the call
|
|
||||||
void onNetworkChange(ice::Stack* stack, void* tag);
|
|
||||||
|
|
||||||
// Fills SDP according to ICE and provider's data
|
|
||||||
void buildSdp(resip::SdpContents& sdp, SdpDirection sdpDirection);
|
|
||||||
|
|
||||||
// Searches provider by its local port number
|
|
||||||
PDataProvider findProviderByPort(int family, unsigned short port);
|
|
||||||
|
|
||||||
// Add provider to internal list
|
|
||||||
void addProvider(PDataProvider provider);
|
|
||||||
PDataProvider providerAt(int index);
|
|
||||||
int getProviderCount();
|
|
||||||
|
|
||||||
void setUserAgent(UserAgent* agent);
|
|
||||||
UserAgent* userAgent();
|
|
||||||
|
|
||||||
// Pauses and resumes all providers; updates states
|
|
||||||
void pause();
|
|
||||||
void resume();
|
|
||||||
void refreshMediaPath();
|
|
||||||
|
|
||||||
// Processes new sdp from offer. Returns response code (200 is ok, 488 bad codec, 503 internal error).
|
|
||||||
// There are passing string objects by value; this is correct; this values will modified on the stack.
|
|
||||||
int processSdp(uint64_t version, bool iceAvailable, std::string icePwd, const std::string iceUfrag,
|
|
||||||
std::string remoteIp, const resip::SdpContents::Session::MediumContainer& media);
|
|
||||||
|
|
||||||
// Session ID
|
|
||||||
int mSessionId;
|
|
||||||
|
|
||||||
// Media streams collection
|
|
||||||
std::vector<Stream> mStreamList;
|
|
||||||
|
|
||||||
// Smart pointer to ICE stack. Actually stack is created in CreateICEStack() method
|
|
||||||
std::shared_ptr<ice::Stack> mIceStack;
|
|
||||||
|
|
||||||
// Pointer to owner user agent instance
|
|
||||||
UserAgent* mUserAgent;
|
|
||||||
|
|
||||||
// Remote peer SIP address
|
|
||||||
resip::NameAddr mRemotePeer;
|
|
||||||
|
|
||||||
// Mutex to protect this instance
|
|
||||||
Mutex mGuard;
|
|
||||||
|
|
||||||
// SDP's origin version for sending
|
|
||||||
int mOriginVersion;
|
|
||||||
uint64_t mRemoteOriginVersion;
|
|
||||||
|
|
||||||
// SDP's session version
|
|
||||||
int mSessionVersion;
|
|
||||||
|
|
||||||
// Marks if this session does not need OnNewSession event
|
|
||||||
bool mAcceptedByEngine;
|
|
||||||
bool mAcceptedByUser;
|
|
||||||
|
|
||||||
// Invite session handle
|
|
||||||
resip::InviteSessionHandle mInviteHandle;
|
|
||||||
|
|
||||||
// Dialog set object pointer
|
|
||||||
ResipSession* mResipSession;
|
|
||||||
|
|
||||||
// Reference counter
|
|
||||||
int mRefCount;
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Initiator = 1,
|
|
||||||
Acceptor = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
// Specifies session role - caller (Initiator) or callee (Acceptor)
|
|
||||||
volatile int mRole;
|
|
||||||
|
|
||||||
// Marks if candidates are gather already
|
|
||||||
volatile bool mGatheredCandidates;
|
|
||||||
|
|
||||||
// Marks if OnTerminated event was called already on session
|
|
||||||
volatile bool mTerminated;
|
|
||||||
|
|
||||||
// User friend remote peer's sip address
|
|
||||||
std::string mRemoteAddress;
|
|
||||||
|
|
||||||
// Application specific data
|
|
||||||
void* mTag;
|
|
||||||
|
|
||||||
// Used to count number of transistions to Connected state and avoid multiple onEstablished events.
|
|
||||||
int mOfferAnswerCounter;
|
|
||||||
|
|
||||||
// List of turn prefixes related to sessioj
|
|
||||||
std::vector<int> mTurnPrefixList;
|
|
||||||
|
|
||||||
// True if user agent has to send offer
|
|
||||||
bool mHasToSendOffer;
|
|
||||||
|
|
||||||
// True if user agent has to enqueue offer after ice gather finished
|
|
||||||
bool mSendOfferUpdateAfterIceGather;
|
|
||||||
|
|
||||||
// Related sip account
|
|
||||||
PAccount mAccount;
|
|
||||||
|
|
||||||
// User headers for INVITE transaction
|
|
||||||
UserHeaders mUserHeaders;
|
|
||||||
|
|
||||||
std::string remoteAddress() const;
|
|
||||||
void setRemoteAddress(const std::string& address);
|
|
||||||
|
|
||||||
void* tag();
|
|
||||||
void setTag(void* tag);
|
|
||||||
int sessionId();
|
|
||||||
int increaseSdpVersion();
|
|
||||||
int addRef();
|
|
||||||
int release();
|
|
||||||
|
|
||||||
// Deletes providers and media sockets
|
|
||||||
void clearProvidersAndSockets();
|
|
||||||
|
|
||||||
// Deletes providers
|
|
||||||
void clearProviders();
|
|
||||||
|
|
||||||
// Helper method to find audio provider for active sip stream
|
|
||||||
AudioProvider* findProviderForActiveAudio();
|
|
||||||
|
|
||||||
void processCommandList();
|
|
||||||
void addCommand(Command* cmd);
|
|
||||||
void enqueueOffer();
|
|
||||||
void processQueuedOffer();
|
|
||||||
static int generateId();
|
|
||||||
static std::atomic_int IdGenerator;
|
|
||||||
static std::atomic_int InstanceCounter;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::shared_ptr<Session> PSession;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// Classes that provide the mapping between Application Data and DUM
|
|
||||||
// dialogs/dialogsets
|
|
||||||
//
|
|
||||||
// The DUM layer creates an AppDialog/AppDialogSet object for inbound/outbound
|
|
||||||
// SIP Request that results in Dialog creation.
|
|
||||||
//
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
class ResipSessionAppDialog : public resip::AppDialog
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ResipSessionAppDialog(resip::HandleManager& ham);
|
|
||||||
virtual ~ResipSessionAppDialog();
|
|
||||||
};
|
|
||||||
|
|
||||||
class ResipSession: public resip::AppDialogSet
|
|
||||||
{
|
|
||||||
friend class UserAgent;
|
|
||||||
friend class Account;
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
Type_None,
|
|
||||||
Type_Registration,
|
|
||||||
Type_Subscription,
|
|
||||||
Type_Call,
|
|
||||||
Type_Auto
|
|
||||||
};
|
|
||||||
static std::atomic_int InstanceCounter;
|
|
||||||
|
|
||||||
|
|
||||||
ResipSession(resip::DialogUsageManager& dum);
|
|
||||||
virtual ~ResipSession();
|
|
||||||
virtual resip::AppDialog* createAppDialog(const resip::SipMessage& msg);
|
|
||||||
virtual std::shared_ptr<resip::UserProfile> selectUASUserProfile(const resip::SipMessage& msg);
|
|
||||||
|
|
||||||
void setType(Type type);
|
|
||||||
Type type();
|
|
||||||
|
|
||||||
Session* session();
|
|
||||||
void setSession(Session* session);
|
|
||||||
|
|
||||||
UserAgent* ua();
|
|
||||||
void setUa(UserAgent* ua);
|
|
||||||
|
|
||||||
// Used for subscriptions/messages
|
|
||||||
int sessionId();
|
|
||||||
|
|
||||||
// Used for subscriptions/messages
|
|
||||||
void* tag() const;
|
|
||||||
void setTag(void* tag);
|
|
||||||
|
|
||||||
// Used for subscriptions/messages
|
|
||||||
std::string remoteAddress() const;
|
|
||||||
void setRemoteAddress(std::string address);
|
|
||||||
|
|
||||||
void runTerminatedEvent(Type type, int code = 0, int reason = 0);
|
|
||||||
|
|
||||||
void setUASProfile(const std::shared_ptr<resip::UserProfile>& profile);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool mTerminated;
|
|
||||||
UserAgent* mUserAgent;
|
|
||||||
Type mType;
|
|
||||||
Session* mSession;
|
|
||||||
int mSessionId;
|
|
||||||
std::string mRemoteAddress;
|
|
||||||
void* mTag;
|
|
||||||
bool mOnWatchingStartSent;
|
|
||||||
std::shared_ptr<resip::UserProfile> mUASProfile;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class ResipSessionFactory : public resip::AppDialogSetFactory
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ResipSessionFactory(UserAgent* agent);
|
|
||||||
virtual resip::AppDialogSet* createAppDialogSet(resip::DialogUsageManager& dum, const resip::SipMessage& msg);
|
|
||||||
protected:
|
|
||||||
UserAgent* mAgent;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
/* 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
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
cmake_minimum_required (VERSION 3.15)
|
|
||||||
project (helper_lib)
|
|
||||||
|
|
||||||
# Rely on C++ 11
|
|
||||||
set (CMAKE_CXX_STANDARD 20)
|
|
||||||
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
|
|
||||||
set (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
||||||
|
|
||||||
file (GLOB HELPER_LIB_SOURCES "*.cpp" "*.h")
|
|
||||||
|
|
||||||
add_library(helper_lib ${HELPER_LIB_SOURCES})
|
|
||||||
set_property(TARGET helper_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
|
||||||
|
|
||||||
# Private include directories
|
|
||||||
target_include_directories(helper_lib PUBLIC ../../libs/ ../../engine ../ .)
|
|
||||||
target_compile_definitions(helper_lib PRIVATE -D_CRT_SECURE_NO_WARNINGS -D_UNICODE)
|
|
||||||
if (TARGET_LINUX)
|
|
||||||
target_link_libraries (helper_lib PUBLIC uuid)
|
|
||||||
endif()
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#include "HL_AsyncCommand.h"
|
|
||||||
|
|
||||||
AsyncCommand::AsyncCommand()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncCommand::~AsyncCommand()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 HL_ASYNCCOMMAND_H
|
|
||||||
#define HL_ASYNCCOMMAND_H
|
|
||||||
|
|
||||||
class AsyncCommand
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AsyncCommand();
|
|
||||||
virtual ~AsyncCommand();
|
|
||||||
|
|
||||||
virtual void run(void* environment) = 0;
|
|
||||||
virtual bool finished() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // HL_ASYNCCOMMAND_H
|
|
||||||
|
|
@ -1,256 +0,0 @@
|
||||||
#ifndef HL_BASE64_H
|
|
||||||
#define HL_BASE64_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
"0123456789+/";
|
|
||||||
|
|
||||||
class Base64 {
|
|
||||||
public:
|
|
||||||
static bool Encode(const std::string &in, std::string *out) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
size_t enc_len = 0;
|
|
||||||
unsigned char a3[3];
|
|
||||||
unsigned char a4[4];
|
|
||||||
|
|
||||||
out->resize(EncodedLength(in));
|
|
||||||
|
|
||||||
int input_len = in.size();
|
|
||||||
std::string::const_iterator input = in.begin();
|
|
||||||
|
|
||||||
while (input_len--) {
|
|
||||||
a3[i++] = *(input++);
|
|
||||||
if (i == 3) {
|
|
||||||
a3_to_a4(a4, a3);
|
|
||||||
|
|
||||||
for (i = 0; i < 4; i++) {
|
|
||||||
(*out)[enc_len++] = kBase64Alphabet[a4[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i) {
|
|
||||||
for (j = i; j < 3; j++) {
|
|
||||||
a3[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
a3_to_a4(a4, a3);
|
|
||||||
|
|
||||||
for (j = 0; j < i + 1; j++) {
|
|
||||||
(*out)[enc_len++] = kBase64Alphabet[a4[j]];
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((i++ < 3)) {
|
|
||||||
(*out)[enc_len++] = '=';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (enc_len == out->size());
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool Encode(const char *input, size_t input_length, char *out, size_t out_length) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
char *out_begin = out;
|
|
||||||
unsigned char a3[3];
|
|
||||||
unsigned char a4[4];
|
|
||||||
|
|
||||||
size_t encoded_length = EncodedLength(input_length);
|
|
||||||
|
|
||||||
if (out_length < encoded_length) return false;
|
|
||||||
|
|
||||||
while (input_length--) {
|
|
||||||
a3[i++] = *input++;
|
|
||||||
if (i == 3) {
|
|
||||||
a3_to_a4(a4, a3);
|
|
||||||
|
|
||||||
for (i = 0; i < 4; i++) {
|
|
||||||
*out++ = kBase64Alphabet[a4[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i) {
|
|
||||||
for (j = i; j < 3; j++) {
|
|
||||||
a3[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
a3_to_a4(a4, a3);
|
|
||||||
|
|
||||||
for (j = 0; j < i + 1; j++) {
|
|
||||||
*out++ = kBase64Alphabet[a4[j]];
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((i++ < 3)) {
|
|
||||||
*out++ = '=';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (out == (out_begin + encoded_length));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool Decode(const std::string &in, std::string *out) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
size_t dec_len = 0;
|
|
||||||
unsigned char a3[3];
|
|
||||||
unsigned char a4[4];
|
|
||||||
|
|
||||||
int input_len = in.size();
|
|
||||||
std::string::const_iterator input = in.begin();
|
|
||||||
|
|
||||||
out->resize(DecodedLength(in));
|
|
||||||
|
|
||||||
while (input_len--) {
|
|
||||||
if (*input == '=') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
a4[i++] = *(input++);
|
|
||||||
if (i == 4) {
|
|
||||||
for (i = 0; i <4; i++) {
|
|
||||||
a4[i] = b64_lookup(a4[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
a4_to_a3(a3,a4);
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
(*out)[dec_len++] = a3[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i) {
|
|
||||||
for (j = i; j < 4; j++) {
|
|
||||||
a4[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < 4; j++) {
|
|
||||||
a4[j] = b64_lookup(a4[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
a4_to_a3(a3,a4);
|
|
||||||
|
|
||||||
for (j = 0; j < i - 1; j++) {
|
|
||||||
(*out)[dec_len++] = a3[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (dec_len == out->size());
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool Decode(const char *input, size_t input_length, char *out, size_t out_length) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
char *out_begin = out;
|
|
||||||
unsigned char a3[3];
|
|
||||||
unsigned char a4[4];
|
|
||||||
|
|
||||||
size_t decoded_length = DecodedLength(input, input_length);
|
|
||||||
|
|
||||||
if (out_length < decoded_length) return false;
|
|
||||||
|
|
||||||
while (input_length--) {
|
|
||||||
if (*input == '=') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
a4[i++] = *(input++);
|
|
||||||
if (i == 4) {
|
|
||||||
for (i = 0; i <4; i++) {
|
|
||||||
a4[i] = b64_lookup(a4[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
a4_to_a3(a3,a4);
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
*out++ = a3[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i) {
|
|
||||||
for (j = i; j < 4; j++) {
|
|
||||||
a4[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j < 4; j++) {
|
|
||||||
a4[j] = b64_lookup(a4[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
a4_to_a3(a3,a4);
|
|
||||||
|
|
||||||
for (j = 0; j < i - 1; j++) {
|
|
||||||
*out++ = a3[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (out == (out_begin + decoded_length));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int DecodedLength(const char *in, size_t in_length) {
|
|
||||||
int numEq = 0;
|
|
||||||
|
|
||||||
const char *in_end = in + in_length;
|
|
||||||
while (*--in_end == '=') ++numEq;
|
|
||||||
|
|
||||||
return ((6 * in_length) / 8) - numEq;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int DecodedLength(const std::string &in) {
|
|
||||||
int numEq = 0;
|
|
||||||
int n = in.size();
|
|
||||||
|
|
||||||
for (std::string::const_reverse_iterator it = in.rbegin(); *it == '='; ++it) {
|
|
||||||
++numEq;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((6 * n) / 8) - numEq;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static int EncodedLength(size_t length) {
|
|
||||||
return (length + 2 - ((length + 2) % 3)) / 3 * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static int EncodedLength(const std::string &in) {
|
|
||||||
return EncodedLength(in.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void StripPadding(std::string *in) {
|
|
||||||
while (!in->empty() && *(in->rbegin()) == '=') in->resize(in->size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static inline void a3_to_a4(unsigned char * a4, unsigned char * a3) {
|
|
||||||
a4[0] = (a3[0] & 0xfc) >> 2;
|
|
||||||
a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
|
|
||||||
a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
|
|
||||||
a4[3] = (a3[2] & 0x3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void a4_to_a3(unsigned char * a3, unsigned char * a4) {
|
|
||||||
a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
|
|
||||||
a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
|
|
||||||
a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline unsigned char b64_lookup(unsigned char c) {
|
|
||||||
if(c >='A' && c <='Z') return c - 'A';
|
|
||||||
if(c >='a' && c <='z') return c - 71;
|
|
||||||
if(c >='0' && c <='9') return c + 4;
|
|
||||||
if(c == '+') return 62;
|
|
||||||
if(c == '/') return 63;
|
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif // HL_BASE64_H
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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 __HL_BYTEBUFFER_H
|
|
||||||
#define __HL_BYTEBUFFER_H
|
|
||||||
|
|
||||||
#include "ice/ICEByteBuffer.h"
|
|
||||||
|
|
||||||
typedef ice::ByteBuffer ByteBuffer;
|
|
||||||
typedef ice::PByteBuffer PByteBuffer;
|
|
||||||
typedef ice::BitReader BitReader;
|
|
||||||
typedef ice::BitWriter BitWriter;
|
|
||||||
typedef ice::BufferReader BufferReader;
|
|
||||||
typedef ice::BufferWriter BufferWriter;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,626 +0,0 @@
|
||||||
#ifndef __HL_CALCULATOR_H
|
|
||||||
#define __HL_CALCULATOR_H
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "helper/HL_VariantMap.h"
|
|
||||||
#include "helper/HL_String.h"
|
|
||||||
#include "helper/HL_InternetAddress.h"
|
|
||||||
|
|
||||||
namespace Calc
|
|
||||||
{
|
|
||||||
class Parser;
|
|
||||||
namespace Ast
|
|
||||||
{
|
|
||||||
enum class Type
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
And,
|
|
||||||
Or,
|
|
||||||
Equal,
|
|
||||||
NotEqual,
|
|
||||||
Less,
|
|
||||||
LessOrEqual,
|
|
||||||
Greater,
|
|
||||||
GreatorOrEqual,
|
|
||||||
Add,
|
|
||||||
Sub,
|
|
||||||
Mul,
|
|
||||||
Div,
|
|
||||||
Number,
|
|
||||||
String,
|
|
||||||
Var
|
|
||||||
};
|
|
||||||
|
|
||||||
class Item;
|
|
||||||
typedef Item* PItem;
|
|
||||||
|
|
||||||
class Item
|
|
||||||
{
|
|
||||||
friend class Calc::Parser;
|
|
||||||
public:
|
|
||||||
bool isVariable() const
|
|
||||||
{
|
|
||||||
return mType == Type::Var;
|
|
||||||
}
|
|
||||||
bool isFixed() const
|
|
||||||
{
|
|
||||||
return mType == Type::Number || mType == Type::String;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isOperation() const
|
|
||||||
{
|
|
||||||
return mType >= Type::And && mType <= Type::Div;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasBrackets() const
|
|
||||||
{
|
|
||||||
return mHasBrackets;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getOperatorLevel() const
|
|
||||||
{
|
|
||||||
switch (mType)
|
|
||||||
{
|
|
||||||
case Type::Or:
|
|
||||||
return -2;
|
|
||||||
|
|
||||||
case Type::And:
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
case Type::Equal:
|
|
||||||
case Type::NotEqual:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case Type::Less:
|
|
||||||
case Type::LessOrEqual:
|
|
||||||
case Type::Greater:
|
|
||||||
case Type::GreatorOrEqual:
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
case Type::Add:
|
|
||||||
case Type::Sub:
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
case Type::Mul:
|
|
||||||
case Type::Div:
|
|
||||||
return 3;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type getType() const
|
|
||||||
{
|
|
||||||
return mType;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getName() const
|
|
||||||
{
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
Variant& value()
|
|
||||||
{
|
|
||||||
return mValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<PItem>& children()
|
|
||||||
{
|
|
||||||
return mChildren;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> NameMap;
|
|
||||||
|
|
||||||
std::ostream& print(std::ostream& oss, const NameMap& nm)
|
|
||||||
{
|
|
||||||
oss << " ( ";
|
|
||||||
|
|
||||||
if (isOperation())
|
|
||||||
mChildren.front()->print(oss, nm);
|
|
||||||
|
|
||||||
oss << " ";
|
|
||||||
switch (mType)
|
|
||||||
{
|
|
||||||
case Type::Number: oss << mValue.asStdString(); break;
|
|
||||||
case Type::String: oss << '"' << mValue.asStdString() << '"'; break;
|
|
||||||
case Type::Var: { NameMap::const_iterator iter = nm.find(mName); oss << ((iter != nm.end()) ? iter->second : mName);} break;
|
|
||||||
case Type::Add: oss << "+"; break;
|
|
||||||
case Type::Mul: oss << "*"; break;
|
|
||||||
case Type::Div: oss << "/"; break;
|
|
||||||
case Type::Sub: oss << "-"; break;
|
|
||||||
case Type::Equal: oss << "=="; break;
|
|
||||||
case Type::NotEqual: oss << "!="; break;
|
|
||||||
case Type::Less: oss << "<"; break;
|
|
||||||
case Type::LessOrEqual: oss << "<="; break;
|
|
||||||
case Type::Greater: oss << ">"; break;
|
|
||||||
case Type::GreatorOrEqual: oss << ">="; break;
|
|
||||||
case Type::Or: oss << "or"; break;
|
|
||||||
case Type::And: oss << "and"; break;
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("operator expected");
|
|
||||||
}
|
|
||||||
oss << " ";
|
|
||||||
if (isOperation() && mChildren.size() == 2 && mChildren.back())
|
|
||||||
mChildren.back()->print(oss, nm);
|
|
||||||
|
|
||||||
oss << " ) ";
|
|
||||||
|
|
||||||
return oss;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef std::map<std::string, Variant> ValueMap;
|
|
||||||
|
|
||||||
Variant eval(const ValueMap& vm)
|
|
||||||
{
|
|
||||||
Variant result, left, right;
|
|
||||||
if (isOperation())
|
|
||||||
{
|
|
||||||
left = mChildren.front()->eval(vm);
|
|
||||||
right = mChildren.back()->eval(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (mType)
|
|
||||||
{
|
|
||||||
case Type::Number:
|
|
||||||
case Type::String: result = mValue; break;
|
|
||||||
|
|
||||||
case Type::Var: { auto iter = vm.find(mName); if (iter != vm.end()) return iter->second; else throw std::runtime_error("Variable " + mName + " did not find."); }
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Type::Add: result = left + right; break;
|
|
||||||
case Type::Mul: result = left * right; break;
|
|
||||||
case Type::Div: result = left / right; break;
|
|
||||||
case Type::Sub: result = left - right; break;
|
|
||||||
case Type::Equal: result = left == right; break;
|
|
||||||
case Type::NotEqual: result = left != right; break;
|
|
||||||
case Type::Less: result = left < right; break;
|
|
||||||
case Type::LessOrEqual: result = left <= right; break;
|
|
||||||
case Type::Greater: result = left > right; break;
|
|
||||||
case Type::GreatorOrEqual: result = left >= right; break;
|
|
||||||
case Type::Or: result = left.asBool() || right.asBool(); break;
|
|
||||||
case Type::And: result = left.asBool() && right.asBool(); break;
|
|
||||||
default:
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Item()
|
|
||||||
{
|
|
||||||
for (auto node: mChildren)
|
|
||||||
delete node;
|
|
||||||
mChildren.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Type mType = Type::None;
|
|
||||||
std::string mName;
|
|
||||||
Variant mValue;
|
|
||||||
std::vector<PItem> mChildren;
|
|
||||||
bool mHasBrackets = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ishex(int c)
|
|
||||||
{
|
|
||||||
if (isdigit(c))
|
|
||||||
return true;
|
|
||||||
return (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parser
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
enum class LexemType
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
Hex,
|
|
||||||
Dec,
|
|
||||||
Float,
|
|
||||||
Str,
|
|
||||||
Oper,
|
|
||||||
Var,
|
|
||||||
OpenBracket,
|
|
||||||
CloseBracket
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Lexem
|
|
||||||
{
|
|
||||||
LexemType mType = LexemType::None;
|
|
||||||
std::string mValue;
|
|
||||||
|
|
||||||
operator bool () const
|
|
||||||
{
|
|
||||||
return mType != LexemType::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string toString() const
|
|
||||||
{
|
|
||||||
return std::to_string((int)mType) + " : " + mValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Lexem mCurrentLexem;
|
|
||||||
|
|
||||||
Lexem processNewLexem(int c)
|
|
||||||
{
|
|
||||||
Lexem result;
|
|
||||||
|
|
||||||
if (c == '(')
|
|
||||||
mCurrentLexem.mType = LexemType::OpenBracket;
|
|
||||||
else
|
|
||||||
if (c == ')')
|
|
||||||
mCurrentLexem.mType = LexemType::CloseBracket;
|
|
||||||
else
|
|
||||||
if (isdigit(c))
|
|
||||||
mCurrentLexem.mType = LexemType::Dec;
|
|
||||||
else
|
|
||||||
if (isalpha(c))
|
|
||||||
mCurrentLexem.mType = LexemType::Var;
|
|
||||||
else
|
|
||||||
if (c == '+' || c == '-' || c == '/' || c == '*' || c == '=' || c == '<' || c == '>' || c == '&' || c == '|')
|
|
||||||
mCurrentLexem.mType = LexemType::Oper;
|
|
||||||
else
|
|
||||||
if (c == '"')
|
|
||||||
mCurrentLexem.mType = LexemType::Str;
|
|
||||||
else
|
|
||||||
return Lexem();
|
|
||||||
|
|
||||||
mCurrentLexem.mValue.push_back(c);
|
|
||||||
|
|
||||||
// Can we return result here already ?
|
|
||||||
if (mCurrentLexem.mType == LexemType::OpenBracket || mCurrentLexem.mType == LexemType::CloseBracket)
|
|
||||||
{
|
|
||||||
// Lexem finished
|
|
||||||
result = mCurrentLexem;
|
|
||||||
mCurrentLexem = Lexem();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCurrentLexem.mType == LexemType::Oper)
|
|
||||||
{
|
|
||||||
if (mCurrentLexem.mValue == "+" ||
|
|
||||||
mCurrentLexem.mValue == "-" ||
|
|
||||||
mCurrentLexem.mValue == "*" ||
|
|
||||||
mCurrentLexem.mValue == "/" ||
|
|
||||||
mCurrentLexem.mValue == ">=" ||
|
|
||||||
mCurrentLexem.mValue == "<=" ||
|
|
||||||
mCurrentLexem.mValue == "==" ||
|
|
||||||
mCurrentLexem.mValue == "||" ||
|
|
||||||
mCurrentLexem.mValue == "&&")
|
|
||||||
{
|
|
||||||
// Lexem finished
|
|
||||||
result = mCurrentLexem;
|
|
||||||
mCurrentLexem = Lexem();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Lexem();
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkNumericLexem()
|
|
||||||
{
|
|
||||||
if (mCurrentLexem.mType != LexemType::Dec)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check if there is ".:" characters
|
|
||||||
if (mCurrentLexem.mValue.find('.') != std::string::npos)
|
|
||||||
{
|
|
||||||
// Dot is here - is it float
|
|
||||||
bool isFloat = false;
|
|
||||||
strx::toFloat(mCurrentLexem.mValue, 0.0f, &isFloat);
|
|
||||||
if (isFloat)
|
|
||||||
mCurrentLexem.mType = LexemType::Float;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Maybe it is IP4/6 address ?
|
|
||||||
InternetAddress addr(mCurrentLexem.mValue, 8000);
|
|
||||||
if (!addr.isEmpty())
|
|
||||||
{
|
|
||||||
mCurrentLexem.mValue = "\"" + mCurrentLexem.mValue + "\"";
|
|
||||||
mCurrentLexem.mType = LexemType::Str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexem getLexem(std::istream& input)
|
|
||||||
{
|
|
||||||
Lexem result;
|
|
||||||
|
|
||||||
// Iterate while characters avaialbe from input stream & lexem is not finished
|
|
||||||
bool putback = false;
|
|
||||||
int c = input.get();
|
|
||||||
while (!input.eof() && c && result.mType == LexemType::None)
|
|
||||||
{
|
|
||||||
switch (mCurrentLexem.mType)
|
|
||||||
{
|
|
||||||
case LexemType::None:
|
|
||||||
result = processNewLexem(c);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Hex:
|
|
||||||
if (!ishex(c))
|
|
||||||
{
|
|
||||||
// Finish Hex lexem
|
|
||||||
result = mCurrentLexem;
|
|
||||||
putback = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mCurrentLexem.mValue.push_back(c);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Dec:
|
|
||||||
if (c == 'x' && mCurrentLexem.mValue == "0")
|
|
||||||
mCurrentLexem.mType = LexemType::Hex;
|
|
||||||
else
|
|
||||||
if (isdigit(c) || c == '.')
|
|
||||||
{
|
|
||||||
mCurrentLexem.mValue.push_back(c);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
checkNumericLexem();
|
|
||||||
result = mCurrentLexem;
|
|
||||||
putback = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Oper:
|
|
||||||
// It must be one of two-characters operations
|
|
||||||
if (c == '<' || c == '>' || c == '=' || c == '&' || c == '|')
|
|
||||||
{
|
|
||||||
mCurrentLexem.mValue.push_back(c);
|
|
||||||
result = mCurrentLexem;
|
|
||||||
mCurrentLexem = Lexem();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = mCurrentLexem;
|
|
||||||
putback = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Var:
|
|
||||||
if (isdigit(c) || isalpha(c) || c == '.' || c == '_')
|
|
||||||
{
|
|
||||||
mCurrentLexem.mValue.push_back(c);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = mCurrentLexem;
|
|
||||||
putback = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Str:
|
|
||||||
mCurrentLexem.mValue.push_back(c);
|
|
||||||
if (c == '"')
|
|
||||||
{
|
|
||||||
result = mCurrentLexem;
|
|
||||||
// String lexem is finished
|
|
||||||
mCurrentLexem.mType = LexemType::None;
|
|
||||||
putback = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (putback)
|
|
||||||
input.putback(c);
|
|
||||||
else
|
|
||||||
if (!result)
|
|
||||||
c = input.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
checkNumericLexem();
|
|
||||||
|
|
||||||
// Recover partially processed lexem - maybe we finish processing at all but there is dec / float / string / variable
|
|
||||||
if (mCurrentLexem.mType != LexemType::None && result.mType == LexemType::None)
|
|
||||||
result = mCurrentLexem;
|
|
||||||
|
|
||||||
// Reset current lexem
|
|
||||||
mCurrentLexem = Lexem();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make AST node from lexem
|
|
||||||
Ast::PItem makeAst(const Lexem& l)
|
|
||||||
{
|
|
||||||
Ast::PItem result(new Ast::Item());
|
|
||||||
|
|
||||||
switch (l.mType)
|
|
||||||
{
|
|
||||||
case LexemType::Oper:
|
|
||||||
if (l.mValue == "-")
|
|
||||||
result->mType = Ast::Type::Sub;
|
|
||||||
else
|
|
||||||
if (l.mValue == "+")
|
|
||||||
result->mType = Ast::Type::Add;
|
|
||||||
else
|
|
||||||
if (l.mValue == "*")
|
|
||||||
result->mType = Ast::Type::Mul;
|
|
||||||
else
|
|
||||||
if (l.mValue == "/")
|
|
||||||
result->mType = Ast::Type::Div;
|
|
||||||
else
|
|
||||||
if (l.mValue == "<")
|
|
||||||
result->mType = Ast::Type::Less;
|
|
||||||
else
|
|
||||||
if (l.mValue == "<=")
|
|
||||||
result->mType = Ast::Type::LessOrEqual;
|
|
||||||
else
|
|
||||||
if (l.mValue == ">")
|
|
||||||
result->mType = Ast::Type::Greater;
|
|
||||||
else
|
|
||||||
if (l.mValue == ">=")
|
|
||||||
result->mType = Ast::Type::GreatorOrEqual;
|
|
||||||
else
|
|
||||||
if (l.mValue == "==")
|
|
||||||
result->mType = Ast::Type::Equal;
|
|
||||||
else
|
|
||||||
if (l.mValue == "!=")
|
|
||||||
result->mType = Ast::Type::NotEqual;
|
|
||||||
else
|
|
||||||
if (l.mValue == "&&")
|
|
||||||
result->mType = Ast::Type::And;
|
|
||||||
else
|
|
||||||
if (l.mValue == "||")
|
|
||||||
result->mType = Ast::Type::Or;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Var:
|
|
||||||
result->mType = Ast::Type::Var;
|
|
||||||
result->mName = l.mValue;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Dec:
|
|
||||||
result->mType = Ast::Type::Number;
|
|
||||||
result->mValue = (int64_t)atoll(l.mValue.c_str());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Hex:
|
|
||||||
result->mType = Ast::Type::Number;
|
|
||||||
result->mValue = strx::fromHex2Int(l.mValue);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Float:
|
|
||||||
result->mType = Ast::Type::Number;
|
|
||||||
result->mValue = (float)atof(l.mValue.c_str());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::Str:
|
|
||||||
result->mType = Ast::Type::String;
|
|
||||||
result->mValue = l.mValue.substr(1, l.mValue.size() - 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Unexpected lexem.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexem mLexem;
|
|
||||||
public:
|
|
||||||
Ast::PItem parseExpression(std::istream& input)
|
|
||||||
{
|
|
||||||
Ast::PItem operationNode(nullptr), leftNode(nullptr), rightNode(nullptr),
|
|
||||||
currentOperation(nullptr);
|
|
||||||
|
|
||||||
// While we have lexem
|
|
||||||
while (mLexem = getLexem(input))
|
|
||||||
{
|
|
||||||
std::cout << "Returned lexem: " << mLexem.toString() << std::endl;
|
|
||||||
|
|
||||||
if (!leftNode)
|
|
||||||
{
|
|
||||||
// It must be first operand!
|
|
||||||
switch (mLexem.mType)
|
|
||||||
{
|
|
||||||
case LexemType::OpenBracket:
|
|
||||||
leftNode = parseExpression(input);
|
|
||||||
leftNode->mHasBrackets = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::CloseBracket:
|
|
||||||
throw std::runtime_error("Expected +/-/constant/variable here.");
|
|
||||||
|
|
||||||
case LexemType::Dec:
|
|
||||||
case LexemType::Hex:
|
|
||||||
case LexemType::Str:
|
|
||||||
case LexemType::Var:
|
|
||||||
case LexemType::Float:
|
|
||||||
leftNode = makeAst(mLexem);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Open bracket or constant / number / string / variable expected.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (!operationNode)
|
|
||||||
{
|
|
||||||
// Well, there is left node already
|
|
||||||
// See operation here
|
|
||||||
switch (mLexem.mType)
|
|
||||||
{
|
|
||||||
case LexemType::Oper:
|
|
||||||
operationNode = makeAst(mLexem);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::None:
|
|
||||||
// Finish the tree building
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LexemType::CloseBracket:
|
|
||||||
// Finish the tree building in this level
|
|
||||||
if (leftNode)
|
|
||||||
return leftNode;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Expected operation.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse rest of expression
|
|
||||||
rightNode = parseExpression(input);
|
|
||||||
|
|
||||||
// If right part of expression is operation - make left side child of right part - to allow calculation in right order
|
|
||||||
if (operationNode)
|
|
||||||
{
|
|
||||||
if (rightNode->isOperation() && rightNode->getOperatorLevel() <= operationNode->getOperatorLevel() && !rightNode->hasBrackets())
|
|
||||||
{
|
|
||||||
// Get left child of right expression - make it our right child
|
|
||||||
operationNode->children().push_back(leftNode);
|
|
||||||
operationNode->children().push_back(rightNode->children().front());
|
|
||||||
rightNode->children().front() = operationNode;
|
|
||||||
currentOperation = rightNode;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
operationNode->children().push_back(leftNode);
|
|
||||||
operationNode->children().push_back(rightNode);
|
|
||||||
currentOperation = operationNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mLexem.mType == LexemType::CloseBracket)
|
|
||||||
break; // Exit from loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentOperation ? currentOperation : leftNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
Ast::PItem parse(std::istream& input)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void testLexemParser(const std::string& test)
|
|
||||||
{
|
|
||||||
std::istringstream iss(test);
|
|
||||||
|
|
||||||
for (Lexem l = getLexem(iss); l.mType != LexemType::None; l = getLexem(iss))
|
|
||||||
{
|
|
||||||
std::cout << "Lexem type: " << (int)l.mType << ", value: " << l.mValue << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Worker
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Variant eval(Ast::PItem ast);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
#include "HL_CrashRpt.h"
|
|
||||||
|
|
||||||
#if defined(USE_CRASHRPT)
|
|
||||||
|
|
||||||
#include "HL_String.h"
|
|
||||||
|
|
||||||
|
|
||||||
// Define this if CrashRpt has to be deployed as .dll
|
|
||||||
// #define CRASHRPT_DYNAMIC
|
|
||||||
|
|
||||||
// --- CrashReporer ---
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
#include "CrashRpt.h"
|
|
||||||
|
|
||||||
BOOL WINAPI CrashReporter::Callback(LPVOID arg)
|
|
||||||
{
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef int(__stdcall *CrInstallProc)(__in PCR_INSTALL_INFOW pInfo);
|
|
||||||
static CrInstallProc CrInstall = nullptr;
|
|
||||||
|
|
||||||
typedef int(__stdcall *CrUninstallProc)();
|
|
||||||
static CrUninstallProc CrUninstall = nullptr;
|
|
||||||
|
|
||||||
typedef int(__stdcall *CrInstallIntoCurrentThreadProc)(DWORD dwFlags);
|
|
||||||
static CrInstallIntoCurrentThreadProc CrInstallIntoCurrentThread = nullptr;
|
|
||||||
|
|
||||||
typedef int(__stdcall *CrUninstallFromCurrentThreadProc)();
|
|
||||||
static CrUninstallFromCurrentThreadProc CrUninstallFromCurrentThread = nullptr;
|
|
||||||
|
|
||||||
typedef int(__stdcall *CrGetLastErrorMsgProc)(LPWSTR buffer, UINT size);
|
|
||||||
static CrGetLastErrorMsgProc CrGetLastErrorMsg = nullptr;
|
|
||||||
|
|
||||||
static HMODULE CrLibraryHandle = NULL;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void CrashReporter::init(const std::string& appname, const std::string& version, const std::string& url)
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
#if defined(CRASHRPT_DYNAMIC)
|
|
||||||
// Check if DLL functions are here
|
|
||||||
if (CrLibraryHandle)
|
|
||||||
return; // Library is loaded already - so initialized already
|
|
||||||
|
|
||||||
CrLibraryHandle = ::LoadLibrary(TEXT("crashrpt.dll"));
|
|
||||||
if (!CrLibraryHandle)
|
|
||||||
return; // No logging here - initialization happens on very first stages, no chance to log anything
|
|
||||||
|
|
||||||
CrInstall = (CrInstallProc)::GetProcAddress(CrLibraryHandle, "crInstallW");
|
|
||||||
CrUninstall = (CrUninstallProc)::GetProcAddress(CrLibraryHandle, "crUninstall");
|
|
||||||
CrInstallIntoCurrentThread = (CrInstallIntoCurrentThreadProc)::GetProcAddress(CrLibraryHandle, "crInstallToCurrentThread2");
|
|
||||||
CrUninstallFromCurrentThread = (CrUninstallFromCurrentThreadProc)::GetProcAddress(CrLibraryHandle, "crUninstallFromCurrentThread");
|
|
||||||
CrGetLastErrorMsg = (CrGetLastErrorMsgProc)::GetProcAddress(CrLibraryHandle, "crGetLastErrorMsgW");
|
|
||||||
#else
|
|
||||||
CrInstall = &crInstallW;
|
|
||||||
CrUninstall = &crUninstall;
|
|
||||||
CrInstallIntoCurrentThread = &crInstallToCurrentThread2;
|
|
||||||
CrUninstallFromCurrentThread = &crUninstallFromCurrentThread;
|
|
||||||
CrGetLastErrorMsg = &crGetLastErrorMsgW;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!isLoaded())
|
|
||||||
return;
|
|
||||||
|
|
||||||
CR_INSTALL_INFO info;
|
|
||||||
memset(&info, 0, sizeof(CR_INSTALL_INFO));
|
|
||||||
info.cb = sizeof(CR_INSTALL_INFO);
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
std::wstring appname, version, url;
|
|
||||||
} unicode;
|
|
||||||
unicode.appname = StringHelper::makeTstring(appname),
|
|
||||||
unicode.version = StringHelper::makeTstring(version),
|
|
||||||
unicode.url = StringHelper::makeTstring(url);
|
|
||||||
|
|
||||||
if (unicode.appname.empty())
|
|
||||||
unicode.appname = L"App";
|
|
||||||
|
|
||||||
if (unicode.url.empty())
|
|
||||||
unicode.url = L"https://voipobjects.com/crashrpt/crashrpt.php";
|
|
||||||
|
|
||||||
if (unicode.version.empty())
|
|
||||||
unicode.version = L"General version";
|
|
||||||
|
|
||||||
info.pszAppName = unicode.appname.c_str();
|
|
||||||
info.pszAppVersion = unicode.version.c_str();
|
|
||||||
info.pszEmailSubject = TEXT("Crash report");
|
|
||||||
//info.pszEmailTo = L"amegyeri@minerva-soft.com";
|
|
||||||
//info.pszUrl = L"http://ftp.minerva-soft.com/crashlog/crashrpt.php";
|
|
||||||
//info.pszUrl = L"http://sip.crypttalk.com/crashlog/crashrpt.php";
|
|
||||||
info.pszUrl = unicode.url.c_str();
|
|
||||||
info.pfnCrashCallback = Callback;
|
|
||||||
info.uPriorities[CR_HTTP] = 1;
|
|
||||||
info.uPriorities[CR_SMTP] = CR_NEGATIVE_PRIORITY;
|
|
||||||
info.uPriorities[CR_SMAPI] = CR_NEGATIVE_PRIORITY;
|
|
||||||
info.dwFlags = 0;
|
|
||||||
info.pszCrashSenderPath = TEXT(".");
|
|
||||||
|
|
||||||
int nResult = CrInstall(&info);
|
|
||||||
if (nResult)
|
|
||||||
{
|
|
||||||
wchar_t errorMsg[512] = L"";
|
|
||||||
CrGetLastErrorMsg(errorMsg, 512);
|
|
||||||
OutputDebugStringW(errorMsg);
|
|
||||||
//LogCritical("Core", << "Failed to install CrashReporter with code " << nResult << " and message " << errorMsg);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReporter::free()
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
if (isLoaded())
|
|
||||||
{
|
|
||||||
CrUninstall();
|
|
||||||
CrInstall = nullptr;
|
|
||||||
CrUninstall = nullptr;
|
|
||||||
CrInstallIntoCurrentThread = nullptr;
|
|
||||||
CrUninstallFromCurrentThread = nullptr;
|
|
||||||
CrGetLastErrorMsg = nullptr;
|
|
||||||
#if defined(CRASHRPT_DYNAMIC)
|
|
||||||
::FreeLibrary(CrLibraryHandle); CrLibraryHandle = NULL;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CrashReporter::isLoaded()
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
return !(!CrInstall || !CrUninstall || !CrGetLastErrorMsg ||
|
|
||||||
!CrInstallIntoCurrentThread || !CrUninstallFromCurrentThread);
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReporter::initThread()
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
if (isLoaded())
|
|
||||||
CrInstallIntoCurrentThread(0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashReporter::freeThread()
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
if (isLoaded())
|
|
||||||
CrUninstallFromCurrentThread();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterThreadPoint::CrashReporterThreadPoint()
|
|
||||||
{
|
|
||||||
CrashReporter::initThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterThreadPoint::~CrashReporterThreadPoint()
|
|
||||||
{
|
|
||||||
CrashReporter::freeThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterGuard::CrashReporterGuard()
|
|
||||||
{
|
|
||||||
CrashReporter::init("generic");
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterGuard::~CrashReporterGuard()
|
|
||||||
{
|
|
||||||
CrashReporter::free();
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
CrashReporterThreadPoint::CrashReporterThreadPoint()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterThreadPoint::~CrashReporterThreadPoint()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterGuard::CrashReporterGuard()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
CrashReporterGuard::~CrashReporterGuard()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
#ifndef __CRASH_RPT_H
|
|
||||||
#define __CRASH_RPT_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
# include <WinSock2.h>
|
|
||||||
# include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Helper class to translate SEH exceptions to C++ - sometimes it is needed
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
|
|
||||||
class SE_Exception
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
unsigned int nSE;
|
|
||||||
public:
|
|
||||||
SE_Exception() {}
|
|
||||||
SE_Exception(unsigned int n) : nSE(n) {}
|
|
||||||
~SE_Exception() {}
|
|
||||||
unsigned int getSeNumber() { return nSE; }
|
|
||||||
};
|
|
||||||
|
|
||||||
extern void SEHToCpp(unsigned int, EXCEPTION_POINTERS*);
|
|
||||||
|
|
||||||
// Although better way is to have _set_se_translator set - in our case we do not call it.
|
|
||||||
// The cause is usage of CrashRpt libraries - it gives better control on exception reporting.
|
|
||||||
# define SET_SEH_TO_CPP
|
|
||||||
//_set_se_translator(&SEHToCpp)
|
|
||||||
#else
|
|
||||||
# define SET_SEH_TO_CPP
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class CrashReporter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void init(const std::string& appname, const std::string& version = "", const std::string& url = "");
|
|
||||||
static void free();
|
|
||||||
static void initThread();
|
|
||||||
static void freeThread();
|
|
||||||
static bool isLoaded();
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
static BOOL WINAPI Callback(LPVOID /*lpvState*/);
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// RAII class to notify crash reporter about thread start/stop
|
|
||||||
class CrashReporterThreadPoint
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CrashReporterThreadPoint();
|
|
||||||
~CrashReporterThreadPoint();
|
|
||||||
};
|
|
||||||
|
|
||||||
class CrashReporterGuard
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CrashReporterGuard();
|
|
||||||
~CrashReporterGuard();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#include "HL_CsvReader.h"
|
|
||||||
#include "HL_String.h"
|
|
||||||
|
|
||||||
// --------- CsvFile ----------------
|
|
||||||
CsvReader::CsvReader(std::istream& stream)
|
|
||||||
:mInputStream(stream)
|
|
||||||
{}
|
|
||||||
|
|
||||||
CsvReader::~CsvReader()
|
|
||||||
{}
|
|
||||||
|
|
||||||
std::istream& CsvReader::stream() const
|
|
||||||
{
|
|
||||||
return mInputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CsvReader::readLine(std::vector<std::string>& cells)
|
|
||||||
{
|
|
||||||
cells.clear();
|
|
||||||
std::string line;
|
|
||||||
if (!std::getline(mInputStream, line))
|
|
||||||
return false;
|
|
||||||
strx::trim(line);
|
|
||||||
if (line.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
strx::split(line, cells, ",;");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#ifndef __HL_CSVREADER_H
|
|
||||||
#define __HL_CSVREADER_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <istream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class CsvReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CsvReader(std::istream& stream);
|
|
||||||
~CsvReader();
|
|
||||||
|
|
||||||
void setStream(std::istream& input);
|
|
||||||
std::istream& stream() const;
|
|
||||||
|
|
||||||
bool readLine(std::vector<std::string>& cells);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::istream& mInputStream;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __HL_EXCEPTION_H
|
|
||||||
#define __HL_EXCEPTION_H
|
|
||||||
|
|
||||||
#include <exception>
|
|
||||||
#include <memory.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
ERR_MEDIA_SOCKET_FAILED = 1, // Failed to create media socket
|
|
||||||
ERR_CANNOT_FIND_SESSION = 2, // Cannot find session
|
|
||||||
ERR_NO_CREDENTIALS = 3, // No credentials to configure instance
|
|
||||||
ERR_BAD_VARIANT_TYPE = 4, // Bad variant type conversion
|
|
||||||
ERR_RINSTANCE = 5,
|
|
||||||
ERR_SRTP = 6, // libsrtp error
|
|
||||||
ERR_WEBRTC = 7, // webrtc error
|
|
||||||
ERR_NOMEM = 8, // no more memory
|
|
||||||
ERR_WMME_FAILED = 9, // WMME error
|
|
||||||
ERR_QPC = 10, // QueryPerformanceCounter failed
|
|
||||||
ERR_BAD_PARAM = 11, // Bad parameter
|
|
||||||
ERR_NET_FAILED = 12, // Call to OS network subsystem failed
|
|
||||||
ERR_NOT_IMPLEMENTED = 13, // Not implemented in this build
|
|
||||||
ERR_MIXER_OVERFLOW = 14, // No more available channels in audio mixer
|
|
||||||
ERR_WAVFILE_FAILED = 15, // Error with .wav file
|
|
||||||
ERR_DSOUND = 16, // DSound error
|
|
||||||
ERR_COREAUDIO = 17, // CoreAudio error
|
|
||||||
ERR_CREATEWINDOW = 18, // CreateWindow failed
|
|
||||||
ERR_REGISTERNOTIFICATION = 19, // RegisterDeviceNotification failed
|
|
||||||
ERR_PCAP = 20, // Smth bad with libpcap
|
|
||||||
ERR_CACHE_FAILED = 21, // Failed to open cache directory
|
|
||||||
ERR_FILENOTOPEN = 22, // Cannot open the file
|
|
||||||
ERR_OPENSLES = 23 // OpenSL ES failed. Subcode has actual error code.
|
|
||||||
};
|
|
||||||
|
|
||||||
class Exception: public std::exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Exception(int code, int subcode = 0)
|
|
||||||
:mCode(code), mSubcode(subcode)
|
|
||||||
{
|
|
||||||
sprintf(mMessage, "%d-%d", code, subcode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception(int code, const char* message)
|
|
||||||
{
|
|
||||||
if (message)
|
|
||||||
strncpy(mMessage, message, (sizeof mMessage) - 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
Exception(const Exception& src)
|
|
||||||
:mCode(src.mCode), mSubcode(src.mSubcode)
|
|
||||||
{
|
|
||||||
memcpy(mMessage, src.mMessage, sizeof mMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
~Exception()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
int code() const
|
|
||||||
{
|
|
||||||
return mCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int subcode() const
|
|
||||||
{
|
|
||||||
return mSubcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* what() const noexcept
|
|
||||||
{
|
|
||||||
return mMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
int mCode, mSubcode;
|
|
||||||
char mMessage[256];
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
#include "HL_File.h"
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX) || defined(TARGET_ANDROID)
|
|
||||||
# include <unistd.h>
|
|
||||||
# include <sys/statvfs.h>
|
|
||||||
# include <memory.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool FileHelper::exists(const std::string& s)
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
std::ifstream ifs(s);
|
|
||||||
return !ifs.bad();
|
|
||||||
#else
|
|
||||||
return (access(s.c_str(), R_OK) != -1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FileHelper::exists(const char* s)
|
|
||||||
{
|
|
||||||
return exists(std::string(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileHelper::remove(const std::string& s)
|
|
||||||
{
|
|
||||||
::remove(s.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void FileHelper::remove(const char* s)
|
|
||||||
{
|
|
||||||
::remove(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::string FileHelper::gettempname()
|
|
||||||
// {
|
|
||||||
// #if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
|
||||||
// char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
|
|
||||||
// int code = mkstemp(template_filename);
|
|
||||||
|
|
||||||
// return template_filename;
|
|
||||||
// #elif defined(TARGET_WIN)
|
|
||||||
// char buffer[L_tmpnam];
|
|
||||||
// tmpnam(buffer);
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
if (s.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return s.front() == '/' || s.front() == '\\';
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string FileHelper::getCurrentDir()
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
return std::string();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX) || defined(TARGET_ANDROID)
|
|
||||||
char buf[512];
|
|
||||||
if (getcwd(buf, sizeof buf) != nullptr)
|
|
||||||
return buf;
|
|
||||||
else
|
|
||||||
return std::string();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string FileHelper::addTrailingSlash(const std::string& s)
|
|
||||||
{
|
|
||||||
if (s.empty())
|
|
||||||
return "/";
|
|
||||||
|
|
||||||
if (s.back() == '/' || s.back() == '\\')
|
|
||||||
return s;
|
|
||||||
|
|
||||||
return s + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string FileHelper::mergePathes(const std::string& s1, const std::string& s2)
|
|
||||||
{
|
|
||||||
std::string result(addTrailingSlash(s1));
|
|
||||||
if (isAbsolute(s2))
|
|
||||||
result += s2.substr(1, s2.size() - 1);
|
|
||||||
else
|
|
||||||
result += s2;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns free space on volume for path
|
|
||||||
size_t FileHelper::getFreespace(const std::string& path)
|
|
||||||
{
|
|
||||||
size_t r = static_cast<size_t>(-1);
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX)
|
|
||||||
struct statvfs stats; memset(&stats, 0, sizeof stats);
|
|
||||||
|
|
||||||
int retcode = statvfs(path.c_str(), &stats);
|
|
||||||
if (retcode == 0)
|
|
||||||
r = stats.f_bfree * stats.f_bsize;
|
|
||||||
#endif
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
#ifndef __HL_FILE_H
|
|
||||||
#define __HL_FILE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class FileHelper
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static bool exists(const std::string& s);
|
|
||||||
static bool exists(const char* s);
|
|
||||||
|
|
||||||
static void remove(const std::string& s);
|
|
||||||
static void remove(const char* s);
|
|
||||||
|
|
||||||
// static std::string gettempname();
|
|
||||||
static bool isAbsolute(const std::string& s);
|
|
||||||
|
|
||||||
static std::string getCurrentDir();
|
|
||||||
|
|
||||||
static std::string addTrailingSlash(const std::string& s);
|
|
||||||
static std::string mergePathes(const std::string& s1, const std::string& s2);
|
|
||||||
|
|
||||||
// Returns free space on volume for path
|
|
||||||
// Works for Linux only. For other systems (size_t)-1 is returned (for errors too)
|
|
||||||
static size_t getFreespace(const std::string& path);
|
|
||||||
static std::string expandUserHome(const std::string& path);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
#include "HL_HepSupport.h"
|
|
||||||
|
|
||||||
using namespace HEP;
|
|
||||||
|
|
||||||
static const uint32_t HEPID1 = 0x011002;
|
|
||||||
static const uint32_t HEPID2 = 0x021002;
|
|
||||||
static const uint32_t HEPID3 = 0x48455033;
|
|
||||||
|
|
||||||
|
|
||||||
bool Packet::parseV3(const ByteBuffer& packet)
|
|
||||||
{
|
|
||||||
if (packet.size() < 30)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BufferReader r(packet);
|
|
||||||
char signature[4];
|
|
||||||
r.readBuffer(signature, 4);
|
|
||||||
|
|
||||||
if (signature[0] != 'H' || signature[1] != 'E' || signature[2] != 'P' || signature[3] != '3')
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Total length
|
|
||||||
int l = r.readUShort();
|
|
||||||
l -= 6;
|
|
||||||
|
|
||||||
InternetAddress sourceAddr4, destAddr4, sourceAddr6, destAddr6;
|
|
||||||
uint16_t sourcePort = 0, destPort = 0;
|
|
||||||
while (r.count() < packet.size())
|
|
||||||
{
|
|
||||||
mVendorId = (VendorId)r.readUShort();
|
|
||||||
ChunkType chunkType = (ChunkType)r.readUShort();
|
|
||||||
int chunkLength = r.readUShort();
|
|
||||||
|
|
||||||
switch (chunkType)
|
|
||||||
{
|
|
||||||
case ChunkType::IPProtocolFamily:
|
|
||||||
mIpProtocolFamily = r.readUChar();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::IPProtocolID:
|
|
||||||
mIpProtocolId = r.readUChar();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::IP4SourceAddress:
|
|
||||||
sourceAddr4 = r.readIp(AF_INET);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::IP4DestinationAddress:
|
|
||||||
destAddr4 = r.readIp(AF_INET);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::IP6SourceAddress:
|
|
||||||
sourceAddr6 = r.readIp(AF_INET);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::IP6DestinationAddress:
|
|
||||||
destAddr6 = r.readIp(AF_INET6);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::SourcePort:
|
|
||||||
sourcePort = r.readUShort();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::DestinationPort:
|
|
||||||
destPort = r.readUShort();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::Timestamp:
|
|
||||||
mTimestamp.tv_sec = r.readUInt();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::TimestampMicro:
|
|
||||||
mTimestamp.tv_usec = r.readUInt() * 1000;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::ProtocolType:
|
|
||||||
mProtocolType = (ProtocolId)r.readUChar();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::CaptureAgentID:
|
|
||||||
mCaptureAgentId = r.readUInt();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::KeepAliveTimer:
|
|
||||||
mKeepAliveTimer = r.readUShort();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::AuthenticationKey:
|
|
||||||
mAuthenticateKey.resize(chunkLength - 6);
|
|
||||||
r.readBuffer(mAuthenticateKey.mutableData(), mAuthenticateKey.size());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ChunkType::PacketPayload:
|
|
||||||
mBodyOffset = r.count();
|
|
||||||
r.readBuffer(mBody, chunkLength - 6);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
r.readBuffer(nullptr, chunkLength - 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sourceAddr4.isEmpty())
|
|
||||||
mSourceAddress = sourceAddr4;
|
|
||||||
else
|
|
||||||
if (!sourceAddr6.isEmpty())
|
|
||||||
mSourceAddress = sourceAddr6;
|
|
||||||
|
|
||||||
if (!mSourceAddress.isEmpty())
|
|
||||||
mSourceAddress.setPort(sourcePort);
|
|
||||||
|
|
||||||
if (!destAddr4.isEmpty())
|
|
||||||
mDestinationAddress = destAddr4;
|
|
||||||
else
|
|
||||||
if (!destAddr6.isEmpty())
|
|
||||||
mDestinationAddress = destAddr6;
|
|
||||||
|
|
||||||
if (!mDestinationAddress.isEmpty())
|
|
||||||
mDestinationAddress.setPort(destPort);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Packet::parseV2(const ByteBuffer &packet)
|
|
||||||
{
|
|
||||||
if (packet.size() < 31)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (packet[0] != 0x02)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
BufferReader r(packet);
|
|
||||||
r.readBuffer(nullptr, 4);
|
|
||||||
|
|
||||||
uint16_t sourcePort = r.readUShort();
|
|
||||||
uint16_t dstPort = r.readUShort();
|
|
||||||
mSourceAddress = r.readIp(AF_INET);
|
|
||||||
mSourceAddress.setPort(sourcePort);
|
|
||||||
mDestinationAddress = r.readIp(AF_INET);
|
|
||||||
mDestinationAddress.setPort(dstPort);
|
|
||||||
mTimestamp.tv_sec = r.readUInt();
|
|
||||||
mTimestamp.tv_usec = r.readUInt() * 1000;
|
|
||||||
mCaptureAgentId = r.readUShort();
|
|
||||||
r.readBuffer(nullptr, 2);
|
|
||||||
mBody.clear();
|
|
||||||
mBodyOffset = r.count();
|
|
||||||
r.readBuffer(mBody, 65536 - 28);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define WRITE_CHUNK_UCHAR(T, V) {w.writeUShort((uint16_t)mVendorId); w.writeUShort((uint16_t)T); w.writeUShort(1); w.writeUChar((uint8_t)V);}
|
|
||||||
#define WRITE_CHUNK_USHORT(T, V) {w.writeUShort((uint16_t)mVendorId); w.writeUShort((uint16_t)T); w.writeUShort(2); w.writeUShort((uint16_t)V);}
|
|
||||||
#define WRITE_CHUNK_UINT(T, V) {w.writeUShort((uint16_t)mVendorId); w.writeUShort((uint16_t)T); w.writeUShort(4); w.writeUInt((uint32_t)V);}
|
|
||||||
#define WRITE_CHUNK_IP4(T, V) {w.writeUShort((uint16_t)mVendorId); w.writeUShort((uint16_t)T); w.writeUShort(4); w.writeIp(V);}
|
|
||||||
#define WRITE_CHUNK_IP6(T, V) {w.writeUShort((uint16_t)mVendorId); w.writeUShort((uint16_t)T); w.writeUShort(8); w.writeIp(V);}
|
|
||||||
#define WRITE_CHUNK_BUFFER(T, V) {w.writeUShort((uint16_t)mVendorId); w.writeUShort((uint16_t)T); w.writeUShort(8); w.writeBuffer(V.data(), V.size());}
|
|
||||||
|
|
||||||
ByteBuffer Packet::buildV3()
|
|
||||||
{
|
|
||||||
ByteBuffer r; r.resize(mBody.size() + 512);
|
|
||||||
BufferWriter w(r);
|
|
||||||
|
|
||||||
// Signature
|
|
||||||
w.writeBuffer("HEP3", 4);
|
|
||||||
|
|
||||||
// Reserve place for total length
|
|
||||||
w.writeUShort(0);
|
|
||||||
|
|
||||||
WRITE_CHUNK_UCHAR(ChunkType::IPProtocolFamily, mIpProtocolFamily);
|
|
||||||
WRITE_CHUNK_UCHAR(ChunkType::IPProtocolID, mIpProtocolId);
|
|
||||||
|
|
||||||
// Source address
|
|
||||||
if (!mSourceAddress.isEmpty())
|
|
||||||
{
|
|
||||||
if (mSourceAddress.isV4())
|
|
||||||
WRITE_CHUNK_IP4(ChunkType::IP4SourceAddress, mSourceAddress)
|
|
||||||
else
|
|
||||||
if (mSourceAddress.isV6())
|
|
||||||
WRITE_CHUNK_IP6(ChunkType::IP6SourceAddress, mSourceAddress);
|
|
||||||
|
|
||||||
WRITE_CHUNK_USHORT(ChunkType::SourcePort, mSourceAddress.port());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destination address
|
|
||||||
if (!mDestinationAddress.isEmpty())
|
|
||||||
{
|
|
||||||
if (mDestinationAddress.isV4())
|
|
||||||
WRITE_CHUNK_IP4(ChunkType::IP4DestinationAddress, mDestinationAddress)
|
|
||||||
else
|
|
||||||
if (mDestinationAddress.isV6())
|
|
||||||
WRITE_CHUNK_IP6(ChunkType::IP6DestinationAddress, mDestinationAddress);
|
|
||||||
|
|
||||||
WRITE_CHUNK_USHORT(ChunkType::DestinationPort, mDestinationAddress.port());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timestamp
|
|
||||||
WRITE_CHUNK_UINT(ChunkType::Timestamp, mTimestamp.tv_sec);
|
|
||||||
|
|
||||||
// TimestampMicro
|
|
||||||
WRITE_CHUNK_UINT(ChunkType::TimestampMicro, mTimestamp.tv_usec / 1000);
|
|
||||||
|
|
||||||
// Protocol type
|
|
||||||
WRITE_CHUNK_UINT(ChunkType::ProtocolType, mProtocolType);
|
|
||||||
|
|
||||||
// Capture agent ID
|
|
||||||
WRITE_CHUNK_UINT(ChunkType::CaptureAgentID, mCaptureAgentId);
|
|
||||||
|
|
||||||
// Keep alive timer value
|
|
||||||
WRITE_CHUNK_USHORT(ChunkType::KeepAliveTimer, mKeepAliveTimer);
|
|
||||||
|
|
||||||
// Authentication key
|
|
||||||
WRITE_CHUNK_BUFFER(ChunkType::AuthenticationKey, mAuthenticateKey);
|
|
||||||
|
|
||||||
// Payload
|
|
||||||
WRITE_CHUNK_BUFFER(ChunkType::PacketPayload, mBody);
|
|
||||||
|
|
||||||
r.resize(w.offset());
|
|
||||||
|
|
||||||
w.rewind(); w.skip(4); w.writeUShort((uint16_t)r.size());
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
#ifndef __HELPER_HEP_SUPPORT_H
|
|
||||||
#define __HELPER_HEP_SUPPORT_H
|
|
||||||
|
|
||||||
#include "HL_ByteBuffer.h"
|
|
||||||
#include "HL_InternetAddress.h"
|
|
||||||
|
|
||||||
namespace HEP
|
|
||||||
{
|
|
||||||
enum class ChunkType
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
IPProtocolFamily,
|
|
||||||
IPProtocolID,
|
|
||||||
IP4SourceAddress,
|
|
||||||
IP4DestinationAddress,
|
|
||||||
IP6SourceAddress,
|
|
||||||
IP6DestinationAddress,
|
|
||||||
SourcePort,
|
|
||||||
DestinationPort,
|
|
||||||
Timestamp,
|
|
||||||
TimestampMicro,
|
|
||||||
ProtocolType, // Maps to Protocol Types below
|
|
||||||
CaptureAgentID,
|
|
||||||
KeepAliveTimer,
|
|
||||||
AuthenticationKey,
|
|
||||||
PacketPayload,
|
|
||||||
CompressedPayload,
|
|
||||||
InternalC
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class VendorId
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
FreeSwitch,
|
|
||||||
Kamailio,
|
|
||||||
OpenSIPS,
|
|
||||||
Asterisk,
|
|
||||||
Homer,
|
|
||||||
SipXecs
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ProtocolId
|
|
||||||
{
|
|
||||||
Reserved = 0,
|
|
||||||
SIP,
|
|
||||||
XMPP,
|
|
||||||
SDP,
|
|
||||||
RTP,
|
|
||||||
RTCP,
|
|
||||||
MGCP,
|
|
||||||
MEGACO,
|
|
||||||
M2UA,
|
|
||||||
M3UA,
|
|
||||||
IAX,
|
|
||||||
H322,
|
|
||||||
H321
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Packet
|
|
||||||
{
|
|
||||||
bool parseV3(const ByteBuffer& packet);
|
|
||||||
bool parseV2(const ByteBuffer& packet);
|
|
||||||
ByteBuffer buildV3();
|
|
||||||
|
|
||||||
uint8_t
|
|
||||||
mIpProtocolFamily,
|
|
||||||
mIpProtocolId;
|
|
||||||
|
|
||||||
InternetAddress
|
|
||||||
mSourceAddress,
|
|
||||||
mDestinationAddress;
|
|
||||||
|
|
||||||
timeval mTimestamp;
|
|
||||||
ProtocolId mProtocolType;
|
|
||||||
uint16_t mCaptureAgentId;
|
|
||||||
uint16_t mKeepAliveTimer;
|
|
||||||
ByteBuffer mAuthenticateKey;
|
|
||||||
ByteBuffer mBody;
|
|
||||||
VendorId mVendorId;
|
|
||||||
uint32_t mBodyOffset = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __HL_INTERNETADDRESS_H
|
|
||||||
#define __HL_INTERNETADDRESS_H
|
|
||||||
|
|
||||||
#include "ice/ICEAddress.h"
|
|
||||||
typedef ice::NetworkAddress InternetAddress;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,39 +0,0 @@
|
||||||
#ifndef HL_IUUP_H
|
|
||||||
#define HL_IUUP_H
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class IuUP
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum class PduType
|
|
||||||
{
|
|
||||||
DataWithCrc = 0,
|
|
||||||
DataNoCrc = 1,
|
|
||||||
ControlProc = 14
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Frame
|
|
||||||
{
|
|
||||||
PduType mPduType;
|
|
||||||
uint8_t mFrameNumber;
|
|
||||||
uint8_t mFqc;
|
|
||||||
uint8_t mRfci;
|
|
||||||
uint8_t mHeaderCrc;
|
|
||||||
bool mHeaderCrcOk;
|
|
||||||
uint16_t mPayloadCrc;
|
|
||||||
bool mPayloadCrcOk;
|
|
||||||
const uint8_t* mPayload;
|
|
||||||
uint16_t mPayloadSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Default value is false */
|
|
||||||
static bool TwoBytePseudoheader;
|
|
||||||
|
|
||||||
static bool parse(const uint8_t* packet, int size, Frame& result);
|
|
||||||
static bool parse2(const uint8_t* packet, int size, Frame& result);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // HL_IUUP_H
|
|
||||||
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __LOG_H
|
|
||||||
#define __LOG_H
|
|
||||||
|
|
||||||
#include "ice/ICELog.h"
|
|
||||||
|
|
||||||
using ice::GLogger;
|
|
||||||
using ice::LogLock;
|
|
||||||
|
|
||||||
using ice::LL_MEDIA;
|
|
||||||
using ice::LL_DEBUG;
|
|
||||||
using ice::LL_INFO;
|
|
||||||
using ice::LL_CRITICAL;
|
|
||||||
using ice::LL_NONE;
|
|
||||||
using ice::LL_SPECIAL;
|
|
||||||
using ice::LL_ERROR;
|
|
||||||
using ice::LogLevelHelper;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "HL_NetworkFrame.h"
|
|
||||||
#include "HL_InternetAddress.h"
|
|
||||||
|
|
||||||
#define ETHERTYPE_MPLS_UC (0x8847)
|
|
||||||
#define ETHERTYPE_MPLS_MC (0x8848)
|
|
||||||
#define ETHERTYPE_IPV6 (0x86dd)
|
|
||||||
#define ETHERTYPE_IP (0x0800)
|
|
||||||
|
|
||||||
#define MPLS_STACK_MASK (0x00000100)
|
|
||||||
#define MPLS_STACK_SHIFT (8)
|
|
||||||
|
|
||||||
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForRaw(const Packet& data)
|
|
||||||
{
|
|
||||||
const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(data.mData);
|
|
||||||
|
|
||||||
if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
|
|
||||||
return Payload();
|
|
||||||
|
|
||||||
|
|
||||||
switch (ip4->version())
|
|
||||||
{
|
|
||||||
case 4:
|
|
||||||
return GetUdpPayloadForIp4(data);
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
return GetUdpPayloadForIp6(data);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return Payload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForEthernet(const Packet& data)
|
|
||||||
{
|
|
||||||
Packet result(data);
|
|
||||||
|
|
||||||
const EthernetHeader* ethernet = reinterpret_cast<const EthernetHeader*>(data.mData);
|
|
||||||
|
|
||||||
// Skip ethernet header
|
|
||||||
result.mData += sizeof(EthernetHeader);
|
|
||||||
result.mLength -= sizeof(EthernetHeader);
|
|
||||||
|
|
||||||
// See if there is Vlan header
|
|
||||||
uint16_t proto = 0;
|
|
||||||
if (ethernet->mEtherType == 129)
|
|
||||||
{
|
|
||||||
// Skip 1 or more VLAN headers
|
|
||||||
do
|
|
||||||
{
|
|
||||||
const VlanHeader* vlan = reinterpret_cast<const VlanHeader*>(result.mData);
|
|
||||||
result.mData += sizeof(VlanHeader);
|
|
||||||
result.mLength -= sizeof(VlanHeader);
|
|
||||||
proto = ntohs(vlan->mData);
|
|
||||||
}
|
|
||||||
while (proto == 0x8100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip MPLS headers
|
|
||||||
switch (proto)
|
|
||||||
{
|
|
||||||
case ETHERTYPE_MPLS_UC:
|
|
||||||
case ETHERTYPE_MPLS_MC:
|
|
||||||
// Parse MPLS here until marker "bottom of mpls stack"
|
|
||||||
for(bool bottomOfStack = false; !bottomOfStack;
|
|
||||||
bottomOfStack = ((ntohl(*(uint32_t*)(result.mData - 4)) & MPLS_STACK_MASK) >> MPLS_STACK_SHIFT) != 0)
|
|
||||||
{
|
|
||||||
result.mData += 4;
|
|
||||||
result.mLength -=4;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ETHERTYPE_IP:
|
|
||||||
// Next IPv4 packet
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ETHERTYPE_IPV6:
|
|
||||||
// Next IPv6 packet
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(result.mData);
|
|
||||||
|
|
||||||
if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
|
|
||||||
return Payload();
|
|
||||||
|
|
||||||
|
|
||||||
switch (ip4->version())
|
|
||||||
{
|
|
||||||
case 4:
|
|
||||||
return GetUdpPayloadForIp4(result);
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
return GetUdpPayloadForIp6(result);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return Payload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForSLL(const Packet& data)
|
|
||||||
{
|
|
||||||
Packet result(data);
|
|
||||||
|
|
||||||
if (result.mLength < 16)
|
|
||||||
return Payload();
|
|
||||||
|
|
||||||
const LinuxSllHeader* sll = reinterpret_cast<const LinuxSllHeader*>(result.mData);
|
|
||||||
|
|
||||||
result.mData += sizeof(LinuxSllHeader);
|
|
||||||
result.mLength -= sizeof(LinuxSllHeader);
|
|
||||||
|
|
||||||
switch (ntohs(sll->mProtocolType))
|
|
||||||
{
|
|
||||||
case 0x0800:
|
|
||||||
return GetUdpPayloadForIp4(result);
|
|
||||||
|
|
||||||
case 0x86DD:
|
|
||||||
return GetUdpPayloadForIp6(result);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return Payload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForLoopback(const Packet& data)
|
|
||||||
{
|
|
||||||
Packet result(data);
|
|
||||||
|
|
||||||
if (result.mLength < 16)
|
|
||||||
return Payload();
|
|
||||||
|
|
||||||
struct LoopbackHeader
|
|
||||||
{
|
|
||||||
uint32_t mProtocolType;
|
|
||||||
};
|
|
||||||
|
|
||||||
const LoopbackHeader* lh = reinterpret_cast<const LoopbackHeader*>(result.mData);
|
|
||||||
|
|
||||||
result.mData += sizeof(LoopbackHeader);
|
|
||||||
result.mLength -= sizeof(LoopbackHeader);
|
|
||||||
|
|
||||||
switch (lh->mProtocolType)
|
|
||||||
{
|
|
||||||
case AF_INET:
|
|
||||||
return GetUdpPayloadForIp4(result);
|
|
||||||
|
|
||||||
case AF_INET6:
|
|
||||||
return GetUdpPayloadForIp6(result);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return Payload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForIp4(const Packet& data)
|
|
||||||
{
|
|
||||||
Packet result(data);
|
|
||||||
const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(data.mData);
|
|
||||||
if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
|
|
||||||
return Payload();
|
|
||||||
|
|
||||||
result.mData += ip4->headerLength();
|
|
||||||
result.mLength -= ip4->headerLength();
|
|
||||||
|
|
||||||
const UdpHeader* udp = reinterpret_cast<const UdpHeader*>(result.mData);
|
|
||||||
result.mData += sizeof(UdpHeader);
|
|
||||||
result.mLength -= sizeof(UdpHeader);
|
|
||||||
|
|
||||||
// Check if UDP payload length is smaller than full packet length. It can be VLAN trailer data - we need to skip it
|
|
||||||
size_t length = ntohs(udp->mDatagramLength);
|
|
||||||
if (length - sizeof(UdpHeader) < (size_t)result.mLength)
|
|
||||||
result.mLength = length - sizeof(UdpHeader);
|
|
||||||
|
|
||||||
InternetAddress addr_source;
|
|
||||||
addr_source.setIp(ip4->mSource);
|
|
||||||
addr_source.setPort(ntohs(udp->mSourcePort));
|
|
||||||
|
|
||||||
InternetAddress addr_dest;
|
|
||||||
addr_dest.setIp(ip4->mDestination);
|
|
||||||
addr_dest.setPort(ntohs(udp->mDestinationPort));
|
|
||||||
|
|
||||||
return {.data = result, .source = addr_source, .dest = addr_dest};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Ip6Header
|
|
||||||
{
|
|
||||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
||||||
uint8_t traffic_class_hi:4,
|
|
||||||
version:4;
|
|
||||||
uint8_t flow_label_hi:4,
|
|
||||||
traffic_class_lo:4;
|
|
||||||
uint16_t flow_label_lo;
|
|
||||||
|
|
||||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
|
||||||
uint8_t version:4,
|
|
||||||
traffic_class_hi:4;
|
|
||||||
uint8_t traffic_class_lo:4,
|
|
||||||
flow_label_hi:4;
|
|
||||||
uint16_t flow_label_lo;
|
|
||||||
#else
|
|
||||||
# error "Please fix endianness defines"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint16_t payload_len;
|
|
||||||
uint8_t next_header;
|
|
||||||
uint8_t hop_limit;
|
|
||||||
|
|
||||||
struct in6_addr src_ip;
|
|
||||||
struct in6_addr dst_ip;
|
|
||||||
};
|
|
||||||
|
|
||||||
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForIp6(const Packet& data)
|
|
||||||
{
|
|
||||||
Packet result(data);
|
|
||||||
const Ip6Header* ip6 = reinterpret_cast<const Ip6Header*>(result.mData);
|
|
||||||
/*if (ip6->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
|
|
||||||
return PacketData(nullptr, 0);
|
|
||||||
*/
|
|
||||||
result.mData += sizeof(Ip6Header);
|
|
||||||
result.mLength -= sizeof(Ip6Header);
|
|
||||||
//std::cout << sizeof(Ip6Header) << std::endl;
|
|
||||||
|
|
||||||
const UdpHeader* udp = reinterpret_cast<const UdpHeader*>(result.mData);
|
|
||||||
result.mData += sizeof(UdpHeader);
|
|
||||||
result.mLength -= sizeof(UdpHeader);
|
|
||||||
|
|
||||||
InternetAddress addr_source;
|
|
||||||
addr_source.setIp(ip6->src_ip);
|
|
||||||
addr_source.setPort(ntohs(udp->mSourcePort));
|
|
||||||
|
|
||||||
InternetAddress addr_dest;
|
|
||||||
addr_dest.setIp(ip6->dst_ip);
|
|
||||||
addr_dest.setPort(ntohs(udp->mDestinationPort));
|
|
||||||
|
|
||||||
return {.data = result, .source = addr_source, .dest = addr_dest};
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
#ifndef _HL_NETWORK_FRAME_H
|
|
||||||
#define _HL_NETWORK_FRAME_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "HL_InternetAddress.h"
|
|
||||||
|
|
||||||
class NetworkFrame
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct Packet
|
|
||||||
{
|
|
||||||
const uint8_t* mData;
|
|
||||||
size_t mLength;
|
|
||||||
|
|
||||||
Packet(const uint8_t* data, size_t length)
|
|
||||||
:mData(data), mLength(length)
|
|
||||||
{}
|
|
||||||
|
|
||||||
Packet()
|
|
||||||
:mData(nullptr), mLength(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool is_empty() const
|
|
||||||
{
|
|
||||||
return mData == nullptr || mLength == 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Payload
|
|
||||||
{
|
|
||||||
Packet data;
|
|
||||||
InternetAddress source;
|
|
||||||
InternetAddress dest;
|
|
||||||
};
|
|
||||||
|
|
||||||
static Payload GetUdpPayloadForEthernet(const Packet& data);
|
|
||||||
static Payload GetUdpPayloadForIp4(const Packet& data);
|
|
||||||
static Payload GetUdpPayloadForIp6(const Packet& data);
|
|
||||||
static Payload GetUdpPayloadForSLL(const Packet& data);
|
|
||||||
static Payload GetUdpPayloadForLoopback(const Packet& data);
|
|
||||||
static Payload GetUdpPayloadForRaw(const Packet& data);
|
|
||||||
|
|
||||||
struct EthernetHeader
|
|
||||||
{
|
|
||||||
/* Ethernet addresses are 6 bytes */
|
|
||||||
static const int AddressLength = 6;
|
|
||||||
uint8_t mEtherDHost[AddressLength]; /* Destination host address */
|
|
||||||
uint8_t mEtherSHost[AddressLength]; /* Source host address */
|
|
||||||
uint16_t mEtherType; /* IP? ARP? RARP? etc */
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
struct /*__attribute__((packed))*/ LinuxSllHeader
|
|
||||||
#else
|
|
||||||
struct __attribute__((packed)) LinuxSllHeader
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
uint16_t mPacketType;
|
|
||||||
uint16_t mARPHRD;
|
|
||||||
uint16_t mAddressLength;
|
|
||||||
uint64_t mAddress;
|
|
||||||
uint16_t mProtocolType;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VlanHeader
|
|
||||||
{
|
|
||||||
uint16_t mMagicId;
|
|
||||||
uint16_t mData;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Ip4Header
|
|
||||||
{
|
|
||||||
uint8_t mVhl; /* version << 4 | header length >> 2 */
|
|
||||||
uint8_t mTos; /* type of service */
|
|
||||||
uint16_t mLen; /* total length */
|
|
||||||
uint16_t mId; /* identification */
|
|
||||||
uint16_t mOffset; /* fragment offset field */
|
|
||||||
#define IP_RF 0x8000 /* reserved fragment flag */
|
|
||||||
#define IP_DF 0x4000 /* dont fragment flag */
|
|
||||||
#define IP_MF 0x2000 /* more fragments flag */
|
|
||||||
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
|
|
||||||
uint8_t mTtl; /* time to live */
|
|
||||||
uint8_t mProtocol; /* protocol */
|
|
||||||
uint16_t mChecksum; /* checksum */
|
|
||||||
in_addr mSource,
|
|
||||||
mDestination; /* source and dest address */
|
|
||||||
|
|
||||||
int headerLength() const
|
|
||||||
{
|
|
||||||
return (mVhl & 0x0f) * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
int version() const
|
|
||||||
{
|
|
||||||
return mVhl >> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
const in_addr& source4() const { return mSource; }
|
|
||||||
const in_addr& dest4() const { return mDestination; }
|
|
||||||
const in6_addr& source6() const { return (const in6_addr&)mSource; }
|
|
||||||
const in6_addr& dest6() const { return (const in6_addr&)mDestination; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UdpHeader
|
|
||||||
{
|
|
||||||
uint16_t mSourcePort; /* source port */
|
|
||||||
uint16_t mDestinationPort;
|
|
||||||
uint16_t mDatagramLength; /* datagram length */
|
|
||||||
uint16_t mDatagramChecksum; /* datagram checksum */
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct TcpHeader
|
|
||||||
{
|
|
||||||
uint16_t mSourcePort; /* source port */
|
|
||||||
uint16_t mDestinationPort; /* destination port */
|
|
||||||
uint32_t mSeqNo; /* sequence number */
|
|
||||||
uint32_t mAckNo; /* acknowledgement number */
|
|
||||||
uint32_t mDataOffset; /* data offset, rsvd */
|
|
||||||
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
|
|
||||||
uint8_t mFlags;
|
|
||||||
#define TH_FIN 0x01
|
|
||||||
#define TH_SYN 0x02
|
|
||||||
#define TH_RST 0x04
|
|
||||||
#define TH_PUSH 0x08
|
|
||||||
#define TH_ACK 0x10
|
|
||||||
#define TH_URG 0x20
|
|
||||||
#define TH_ECE 0x40
|
|
||||||
#define TH_CWR 0x80
|
|
||||||
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
|
|
||||||
uint16_t mWindow; /* window */
|
|
||||||
uint16_t mChecksum; /* checksum */
|
|
||||||
uint16_t mUrgentPointer; /* urgent pointer */
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 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/. */
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
|
||||||
# include <asm/ioctls.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "../engine_config.h"
|
|
||||||
#include "HL_NetworkSocket.h"
|
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
|
||||||
# include <fcntl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(TARGET_WIN)
|
|
||||||
# include <unistd.h>
|
|
||||||
#endif
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
DatagramSocket::DatagramSocket()
|
|
||||||
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DatagramSocket::~DatagramSocket()
|
|
||||||
{
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatagramSocket::open(int family)
|
|
||||||
{
|
|
||||||
if (mHandle != INVALID_SOCKET || mFamily != family)
|
|
||||||
closeSocket();
|
|
||||||
|
|
||||||
assert(family == AF_INET || family == AF_INET6);
|
|
||||||
mFamily = family;
|
|
||||||
mHandle = ::socket(mFamily, SOCK_DGRAM, IPPROTO_UDP);
|
|
||||||
if (mHandle != INVALID_SOCKET)
|
|
||||||
{
|
|
||||||
sockaddr_in addr4; sockaddr_in6 addr6;
|
|
||||||
memset(&addr4, 0, sizeof(addr4)); memset(&addr6, 0, sizeof(addr6));
|
|
||||||
socklen_t l = mFamily == AF_INET ? sizeof(addr4) : sizeof(addr6);
|
|
||||||
int retcode = getsockname(mHandle, (mFamily == AF_INET ? (sockaddr*)&addr4 : (sockaddr*)&addr6), &l);
|
|
||||||
if (!retcode)
|
|
||||||
{
|
|
||||||
mLocalPort = ntohs(mFamily == AF_INET ? addr4.sin_port : addr6.sin6_port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int DatagramSocket::localport()
|
|
||||||
{
|
|
||||||
return mLocalPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatagramSocket::sendDatagram(InternetAddress &dest, const void *packetData, unsigned int packetSize)
|
|
||||||
{
|
|
||||||
if (mHandle == INVALID_SOCKET)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*int sent = */::sendto(mHandle, (const char*)packetData, packetSize, 0, dest.genericsockaddr(), dest.sockaddrLen());
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned DatagramSocket::recvDatagram(InternetAddress &src, void *packetBuffer, unsigned packetCapacity)
|
|
||||||
{
|
|
||||||
if (mHandle == INVALID_SOCKET)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
sockaddr_in sourceaddr;
|
|
||||||
#ifdef WIN32
|
|
||||||
int addrlen = sizeof(sourceaddr);
|
|
||||||
#else
|
|
||||||
socklen_t addrlen = sizeof(sourceaddr);
|
|
||||||
#endif
|
|
||||||
int received = ::recvfrom(mHandle, (char*)packetBuffer, packetCapacity, 0, (sockaddr*)&sourceaddr, &addrlen);
|
|
||||||
if (received > 0)
|
|
||||||
{
|
|
||||||
src = InternetAddress((sockaddr&)sourceaddr, addrlen);
|
|
||||||
return received;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatagramSocket::internalClose()
|
|
||||||
{
|
|
||||||
if (mHandle != INVALID_SOCKET)
|
|
||||||
{
|
|
||||||
#ifdef WIN32
|
|
||||||
::closesocket(mHandle);
|
|
||||||
#else
|
|
||||||
close(mHandle);
|
|
||||||
#endif
|
|
||||||
mHandle = INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatagramSocket::closeSocket()
|
|
||||||
{
|
|
||||||
internalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DatagramSocket::isValid() const
|
|
||||||
{
|
|
||||||
return mHandle != INVALID_SOCKET;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DatagramSocket::family() const
|
|
||||||
{
|
|
||||||
return mFamily;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DatagramSocket::setBlocking(bool blocking)
|
|
||||||
{
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
unsigned long mode = blocking ? 0 : 1;
|
|
||||||
return (ioctlsocket(mHandle, FIONBIO, &mode) == 0) ? true : false;
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
|
||||||
int flags = fcntl(mHandle, F_GETFL, 0);
|
|
||||||
if (flags < 0)
|
|
||||||
return false;
|
|
||||||
flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK);
|
|
||||||
return (fcntl(mHandle, F_SETFL, flags) == 0) ? true : false;
|
|
||||||
#endif
|
|
||||||
#if defined(TARGET_ANDROID)
|
|
||||||
unsigned long mode = blocking ? 0 : 1;
|
|
||||||
return (ioctl(mHandle, FIONBIO, &mode) == 0) ? true : false;
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKET DatagramSocket::socket() const
|
|
||||||
{
|
|
||||||
return mHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
DatagramAgreggator::DatagramAgreggator()
|
|
||||||
{
|
|
||||||
FD_ZERO(&mReadSet);
|
|
||||||
mMaxHandle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DatagramAgreggator::~DatagramAgreggator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatagramAgreggator::addSocket(PDatagramSocket socket)
|
|
||||||
{
|
|
||||||
if (socket->mHandle == INVALID_SOCKET)
|
|
||||||
return;
|
|
||||||
|
|
||||||
FD_SET(socket->mHandle, &mReadSet);
|
|
||||||
if (socket->mHandle > mMaxHandle)
|
|
||||||
mMaxHandle = socket->mHandle;
|
|
||||||
|
|
||||||
mSocketVector.push_back(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned DatagramAgreggator::count()
|
|
||||||
{
|
|
||||||
return mSocketVector.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DatagramAgreggator::hasDataAtIndex(unsigned index)
|
|
||||||
{
|
|
||||||
PDatagramSocket socket = mSocketVector[index];
|
|
||||||
return FD_ISSET(socket->mHandle, &mReadSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
PDatagramSocket DatagramAgreggator::socketAt(unsigned index)
|
|
||||||
{
|
|
||||||
return mSocketVector[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DatagramAgreggator::waitForData(unsigned milliseconds)
|
|
||||||
{
|
|
||||||
timeval tv;
|
|
||||||
tv.tv_sec = milliseconds / 1000;
|
|
||||||
tv.tv_usec = (milliseconds % 1000) * 1000;
|
|
||||||
|
|
||||||
int rescode = ::select(mMaxHandle, &mReadSet, NULL, NULL, &tv);
|
|
||||||
return rescode > 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __NETWORK_SOCKET_H
|
|
||||||
#define __NETWORK_SOCKET_H
|
|
||||||
|
|
||||||
#include "HL_InternetAddress.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class NetworkSocket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual int localport() = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class DatagramSocket
|
|
||||||
{
|
|
||||||
friend class SocketHeap;
|
|
||||||
friend class DatagramAgreggator;
|
|
||||||
public:
|
|
||||||
DatagramSocket();
|
|
||||||
virtual ~DatagramSocket();
|
|
||||||
|
|
||||||
virtual int localport();
|
|
||||||
|
|
||||||
virtual void sendDatagram(InternetAddress& dest, const void* packetData, unsigned packetSize);
|
|
||||||
virtual unsigned recvDatagram(InternetAddress& src, void* packetBuffer, unsigned packetCapacity);
|
|
||||||
virtual void closeSocket();
|
|
||||||
virtual bool isValid() const;
|
|
||||||
virtual int family() const;
|
|
||||||
virtual bool setBlocking(bool blocking);
|
|
||||||
virtual SOCKET socket() const;
|
|
||||||
|
|
||||||
virtual void open(int family);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
int mFamily;
|
|
||||||
SOCKET mHandle;
|
|
||||||
int mLocalPort;
|
|
||||||
void internalClose();
|
|
||||||
};
|
|
||||||
typedef std::shared_ptr<DatagramSocket> PDatagramSocket;
|
|
||||||
|
|
||||||
class DatagramAgreggator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DatagramAgreggator();
|
|
||||||
~DatagramAgreggator();
|
|
||||||
|
|
||||||
void addSocket(PDatagramSocket socket);
|
|
||||||
unsigned count();
|
|
||||||
bool hasDataAtIndex(unsigned index);
|
|
||||||
PDatagramSocket socketAt(unsigned index);
|
|
||||||
|
|
||||||
bool waitForData(unsigned milliseconds);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
typedef std::vector<PDatagramSocket> SocketList;
|
|
||||||
SocketList mSocketVector;
|
|
||||||
fd_set mReadSet;
|
|
||||||
SOCKET mMaxHandle;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,152 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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/. */
|
|
||||||
|
|
||||||
#include "HL_OsVersion.h"
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#if defined(USE_MINIDUMP)
|
|
||||||
# include <DbgHelp.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int winVersion()
|
|
||||||
{
|
|
||||||
DWORD dwVersion = 0;
|
|
||||||
DWORD dwMajorVersion = 0;
|
|
||||||
DWORD dwMinorVersion = 0;
|
|
||||||
DWORD dwBuild = 0;
|
|
||||||
|
|
||||||
dwVersion = GetVersion();
|
|
||||||
|
|
||||||
// Get the Windows version.
|
|
||||||
|
|
||||||
dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
|
|
||||||
dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
|
|
||||||
|
|
||||||
// Get the build number.
|
|
||||||
|
|
||||||
if (dwVersion < 0x80000000)
|
|
||||||
dwBuild = (DWORD)(HIWORD(dwVersion));
|
|
||||||
|
|
||||||
if (dwMajorVersion == 5)
|
|
||||||
return Win_Xp;
|
|
||||||
|
|
||||||
if (dwMinorVersion == 1)
|
|
||||||
return Win_Seven;
|
|
||||||
else
|
|
||||||
return Win_Vista;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- CrashMiniDump -----------------
|
|
||||||
#if defined(USE_MINIDUMP)
|
|
||||||
static LONG WINAPI MyExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
|
|
||||||
{
|
|
||||||
// Open the file
|
|
||||||
HANDLE hFile = CreateFile( L"MiniDump.dmp", GENERIC_READ | GENERIC_WRITE,
|
|
||||||
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
|
||||||
|
|
||||||
if( ( hFile != NULL ) && ( hFile != INVALID_HANDLE_VALUE ) )
|
|
||||||
{
|
|
||||||
// Create the minidump
|
|
||||||
MINIDUMP_EXCEPTION_INFORMATION mdei;
|
|
||||||
|
|
||||||
mdei.ThreadId = GetCurrentThreadId();
|
|
||||||
mdei.ExceptionPointers = ExceptionInfo;
|
|
||||||
mdei.ClientPointers = FALSE;
|
|
||||||
|
|
||||||
MINIDUMP_TYPE mdt = MiniDumpWithFullMemory;
|
|
||||||
|
|
||||||
BOOL rv = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(),
|
|
||||||
hFile, mdt, (ExceptionInfo != 0) ? &mdei : 0, 0, 0 );
|
|
||||||
|
|
||||||
// Close the file
|
|
||||||
CloseHandle( hFile );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
static LPTOP_LEVEL_EXCEPTION_FILTER OldExceptionHandler = nullptr;
|
|
||||||
|
|
||||||
void CrashMiniDump::registerHandler()
|
|
||||||
{
|
|
||||||
OldExceptionHandler = ::SetUnhandledExceptionFilter(&MyExceptionHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrashMiniDump::unregisterHandler()
|
|
||||||
{
|
|
||||||
::SetUnhandledExceptionFilter(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef TARGET_IOS
|
|
||||||
int iosVersion()
|
|
||||||
{
|
|
||||||
return 4; // Stick with this for now
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
int _kbhit()
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
static const int STDIN = 0;
|
|
||||||
static bool initialized = false;
|
|
||||||
|
|
||||||
if (! initialized)
|
|
||||||
{
|
|
||||||
// Use termios to turn off line buffering
|
|
||||||
termios term;
|
|
||||||
tcgetattr(STDIN, &term);
|
|
||||||
term.c_lflag &= ~ICANON;
|
|
||||||
tcsetattr(STDIN, TCSANOW, &term);
|
|
||||||
setbuf(stdin, NULL);
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesWaiting;
|
|
||||||
ioctl(STDIN, FIONREAD, &bytesWaiting);
|
|
||||||
return bytesWaiting;*/
|
|
||||||
static const int STDIN_FILENO = 0;
|
|
||||||
struct termios oldt, newt;
|
|
||||||
int ch;
|
|
||||||
int oldf;
|
|
||||||
|
|
||||||
tcgetattr(STDIN_FILENO, &oldt);
|
|
||||||
newt = oldt;
|
|
||||||
newt.c_lflag &= ~(ICANON | ECHO);
|
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
|
||||||
oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
|
|
||||||
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
|
|
||||||
|
|
||||||
ch = getchar();
|
|
||||||
|
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
|
||||||
fcntl(STDIN_FILENO, F_SETFL, oldf);
|
|
||||||
|
|
||||||
if(ch != EOF)
|
|
||||||
{
|
|
||||||
ungetc(ch, stdin);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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 __OS_VERSION_H
|
|
||||||
#define __OS_VERSION_H
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
Win_Xp = 0,
|
|
||||||
Win_Vista = 1,
|
|
||||||
Win_Seven = 2,
|
|
||||||
Win_Eight = 3,
|
|
||||||
Win_Ten = 4
|
|
||||||
};
|
|
||||||
|
|
||||||
extern int winVersion();
|
|
||||||
|
|
||||||
class CrashMiniDump
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void registerHandler();
|
|
||||||
static void unregisterHandler();
|
|
||||||
};
|
|
||||||
|
|
||||||
extern void writeMiniDump();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef TARGET_IOS
|
|
||||||
int iosVersion();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <termios.h>
|
|
||||||
#if defined(TARGET_LINUX)
|
|
||||||
//# include <stropts.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern int _kbhit();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
# include <conio.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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/. */
|
|
||||||
|
|
||||||
#include "HL_Pointer.h"
|
|
||||||
|
|
||||||
UsageCounter::UsageCounter()
|
|
||||||
{}
|
|
||||||
|
|
||||||
UsageCounter::~UsageCounter()
|
|
||||||
{}
|
|
||||||
|
|
||||||
int UsageCounter::obtain(int usageId)
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
UsageMap::iterator usageIter = mUsage.find(usageId);
|
|
||||||
if (usageIter != mUsage.end())
|
|
||||||
usageIter->second = usageIter->second + 1;
|
|
||||||
else
|
|
||||||
mUsage[usageId] = 1;
|
|
||||||
|
|
||||||
return usageCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
int UsageCounter::release(int usageId)
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
UsageMap::iterator usageIter = mUsage.find(usageId);
|
|
||||||
if (usageIter == mUsage.end())
|
|
||||||
return usageCount();
|
|
||||||
|
|
||||||
usageIter->second = usageIter->second - 1;
|
|
||||||
if (!usageIter->second)
|
|
||||||
mUsage.erase(usageIter);
|
|
||||||
|
|
||||||
return usageCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
int UsageCounter::usageCount()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
UsageMap::const_iterator usageIter;
|
|
||||||
int result = 0;
|
|
||||||
for (usageIter = mUsage.begin(); usageIter != mUsage.end(); usageIter++)
|
|
||||||
result += usageIter->second;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsageCounter::clear()
|
|
||||||
{
|
|
||||||
Lock l(mGuard);
|
|
||||||
mUsage.clear();
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/* Copyright(C) 2007-2014 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 __SMART_POINTER_H
|
|
||||||
#define __SMART_POINTER_H
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <map>
|
|
||||||
#include "HL_Sync.h"
|
|
||||||
|
|
||||||
class UsageCounter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
UsageCounter();
|
|
||||||
~UsageCounter();
|
|
||||||
int obtain(int usageId);
|
|
||||||
int release(int usageId);
|
|
||||||
int usageCount();
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
typedef std::map<int, int> UsageMap;
|
|
||||||
UsageMap mUsage;
|
|
||||||
Mutex mGuard;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,339 +0,0 @@
|
||||||
#include "HL_Process.h"
|
|
||||||
#include <thread>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#ifdef TARGET_WIN
|
|
||||||
# define popen _popen
|
|
||||||
# define pclose _pclose
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include "helper/HL_String.h"
|
|
||||||
|
|
||||||
int OsProcess::execSystem(const std::string& cmd)
|
|
||||||
{
|
|
||||||
return system(cmd.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OsProcess::execCommand(const std::string& cmd)
|
|
||||||
{
|
|
||||||
std::string output;
|
|
||||||
HANDLE hPipeRead, hPipeWrite;
|
|
||||||
|
|
||||||
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES) };
|
|
||||||
saAttr.bInheritHandle = TRUE; //Pipe handles are inherited by child process.
|
|
||||||
saAttr.lpSecurityDescriptor = NULL;
|
|
||||||
|
|
||||||
// Create a pipe to get results from child's stdout.
|
|
||||||
if ( !CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0) )
|
|
||||||
return output;
|
|
||||||
|
|
||||||
STARTUPINFOA si = { sizeof(STARTUPINFOA) };
|
|
||||||
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
|
||||||
si.hStdOutput = hPipeWrite;
|
|
||||||
si.hStdError = hPipeWrite;
|
|
||||||
si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing. Requires STARTF_USESHOWWINDOW in dwFlags.
|
|
||||||
|
|
||||||
PROCESS_INFORMATION pi = { 0 };
|
|
||||||
|
|
||||||
char* cmdline = (char*)_alloca(cmd.size()+1);
|
|
||||||
strcpy(cmdline, strx::replace(cmd, "/", "\\").c_str());
|
|
||||||
|
|
||||||
BOOL fSuccess = CreateProcessA( nullptr, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
|
|
||||||
if (! fSuccess)
|
|
||||||
{
|
|
||||||
CloseHandle( hPipeWrite );
|
|
||||||
CloseHandle( hPipeRead );
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bProcessEnded = false;
|
|
||||||
for (; !bProcessEnded ;)
|
|
||||||
{
|
|
||||||
// Give some timeslice (50ms), so we won't waste 100% cpu.
|
|
||||||
bProcessEnded = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0;
|
|
||||||
|
|
||||||
// Even if process exited - we continue reading, if there is some data available over pipe.
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
char buf[1024];
|
|
||||||
DWORD dwRead = 0;
|
|
||||||
DWORD dwAvail = 0;
|
|
||||||
|
|
||||||
if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!dwAvail) // no data available, return
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
|
|
||||||
// error, the child process might ended
|
|
||||||
break;
|
|
||||||
|
|
||||||
buf[dwRead] = 0;
|
|
||||||
output += buf;
|
|
||||||
}
|
|
||||||
} //for
|
|
||||||
|
|
||||||
CloseHandle( hPipeWrite );
|
|
||||||
CloseHandle( hPipeRead );
|
|
||||||
CloseHandle( pi.hProcess );
|
|
||||||
CloseHandle( pi.hThread );
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdline,
|
|
||||||
std::function<void(const std::string& line)> callback,
|
|
||||||
std::function<void(const std::string& reason)> finished_callback,
|
|
||||||
bool& finish_flag)
|
|
||||||
{
|
|
||||||
// std::cout << cmdline << std::endl;
|
|
||||||
|
|
||||||
std::string output;
|
|
||||||
HANDLE hPipeRead, hPipeWrite;
|
|
||||||
|
|
||||||
SECURITY_ATTRIBUTES saAttr = { sizeof(SECURITY_ATTRIBUTES), nullptr, FALSE };
|
|
||||||
saAttr.bInheritHandle = TRUE; //Pipe handles are inherited by child process.
|
|
||||||
saAttr.lpSecurityDescriptor = nullptr;
|
|
||||||
|
|
||||||
// Create a pipe to get results from child's stdout.
|
|
||||||
if ( !CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0) )
|
|
||||||
return std::shared_ptr<std::thread>();
|
|
||||||
|
|
||||||
STARTUPINFOA si; memset(&si, 0, sizeof si);
|
|
||||||
si.cb = sizeof(STARTUPINFOA);
|
|
||||||
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
|
||||||
si.hStdOutput = hPipeWrite;
|
|
||||||
si.hStdError = hPipeWrite;
|
|
||||||
si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing. Requires STARTF_USESHOWWINDOW in dwFlags.
|
|
||||||
|
|
||||||
PROCESS_INFORMATION pi;
|
|
||||||
memset(&pi, 0, sizeof pi);
|
|
||||||
|
|
||||||
char* cmdbuffer = (char*)_alloca(cmdline.size()+1);
|
|
||||||
strcpy(cmdbuffer, strx::replace(cmdline, "/", "\\").c_str());
|
|
||||||
|
|
||||||
|
|
||||||
BOOL fSuccess = CreateProcessA( nullptr, cmdbuffer, nullptr, nullptr, TRUE,
|
|
||||||
CREATE_NEW_CONSOLE, nullptr, nullptr, &si, &pi);
|
|
||||||
if (! fSuccess)
|
|
||||||
{
|
|
||||||
CloseHandle( hPipeWrite );
|
|
||||||
CloseHandle( hPipeRead );
|
|
||||||
return std::shared_ptr<std::thread>();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<std::thread> r = std::make_shared<std::thread>(
|
|
||||||
[&finish_flag, pi, callback, finished_callback, hPipeRead, hPipeWrite]()
|
|
||||||
{
|
|
||||||
char buf[4096]; memset(buf, 0, sizeof buf);
|
|
||||||
for (; !finish_flag ;)
|
|
||||||
{
|
|
||||||
// Give some timeslice (50ms), so we won't waste 100% cpu.
|
|
||||||
bool timeouted = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0;
|
|
||||||
|
|
||||||
// Even if process exited - we continue reading, if there is some data available over pipe.
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
DWORD dwRead = 0;
|
|
||||||
DWORD dwAvail = 0;
|
|
||||||
|
|
||||||
if (!::PeekNamedPipe(hPipeRead, nullptr, 0, nullptr, &dwAvail, nullptr))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!dwAvail) // no data available, return
|
|
||||||
break;
|
|
||||||
|
|
||||||
int filled = strlen(buf);
|
|
||||||
if (!::ReadFile(hPipeRead, buf + filled, min(sizeof(buf) - 1 - filled, dwAvail), &dwRead, nullptr) || !dwRead)
|
|
||||||
// error, the child process might ended
|
|
||||||
break;
|
|
||||||
|
|
||||||
buf[dwRead] = 0;
|
|
||||||
|
|
||||||
// Split to lines and send to callback
|
|
||||||
const char* cr;
|
|
||||||
while ((cr = strchr(buf, '\n')) != nullptr)
|
|
||||||
{
|
|
||||||
std::string line(buf, cr - buf -1);
|
|
||||||
if (callback)
|
|
||||||
callback(strx::trim(line));
|
|
||||||
memmove(buf, cr + 1, strlen(cr+1) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} //for
|
|
||||||
|
|
||||||
if (buf[0])
|
|
||||||
callback(strx::trim(std::string(buf)));
|
|
||||||
|
|
||||||
char ctrlc = 3;
|
|
||||||
//if (finish_flag)
|
|
||||||
// ::WriteFile(hPipeWrite, &ctrlc, 1, nullptr, nullptr);
|
|
||||||
|
|
||||||
// GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId);
|
|
||||||
|
|
||||||
CloseHandle( hPipeWrite );
|
|
||||||
CloseHandle( hPipeRead );
|
|
||||||
if (finish_flag)
|
|
||||||
{
|
|
||||||
//GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
|
||||||
// Close underlying process
|
|
||||||
//TerminateProcess(pi.hProcess, 3);
|
|
||||||
}
|
|
||||||
CloseHandle( pi.hProcess );
|
|
||||||
CloseHandle( pi.hThread );
|
|
||||||
if (finished_callback)
|
|
||||||
finished_callback(std::string());
|
|
||||||
});
|
|
||||||
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
std::string OsProcess::execCommand(const std::string& cmd)
|
|
||||||
{
|
|
||||||
std::string cp = cmd;
|
|
||||||
std::shared_ptr<FILE> pipe(popen(cp.c_str(), "r"), pclose);
|
|
||||||
if (!pipe)
|
|
||||||
throw std::runtime_error("Failed to run.");
|
|
||||||
|
|
||||||
char buffer[1024];
|
|
||||||
std::string result = "";
|
|
||||||
while (!feof(pipe.get()))
|
|
||||||
{
|
|
||||||
if (fgets(buffer, 1024, pipe.get()) != nullptr)
|
|
||||||
result += buffer;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
int OsProcess::execSystem(const std::string& cmd)
|
|
||||||
{
|
|
||||||
return system(cmd.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <poll.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/uio.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
|
||||||
#include "helper/HL_String.h"
|
|
||||||
#include "helper/HL_Sync.h"
|
|
||||||
|
|
||||||
std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdline,
|
|
||||||
std::function<void(const std::string& line)> line_callback,
|
|
||||||
std::function<void(const std::string& reason)> finished_callback,
|
|
||||||
bool& finish_flag)
|
|
||||||
{
|
|
||||||
std::shared_ptr<std::thread> t = std::make_shared<std::thread>([cmdline, line_callback, finished_callback, &finish_flag]()
|
|
||||||
{
|
|
||||||
ThreadHelper::setName("OsProcess::asyncExecCommand");
|
|
||||||
std::string cp = cmdline;
|
|
||||||
FILE* pipe = popen(cp.c_str(), "r");
|
|
||||||
if (!pipe)
|
|
||||||
{
|
|
||||||
if (finished_callback)
|
|
||||||
finished_callback("Failed to open pipe");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buffer[1024];
|
|
||||||
std::string lines;
|
|
||||||
std::string result = "";
|
|
||||||
int fno = fileno(pipe);
|
|
||||||
|
|
||||||
// Make it non blocking
|
|
||||||
fcntl(fno, F_SETFL, O_NONBLOCK);
|
|
||||||
|
|
||||||
while (!feof(pipe) && !finish_flag)
|
|
||||||
{
|
|
||||||
// Wait for more data
|
|
||||||
struct pollfd pfd{ .fd = fno, .events = POLLIN };
|
|
||||||
|
|
||||||
while (poll(&pfd, 1, 0) == 0 && !finish_flag)
|
|
||||||
;
|
|
||||||
|
|
||||||
// Read data
|
|
||||||
if (finish_flag)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int r;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
r = static_cast<int>(read(fno, buffer, sizeof(buffer) - 1));
|
|
||||||
if (r > 0)
|
|
||||||
{
|
|
||||||
buffer[r] = 0;
|
|
||||||
lines += std::string(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (r == sizeof(buffer) - 1);
|
|
||||||
|
|
||||||
if (lines.find('\n') != std::string::npos && line_callback)
|
|
||||||
{
|
|
||||||
std::string::size_type p = 0;
|
|
||||||
while (p < lines.size())
|
|
||||||
{
|
|
||||||
std::string::size_type d = lines.find('\n', p);
|
|
||||||
if (d != std::string::npos)
|
|
||||||
{
|
|
||||||
if (line_callback)
|
|
||||||
line_callback(strx::trim(lines.substr(p, d-p)));
|
|
||||||
p = d + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.erase(0, p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finish_flag)
|
|
||||||
{
|
|
||||||
// Send SIGINT to process
|
|
||||||
}
|
|
||||||
if (pipe)
|
|
||||||
pclose(pipe);
|
|
||||||
|
|
||||||
finish_flag = true;
|
|
||||||
if (finished_callback)
|
|
||||||
finished_callback(std::string());
|
|
||||||
});
|
|
||||||
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
|
||||||
|
|
||||||
pid_t OsProcess::findPid(const std::string& cmdline)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << "pgrep -f " << "\"" << cmdline << "\"";
|
|
||||||
std::string output = execCommand(oss.str());
|
|
||||||
return std::atoi(output.c_str());
|
|
||||||
}
|
|
||||||
catch(...)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OsProcess::killByPid(pid_t pid)
|
|
||||||
{
|
|
||||||
if (pid <= 0)
|
|
||||||
return;
|
|
||||||
execSystem("kill -9 " + std::to_string(pid) + " &");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
#ifndef __HL_PROCESS_H
|
|
||||||
#define __HL_PROCESS_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
class OsProcess
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static std::string execCommand(const std::string& cmdline);
|
|
||||||
static int execSystem(const std::string& cmdline);
|
|
||||||
static std::shared_ptr<std::thread> asyncExecCommand(const std::string& cmdline,
|
|
||||||
std::function<void(const std::string& line)> line_callback,
|
|
||||||
std::function<void(const std::string& reason)> finished_callback,
|
|
||||||
bool& finish_flag);
|
|
||||||
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
|
|
||||||
static pid_t findPid(const std::string& cmdline);
|
|
||||||
static void killByPid(pid_t pid);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
/* Copyright(C) 2007-2017 VoIPobjects (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/. */
|
|
||||||
|
|
||||||
#if defined(TARGET_WIN)
|
|
||||||
# include <WinSock2.h>
|
|
||||||
# include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
|
|
||||||
# include <arpa/inet.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "HL_Rtp.h"
|
|
||||||
#include "HL_Exception.h"
|
|
||||||
#include "HL_String.h"
|
|
||||||
|
|
||||||
#if defined(USE_RTP_DUMP)
|
|
||||||
# include "jrtplib/src/rtprawpacket.h"
|
|
||||||
# include "jrtplib/src/rtpipv4address.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(TARGET_WIN)
|
|
||||||
# include <alloca.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
struct RtpHeader
|
|
||||||
{
|
|
||||||
unsigned char cc:4; /* CSRC count */
|
|
||||||
unsigned char x:1; /* header extension flag */
|
|
||||||
unsigned char p:1; /* padding flag */
|
|
||||||
unsigned char version:2; /* protocol version */
|
|
||||||
unsigned char pt:7; /* payload type */
|
|
||||||
unsigned char m:1; /* marker bit */
|
|
||||||
unsigned short seq; /* sequence number */
|
|
||||||
unsigned int ts; /* timestamp */
|
|
||||||
unsigned int ssrc; /* synchronization source */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RtcpHeader
|
|
||||||
{
|
|
||||||
unsigned char rc:5; /* reception report count */
|
|
||||||
unsigned char p:1; /* padding flag */
|
|
||||||
unsigned char version:2; /* protocol version */
|
|
||||||
unsigned char pt:8; /* payload type */
|
|
||||||
uint16_t len; /* length */
|
|
||||||
uint32_t ssrc; /* synchronization source */
|
|
||||||
};
|
|
||||||
|
|
||||||
bool RtpHelper::isRtp(const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
if (length < 12)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer);
|
|
||||||
if (h->version != 0b10)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
unsigned char pt = h->pt;
|
|
||||||
bool rtp = ( (pt & 0x7F) >= 96 && (pt & 0x7F) <= 127) || ((pt & 0x7F) < 35);
|
|
||||||
return rtp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
if (length < 12)
|
|
||||||
return false;
|
|
||||||
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer);
|
|
||||||
return h->version == 0b10;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RtpHelper::isRtcp(const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
return (isRtpOrRtcp(buffer, length) && !isRtp(buffer, length));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
if (isRtp(buffer, length))
|
|
||||||
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc);
|
|
||||||
else
|
|
||||||
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
|
|
||||||
{
|
|
||||||
if (isRtp(buffer, length))
|
|
||||||
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
|
|
||||||
else
|
|
||||||
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
|
|
||||||
}
|
|
||||||
|
|
||||||
int RtpHelper::findPtype(const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
if (isRtp(buffer, length))
|
|
||||||
return reinterpret_cast<const RtpHeader*>(buffer)->pt;
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int RtpHelper::findPacketNo(const void *buffer, size_t length)
|
|
||||||
{
|
|
||||||
if (isRtp(buffer, length))
|
|
||||||
return ntohs(reinterpret_cast<const RtpHeader*>(buffer)->seq);
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int RtpHelper::findPayloadLength(const void* buffer, size_t length)
|
|
||||||
{
|
|
||||||
if (isRtp(buffer, length))
|
|
||||||
{
|
|
||||||
return length - 12;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(USE_RTPDUMP)
|
|
||||||
RtpDump::RtpDump(const char *filename)
|
|
||||||
:mFilename(filename)
|
|
||||||
{}
|
|
||||||
|
|
||||||
RtpDump::~RtpDump()
|
|
||||||
{
|
|
||||||
flush();
|
|
||||||
for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter)
|
|
||||||
{
|
|
||||||
//free(packetIter->mData);
|
|
||||||
delete packetIter->mPacket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RtpDump::load()
|
|
||||||
{
|
|
||||||
FILE* f = fopen(mFilename.c_str(), "rb");
|
|
||||||
if (!f)
|
|
||||||
throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
while (!feof(f))
|
|
||||||
{
|
|
||||||
RtpData data;
|
|
||||||
fread(&data.mLength, sizeof data.mLength, 1, f);
|
|
||||||
data.mData = new char[data.mLength];
|
|
||||||
fread(data.mData, 1, data.mLength, f);
|
|
||||||
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
|
|
||||||
jrtplib::RTPTime t(0);
|
|
||||||
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)data.mData, data.mLength, &addr, t, true);
|
|
||||||
data.mPacket = new jrtplib::RTPPacket(*raw);
|
|
||||||
mPacketList.push_back(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t RtpDump::count() const
|
|
||||||
{
|
|
||||||
return mPacketList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
|
|
||||||
{
|
|
||||||
return *mPacketList[index].mPacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RtpDump::add(const void* buffer, size_t len)
|
|
||||||
{
|
|
||||||
RtpData data;
|
|
||||||
data.mData = malloc(len);
|
|
||||||
memcpy(data.mData, buffer, len);
|
|
||||||
data.mLength = len;
|
|
||||||
|
|
||||||
jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
|
|
||||||
jrtplib::RTPTime t(0);
|
|
||||||
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
|
|
||||||
data.mPacket = new jrtplib::RTPPacket(*raw);
|
|
||||||
//delete raw;
|
|
||||||
mPacketList.push_back(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RtpDump::flush()
|
|
||||||
{
|
|
||||||
FILE* f = fopen(mFilename.c_str(), "wb");
|
|
||||||
if (!f)
|
|
||||||
throw Exception(ERR_WAVFILE_FAILED);
|
|
||||||
|
|
||||||
PacketList::iterator packetIter = mPacketList.begin();
|
|
||||||
for (;packetIter != mPacketList.end(); ++packetIter)
|
|
||||||
{
|
|
||||||
RtpData& data = *packetIter;
|
|
||||||
// Disabled for debugging only
|
|
||||||
//fwrite(&data.mLength, sizeof data.mLength, 1, f);
|
|
||||||
fwrite(data.mData, data.mLength, 1, f);
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue