Compare commits

..

No commits in common. "main" and "master" have entirely different histories.
main ... master

4065 changed files with 1621382 additions and 21 deletions

12
.gitmodules vendored Normal file
View File

@ -0,0 +1,12 @@
[submodule "src/libs/resiprocate"]
path = src/libs/resiprocate
url = git@git.sevana.biz:public/resiprocate.git
branch = sevana
[submodule "src/libs/libsrtp"]
path = src/libs/libsrtp
url = git@git.sevana.biz:public/libsrtp.git
branch = master
[submodule "src/libs/libraries"]
path = src/libs/libraries
url = git@git.sevana.biz:public/libraries.git

21
LICENSE
View File

@ -1,21 +0,0 @@
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 Normal file
View File

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

141
README.txt Normal file
View File

@ -0,0 +1,141 @@
# RTPhone Platform
RTPhone is a comprehensive real-time communication (RTC) platform that provides a complete software stack for building VoIP/SIP-based communication applications. Developed by VoIP Objects (Sevana), RTPhone delivers production-ready voice communication capabilities with extensive codec support and cross-platform compatibility.
## Overview
RTPhone serves as a static library (`librtphone.a`) that can be integrated into larger telephony and communication systems. It provides a JSON-based command interface for easy integration and control, making it suitable for building softphones, PBX systems, WebRTC gateways, and carrier-grade voice solutions.
## Key Features
### Audio Codec Support
RTPhone supports an extensive range of audio codecs:
**Standard Codecs:**
- G.711 (A-law/¼-law)
- G.722 (16kHz wideband)
- G.729
- GSM (Full Rate, Half Rate, Enhanced Full Rate)
- iLBC (20ms/30ms)
- ISAC (16kHz/32kHz)
**Advanced Codecs:**
- AMR-NB/AMR-WB (Adaptive Multi-Rate Narrowband/Wideband) - please be aware - there is no patents for AMR codecs usage included ! You should acquire them on your own.
- EVS (Enhanced Voice Services) - 3GPP's latest codec. Again - please be aware - there is no patents for EVS codec usage included ! You should acquire them on your own.
- Opus - Modern low-latency codec
- Speex (with acoustic echo cancellation)
**Codec Features:**
- Bandwidth-efficient and octet-aligned modes
- IuUP (Iu User Plane) protocol support for 3G networks
- Dynamic codec switching
- Packet loss concealment (PLC)
- Comfort noise generation (CNG)
### Network & Protocol Support
**SIP Features:**
- Full SIP 2.0 implementation via reSIProcate
- Multiple transport protocols (UDP, TCP, TLS)
- Registration, authentication, and session management
- SIP MESSAGE, presence, and REFER support
**Media Transport:**
- RTP/RTCP for media streaming
- SRTP for secure media
- ICE for NAT traversal with STUN/TURN support
- WebRTC integration components
- IPv4 and IPv6 support
### Cross-Platform Audio Support
- DirectSound/WMME (Windows)
- Core Audio (macOS/iOS)
- ALSA/PulseAudio (Linux)
- Oboe (Android) for low-latency audio
- PortAudio fallback support
### Audio Quality Features
- 48kHz sample rate support
- Acoustic Echo Cancellation (AEC)
- Audio resampling and format conversion
- Multi-channel audio mixing
- Perceptual Voice Quality Assessment (PVQA)
## Architecture
The platform is organized into several core modules:
- **Engine/Agent**: JSON-based command interface
- **Engine/Endpoint**: SIP user agent implementation
- **Engine/Media**: Audio codec management and processing
- **Engine/Audio**: Cross-platform audio I/O handling
- **Engine/Helper**: Utility functions (networking, logging, threading)
## Supported Platforms
- Linux (x64, ARM/Raspberry Pi)
- Windows (32/64-bit)
- macOS
- Android (with Oboe integration)
- iOS
## Building
RTPhone uses a CMake-based build system with cross-compilation support:
### Linux
```bash
python3 build_linux.py
```
### Android
```bash
python3 build_android.py
# or
./build_android.sh
```
### Dependencies
- CMake 3.10+
- OpenSSL 1.1+
- Boost libraries
- Platform-specific audio libraries
## Recent Updates
Recent development has focused on:
- AMR codec parsing and decoding improvements
- Octet-aligned mode fixes for AMR-WB
- RTP SSRC handling enhancements
- Build system optimizations
- Code modernization to C++20
## Use Cases
RTPhone is good for building:
- VoIP softphones and mobile applications
- PBX and telephony server systems
- RTP proxies
- Carrier-grade voice communication platforms
- 3GPP/IMS-compliant systems
## Security
RTPhone includes comprehensive security features:
- OpenSSL 1.1 integration for encryption
- TLS transport layer security
- SRTP media encryption
- Certificate management support
## Integration
The platform provides a developer-friendly interface with:
- Event-driven architecture
- Comprehensive logging system
- Modern C++20 codebase
For detailed integration instructions and API documentation, please refer to the source code and header files in the `/src/engine/` directory.
## License
Our source code is licensed under the MPL license. Naturally, any third-party components we use are subject to their respective licenses.

44
build_android.py Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/python3
from pathlib import Path
import os
import multiprocessing
import shutil
# Temporary build directory
DIR_BUILD = 'build_android'
# Android NDK home directory
NDK_HOME = os.environ['ANDROID_NDK_HOME']
# CMake toolchain file
TOOLCHAIN_FILE = f'{NDK_HOME}/build/cmake/android.toolchain.cmake'
# This directory
DIR_THIS = Path(__file__).parent.resolve()
# Path to app
DIR_SOURCE = (DIR_THIS / '../src').resolve()
def make_build() -> Path:
if Path(DIR_BUILD).exists():
shutil.rmtree(DIR_BUILD)
os.mkdir(DIR_BUILD)
os.chdir(DIR_BUILD)
cmd = f'cmake -DCMAKE_TOOLCHAIN_FILE={TOOLCHAIN_FILE} '
cmd += f'-DANDROID_NDK=$NDK_HOME '
cmd += f'-DANDROID_PLATFORM=24 '
cmd += f'-DCMAKE_BUILD=Release '
cmd += f'-DANDROID_ABI="arm64-v8a" '
cmd += '../src'
retcode = os.system(cmd)
if retcode != 0:
raise RuntimeError('Problem when configuring the project')
cmd = f'cmake --build . -j {multiprocessing.cpu_count()}'
retcode = os.system(cmd)
if retcode != 0:
raise RuntimeError('Problem when building the project')
if __name__ == '__main__':
make_build()

15
build_android.sh Executable file
View File

@ -0,0 +1,15 @@
#!/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

37
build_linux.py Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
from pathlib import Path
import os
import multiprocessing
import shutil
# Temporary build directory
DIR_BUILD = 'build_linux'
# This directory
DIR_THIS = Path(__file__).parent.resolve()
# Path to app
DIR_SOURCE = (DIR_THIS / '../src').resolve()
def make_build() -> Path:
if Path(DIR_BUILD).exists():
shutil.rmtree(DIR_BUILD)
os.mkdir(DIR_BUILD)
os.chdir(DIR_BUILD)
cmd = f'cmake ../src -G Ninja'
retcode = os.system(cmd)
if retcode != 0:
raise RuntimeError('Problem when configuring the project')
cmd = f'cmake --build . -j {multiprocessing.cpu_count()}'
retcode = os.system(cmd)
if retcode != 0:
raise RuntimeError('Problem when building the project')
os.chdir('..')
return Path(DIR_BUILD) / 'librtphone.a'
if __name__ == '__main__':
p = make_build()
print (f'Built: {p}')

23
run_ci.sh Executable file
View File

@ -0,0 +1,23 @@
#!/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"

380
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,380 @@
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>")

View File

@ -0,0 +1,207 @@
/* 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]);
}

View File

@ -0,0 +1,89 @@
/* 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

View File

@ -0,0 +1,856 @@
#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

View File

@ -0,0 +1,128 @@
#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

View File

@ -0,0 +1,27 @@
#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()
{
}

View File

@ -0,0 +1,19 @@
#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

View File

@ -0,0 +1,599 @@
#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

View File

@ -0,0 +1,146 @@
/* 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

View File

@ -0,0 +1,245 @@
#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

View File

@ -0,0 +1,109 @@
/* 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

View File

@ -0,0 +1,197 @@
/* 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

View File

@ -0,0 +1,180 @@
/* 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;
}

View File

@ -0,0 +1,47 @@
/* 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

View File

@ -0,0 +1,291 @@
/* 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);
}

View File

@ -0,0 +1,85 @@
/* 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

View File

@ -0,0 +1,187 @@
/* 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

View File

@ -0,0 +1,150 @@
/* 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();
}
}

View File

@ -0,0 +1,78 @@
/* 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

View File

@ -0,0 +1,154 @@
/* 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;
}

View File

@ -0,0 +1,156 @@
/* 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

View File

@ -0,0 +1,337 @@
/* 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();
}

View File

@ -0,0 +1,72 @@
/* 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

View File

@ -0,0 +1,188 @@
#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;
}

View File

@ -0,0 +1,90 @@
#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

View File

@ -0,0 +1,171 @@
/* 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();
}

View File

@ -0,0 +1,71 @@
/* 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

View File

@ -0,0 +1,238 @@
/* 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;
}

View File

@ -0,0 +1,69 @@
/* 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

View File

@ -0,0 +1,286 @@
/* 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

View File

@ -0,0 +1,104 @@
/* 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

View File

@ -0,0 +1,431 @@
/* 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;
}

View File

@ -0,0 +1,96 @@
/* 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

View File

@ -0,0 +1,555 @@
/* 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

View File

@ -0,0 +1,148 @@
/* 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

View File

@ -0,0 +1,2 @@
#include "Audio_iOS.h"

View File

@ -0,0 +1,38 @@
#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

View File

@ -0,0 +1,46 @@
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
../)

View File

@ -0,0 +1,746 @@
/* 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;
}

View File

@ -0,0 +1,143 @@
/* 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

View File

@ -0,0 +1,367 @@
/* 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);
}

View File

@ -0,0 +1,120 @@
/* 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

View File

@ -0,0 +1,74 @@
/* 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;
}

View File

@ -0,0 +1,91 @@
/* 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

View File

@ -0,0 +1,493 @@
/* 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

View File

@ -0,0 +1,182 @@
#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();
}

View File

@ -0,0 +1,69 @@
/* 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

View File

@ -0,0 +1,106 @@
/* 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;
}

View File

@ -0,0 +1,73 @@
/* 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

View File

@ -0,0 +1,304 @@
/*
* 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);
}

View File

@ -0,0 +1,147 @@
/*
* 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

View File

@ -0,0 +1,411 @@
/* 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

117
src/engine/engine_config.h Normal file
View File

@ -0,0 +1,117 @@
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __TOOLKIT_CONFIG_H
#define __TOOLKIT_CONFIG_H
#define USE_SPEEX_AEC
// TODO: test implementation with webrtc aec; be careful - it needs fixes!
//#define USE_WEBRTC_AEC
#define USER
#define AUDIO_SAMPLE_WIDTH 16
#define AUDIO_CHANNELS 1
// Samplerate must be 8 / 16 / 24 / 32 / 48 KHz
#define AUDIO_SAMPLERATE 48000
#define AUDIO_MIC_BUFFER_COUNT 16
#define AUDIO_MIC_BUFFER_LENGTH 10
#define AUDIO_MIC_BUFFER_SIZE (AUDIO_MIC_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
#define AUDIO_SPK_BUFFER_COUNT 16
#define AUDIO_SPK_BUFFER_LENGTH 10
#define AUDIO_SPK_BUFFER_SIZE (AUDIO_SPK_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
#define AUDIO_MIX_CHANNEL_COUNT 16
#define AUDIO_DEVICEPAIR_INPUTBUFFER 16384
// Avoid too high resampler quality - it can take many CPU and cause gaps in playing
#define AUDIO_RESAMPLER_QUALITY 1
#define AEC_FRAME_TIME 10
#define AEC_TAIL_TIME 160
// Defined these two lines to get dumping of audio input/output
//#define AUDIO_DUMPINPUT
//#define AUDIO_DUMPOUTPUT
#define UA_REGISTRATION_TIME 3600
#define UA_MEDIA_PORT_START 20000
#define UA_MEDIA_PORT_FINISH 30000
#define UA_MAX_UDP_PACKET_SIZE 576
#define UA_PUBLICATION_ID "314"
#define MT_SAMPLERATE AUDIO_SAMPLERATE
#define MT_MAXAUDIOFRAME 1440
#define MT_MAXRTPPACKET 1500
#define MT_DTMF_END_PACKETS 3
#define RTP_BUFFER_HIGH 0
#define RTP_BUFFER_LOW 0
#define RTP_BUFFER_PREBUFFER 0
// #define RTP_BUFFER_HIGH 160
// #define RTP_BUFFER_LOW 10
// #define RTP_BUFFER_PREBUFFER 160
#define RTP_DECODED_CAPACITY 2048
#define DEFAULT_SUBSCRIPTION_TIME 1200
#define DEFAULT_SUBSCRIPTION_REFRESHTIME 500
#define PRESENCE_IN_REG_HEADER "PresenceInReg"
// Maximum UDP packet length
#define MAX_UDPPACKET_SIZE 65535
#define MAX_VALID_UDPPACKET_SIZE 2048
// AMR codec defines - it requires USE_AMR_CODEC defined
// #define USE_AMR_CODEC
#define MT_AMRNB_PAYLOADTYPE 112
#define MT_AMRNB_CODECNAME "amr"
#define MT_AMRNB_OCTET_PAYLOADTYPE 113
#define MT_AMRWB_PAYLOADTYPE 96
#define MT_AMRWB_CODECNAME "amr-wb"
#define MT_AMRWB_OCTET_PAYLOADTYPE 97
#define MT_GSMEFR_PAYLOADTYPE 126
#define MT_GSMEFR_CODECNAME "GERAN-EFR"
#define MT_EVS_PAYLOADTYPE 127
#define MT_EVS_CODECNAME "EVS"
// OPUS codec defines
// #define USE_OPUS_CODEC
#define MT_OPUS_CODEC_PT 106
// ILBC codec defines
#define MT_ILBC20_PAYLOADTYPE -1
#define MT_ILBC30_PAYLOADTYPE -1
// ISAC codec defines
#define MT_ISAC16K_PAYLOADTYPE -1
#define MT_ISAC32K_PAYLOADTYPE -1
// GSM HR payload type
#define MT_GSMHR_PAYLOADTYPE -1
// Mirror buffer capacity
#define MT_MIRROR_CAPACITY 32768
// Mirror buffer readiness threshold - 50 milliseconds
#define MT_MIRROR_PREBUFFER (MT_SAMPLERATE / 10)
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
# define TEXT(X) X
#endif
// In milliseconds
#define MT_SEVANA_FRAME_TIME 680
#endif

View File

@ -0,0 +1,20 @@
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()

View File

@ -0,0 +1,16 @@
/* 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()
{
}

View File

@ -0,0 +1,19 @@
/* 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

View File

@ -0,0 +1,256 @@
#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

View File

@ -0,0 +1,18 @@
/* 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

View File

View File

@ -0,0 +1,626 @@
#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

View File

@ -0,0 +1,193 @@
#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

View File

@ -0,0 +1,64 @@
#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

View File

@ -0,0 +1,29 @@
#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;
}

View File

@ -0,0 +1,23 @@
#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

View File

View File

View File

@ -0,0 +1,85 @@
/* 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

View File

@ -0,0 +1,141 @@
#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);
}

View File

@ -0,0 +1,29 @@
#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

View File

@ -0,0 +1,222 @@
#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;
}

View File

@ -0,0 +1,85 @@
#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

View File

@ -0,0 +1,12 @@
/* 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

View File

@ -0,0 +1,39 @@
#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

View File

@ -0,0 +1,5 @@
/* 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/. */

View File

@ -0,0 +1,23 @@
/* 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

View File

@ -0,0 +1,238 @@
#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};
}

View File

@ -0,0 +1,137 @@
#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

View File

@ -0,0 +1,187 @@
/* 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;
}

View File

@ -0,0 +1,68 @@
/* 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

View File

@ -0,0 +1,152 @@
/* 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

View File

@ -0,0 +1,54 @@
/* 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

View File

@ -0,0 +1,55 @@
/* 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();
}

View File

@ -0,0 +1,29 @@
/* 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

View File

@ -0,0 +1,339 @@
#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

View File

@ -0,0 +1,25 @@
#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

View File

@ -0,0 +1,201 @@
/* 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