Compare commits

..

4 Commits

12079 changed files with 402809 additions and 2316546 deletions
-89
View File
@@ -1,89 +0,0 @@
# Build directories
build/
build_*/
out/
bin/
lib/
Release/
Debug/
x64/
x86/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
*.cmake
!CMakeLists.txt
compile_commands.json
CTestTestfile.cmake
_deps/
# QtCreator
*.user
*.user.*
*.qmlproject.user
*.creator.user
.qmake.stash
# Visual Studio
*.suo
*.user
*.userosscache
*.sln.docstates
.vs/
*.vcxproj.user
*.ncb
*.sdf
*.opensdf
*.VC.db
*.VC.VC.opendb
ipch/
# Compiled object files
*.o
*.obj
*.ko
*.elf
# Precompiled headers
*.gch
*.pch
# Compiled libraries
# *.lib
# *.a
*.la
*.lo
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
# IDE caches
.cache/
.idea/
.vscode/
*.swp
*.swo
*~
# Downloaded archives
*.tar.gz
*.tar.bz2
*.tar.xz
*.zip
# OS specific
.DS_Store
Thumbs.db
# Logs and temp files
*.log
*.tmp
*.temp
+8
View File
@@ -0,0 +1,8 @@
build_exe:
script:
- mkdir -p build
- cd build
- git clone https://gitlab.com/dmytro.bogovych/libraries.git
- cmake -D LIB_PLATFORM=libraries ../src
- cmake --build .
-366
View File
@@ -1,366 +0,0 @@
# RTPhone Platform - Agent Guide
## Project Overview
RTPhone is a comprehensive real-time communication (RTC) platform developed by VoIP Objects (Sevana) that provides a complete software stack for building VoIP/SIP-based communication applications. It delivers production-ready voice communication capabilities with extensive codec support and cross-platform compatibility.
The project produces 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.
## Technology Stack
- **Language**: C++20
- **Build System**: CMake 3.20+
- **License**: Mozilla Public License Version 2.0 (see LICENSE_MPL.txt)
- **Primary Platforms**: Linux (x64, ARM/Raspberry Pi), Windows (32/64-bit), macOS, Android, iOS
## Project Structure
```
/home/anand/works/sevana/platform/rtphone/
├── src/ # Main source code
│ ├── CMakeLists.txt # Main CMake configuration
│ ├── engine/ # Core engine modules
│ │ ├── agent/ # JSON-based command interface
│ │ ├── audio/ # Cross-platform audio I/O handling
│ │ ├── endpoint/ # SIP user agent implementation
│ │ ├── helper/ # Utility functions (networking, logging, threading)
│ │ ├── media/ # Audio codec management and processing
│ │ └── engine_config.h # Compile-time configuration
│ └── libs/ # Third-party libraries
│ ├── resiprocate/ # SIP stack (git submodule)
│ ├── libsrtp/ # SRTP library (git submodule)
│ ├── libraries/ # Prebuilt platform libraries (git submodule)
│ ├── ice/ # ICE (Interactive Connectivity Establishment)
│ ├── jrtplib/ # RTP library
│ ├── opus/ # Opus codec
│ ├── webrtc/ # WebRTC components
│ ├── libg729/ # G.729 codec
│ ├── libgsm/ # GSM codec
│ ├── gsmhr/ # GSM HR codec
│ ├── g722/ # G.722 codec
│ ├── speexdsp/ # Speex DSP
│ ├── libevs/ # EVS codec (optional)
│ ├── opencore-amr/ # AMR codec (optional)
│ ├── oboe/ # Android low-latency audio
│ └── fmt/ # Format library
├── build_linux.py # Linux build script
├── build_android.py # Android build script
├── build_android.sh # Android build script (shell)
├── run_ci.sh # CI build script
└── README.txt # Human-readable README
```
## Build Instructions
### Prerequisites
- CMake 3.20+
- C++20 compatible compiler (GCC, Clang, MSVC)
- Python 3 (for build scripts)
- Ninja (recommended, used by build scripts)
- OpenSSL 1.1+ development libraries
- Platform-specific audio libraries
### Linux Build
```bash
python3 build_linux.py
```
This creates a `build_linux/` directory and outputs `librtphone.a`.
### Android Build
```bash
# Ensure ANDROID_NDK_HOME environment variable is set
export ANDROID_NDK_HOME=/path/to/android-ndk
python3 build_android.py
```
Or use the shell script:
```bash
./build_android.sh
```
This creates a `build_android/` directory with ARM64 libraries by default.
### Manual CMake Build
```bash
mkdir build && cd build
cmake ../src -G Ninja
cmake --build . -j$(nproc)
```
### Build Options
The following CMake options are available (defined in `src/CMakeLists.txt`):
- `USE_AMR_CODEC` (ON/OFF): Include AMR-NB/AMR-WB codec support. Default: ON
- `USE_EVS_CODEC` (ON/OFF): Include EVS codec support. Default: ON
- `USE_MUSL` (ON/OFF): Build with MUSL library. Default: OFF
## Module Architecture
### 1. Agent Module (`src/engine/agent/`)
Provides a JSON-based command interface for controlling the engine.
**Key Classes:**
- `AgentImpl`: Main agent implementation, processes JSON commands
- `Agent_AudioManager`: Manages audio devices and streams
**Interface Pattern:**
Commands are sent as JSON strings and responses are returned as JSON.
### 2. Endpoint Module (`src/engine/endpoint/`)
SIP user agent implementation based on reSIProcate.
**Key Classes:**
- `UserAgent` (in EP_Engine.h): Main SIP stack wrapper, handles registration, sessions, presence
- `Account` (EP_Account): SIP account management
- `Session` (EP_Session): Call/session management
- `EP_AudioProvider`: Audio data provider for sessions
- `EP_DataProvider`: Generic data provider interface
**Key Concepts:**
- Implements resiprocate handlers: `ClientRegistrationHandler`, `InviteSessionHandler`, etc.
- Supports multiple transport types: UDP, TCP, TLS
- ICE integration for NAT traversal
### 3. Media Module (`src/engine/media/`)
Audio codec management, RTP/RTCP handling, and media processing.
**Key Classes:**
- `MT::AudioCodec`: Codec management
- `MT::AudioStream`: Audio streaming
- `MT::AudioReceiver`: RTP packet receiving
- `MT::CodecList`: Codec negotiation
- `MT::SrtpHelper`: SRTP encryption
- `MT::Dtmf`: DTMF tone handling
- `MT::AmrCodec`: AMR codec wrapper
- `MT::EvsCodec`: EVS codec wrapper
### 4. Audio Module (`src/engine/audio/`)
Cross-platform audio I/O abstraction.
**Key Classes:**
- `Audio::Interface`: Audio interface abstraction
- `Audio::DevicePair`: Input/output device pair
- `Audio::Resampler`: Audio resampling
- `Audio::Mixer`: Multi-channel audio mixing
- `Audio::CoreAudio`: macOS/iOS implementation
- `Audio::DirectSound`: Windows DirectSound implementation
- `Audio::AndroidOboe`: Android Oboe implementation
- `Audio::Null`: Null/no-op implementation for testing
### 5. Helper Module (`src/engine/helper/`)
Utility classes and platform abstractions.
**Key Classes:**
- `HL::Types`: Type definitions and BiMap template
- `HL::Sync`: Threading primitives (Mutex, Event, etc.)
- `HL::ByteBuffer`: Binary data buffer
- `HL::VariantMap`: Key-value configuration storage
- `HL::Rtp`: RTP packet utilities
- `HL::IuUP`: Iu User Plane protocol (3G)
- `HL::NetworkSocket`: Network socket abstraction
- `HL::ThreadPool`: Thread pool implementation
## Code Style Guidelines
### Naming Conventions
1. **Classes**: PascalCase with module prefix
- `AgentImpl`, `UserAgent`, `MT::AudioCodec`, `HL::Sync`
2. **Files**: Prefix indicates module
- `Agent_*.cpp/h` - Agent module
- `EP_*.cpp/h` - Endpoint module
- `MT_*.cpp/h` - Media module
- `Audio_*.cpp/h` - Audio module
- `HL_*.cpp/h` - Helper module
3. **Member Variables**: Hungarian notation with `m` prefix
- `mAgentMutex`, `mSessionMap`, `mShutdown`
4. **Type Aliases**: `P` prefix for smart pointers
- `PAccount`, `PSession`, `PVariantMap`
### File Header Template
All source files must include the MPL license header:
```cpp
/* Copyright(C) 2007-YYYY 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 Order
1. Corresponding header file (for .cpp files)
2. Project headers (using `"..."`)
3. Third-party library headers
4. Standard library headers
5. System headers
### Platform Abstraction
Use preprocessor defines for platform-specific code:
- `TARGET_WIN` - Windows
- `TARGET_LINUX` - Linux
- `TARGET_OSX` - macOS
- `TARGET_ANDROID` - Android
- `TARGET_MUSL` - MUSL libc
## Configuration
### Compile-Time Configuration (`src/engine/engine_config.h`)
Key configuration constants:
```cpp
// Audio settings
#define AUDIO_SAMPLE_WIDTH 16
#define AUDIO_CHANNELS 1
#define AUDIO_SAMPLERATE 48000
#define AUDIO_RESAMPLER_QUALITY 1
// SIP settings
#define UA_REGISTRATION_TIME 3600
#define UA_MEDIA_PORT_START 20000
#define UA_MEDIA_PORT_FINISH 30000
// Codec payload types
#define MT_AMRNB_PAYLOADTYPE 112
#define MT_AMRWB_PAYLOADTYPE 96
#define MT_EVS_PAYLOADTYPE 127
#define MT_OPUS_CODEC_PT 106
```
### Runtime Configuration
Configuration is passed via `VariantMap` objects to the UserAgent:
```cpp
enum
{
CONFIG_IPV4 = 0, // Use IP4
CONFIG_IPV6, // Use IP6
CONFIG_USERNAME, // Username
CONFIG_DOMAIN, // Domain
CONFIG_PASSWORD, // Password
CONFIG_STUNSERVER_NAME, // STUN server hostname
CONFIG_STUNSERVER_PORT, // STUN server port
CONFIG_TRANSPORT, // 0=all, 1=UDP, 2=TCP, 3=TLS
// ... see EP_Engine.h for full list
};
```
## Dependencies
### Git Submodules
The project uses Git submodules for some dependencies:
- `src/libs/resiprocate` - SIP stack (sevana branch)
- `src/libs/libsrtp` - SRTP library
- `src/libs/libraries` - Prebuilt platform libraries
To initialize:
```bash
git submodule update --init --recursive
```
### Third-Party Libraries
Prebuilt libraries are provided in `src/libs/libraries/`:
- OpenSSL 1.1 (crypto, SSL)
- Opus codec
- Opencore AMR (NB/WB)
- Boost (headers)
- PortAudio
- libevent2
## Testing
There is no dedicated test suite in the main project. Testing is typically done through:
1. Integration with the final application
2. The `run_ci.sh` script for build verification
3. Unit tests in individual library submodules (e.g., oboe)
### CI Build
```bash
./run_ci.sh
```
This configures with CMake and builds with make (2 parallel jobs).
## Security Considerations
1. **OpenSSL Integration**: Uses OpenSSL 1.1+ for TLS and certificate handling
2. **SRTP Support**: Media encryption via libsrtp
3. **Certificate Management**: Root certificates can be added at runtime via `addRootCert()`
4. **AMR/EVS Patents**: The project does NOT include patent licenses for AMR and EVS codecs. Users must acquire these independently.
## Integration Guide
### Basic Usage Pattern
1. Create `AgentImpl` instance
2. Send JSON commands via `command()` method
3. Poll for events via `waitForData()` and `read()`
4. Process JSON event responses
### Example Commands
- `config` - Configure the engine
- `start` - Start the engine
- `createAccount` - Create SIP account
- `startAccount` - Register account
- `createSession` - Create call session
- `startSession` - Make/accept call
- `waitForEvent` - Poll for events
## Development Workflow
1. **Making Changes**: Edit files in appropriate `src/engine/<module>/` directory
2. **Building**: Use `build_linux.py` for quick Linux builds
3. **Testing**: Integrate with test application or use existing CI
4. **Commits**: Follow existing commit message style (visible in git log)
## Common Tasks
### Adding a New Codec
1. Create `MT_<Codec>Codec.cpp/h` in `src/engine/media/`
2. Derive from `MT::Codec` base class
3. Register in `MT_CodecList.cpp`
4. Add to `src/CMakeLists.txt` if external library needed
### Adding Platform Audio Support
1. Create `Audio_<Platform>.cpp/h` in `src/engine/audio/`
2. Implement `Audio::Interface` methods
3. Add platform detection in `src/CMakeLists.txt`
4. Include in build conditionally
### Modifying Build Configuration
- Edit `src/CMakeLists.txt` for main build changes
- Edit `src/libs/libraries/platform_libs.cmake` for platform library paths
- Edit `src/engine/engine_config.h` for compile-time constants
## Notes
- The codebase uses C++20 features - ensure compiler compatibility
- Thread safety: Use `std::recursive_mutex` (e.g., `mAgentMutex`) for thread-safe access
- Memory management: Uses smart pointers (`std::shared_ptr`) extensively
- The JSON library is embedded in `src/libs/json/`
-79
View File
@@ -1,79 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A more detailed agent reference (style guide, JSON command list, integration notes, full module class listings) is in `AGENTS.md`. Read that for deeper context; this file only captures what is needed to be productive quickly.
## What this repo produces
A single C++20 static library, `librtphone.a`, that implements a SIP/RTP softphone stack (codecs, media transport, SIP user agent, cross-platform audio I/O). It is consumed by other applications via a JSON command interface (`AgentImpl`). There is no executable target in `src/` — only the library.
## Build and run
The Python scripts wipe and recreate their build directory each invocation, so they are clean configure+build runs, not incremental.
```bash
python3 build_linux.py # wipes build_linux/, configures with Ninja, outputs build_linux/librtphone.a
python3 build_android.py # needs ANDROID_NDK_HOME and VCPKG_ROOT; arm64-v8a, API 24
python3 build_windows.py # needs VCPKG_ROOT (defaults to C:\tools\vcpkg); VS 2022 x64
./run_ci.sh # Make-based CI build into ./build (used by Jenkins; pings Telegram on failure)
```
For incremental work, configure once and rebuild manually instead of re-running the wrapper:
```bash
mkdir -p build && cd build
cmake ../src -G Ninja -D OPUS_X86_MAY_HAVE_SSE4_1=ON
cmake --build . -j$(nproc)
```
CMake options (in `src/CMakeLists.txt`): `USE_AMR_CODEC` (default ON, force-OFF on Android), `USE_EVS_CODEC` (default ON), `USE_MUSL` (default OFF).
## Tests
There is no unit test suite for the library itself. The only first-party test is `test/rtp_decode/` — a standalone CMake project that links the library and exercises RTP decoding from a capture file. Build and run it via:
```bash
mkdir -p test/rtp_decode/build && cd test/rtp_decode/build
cmake .. && cmake --build . -j$(nproc)
./rtp_decode <args>
```
Note: `test/rtp_decode/CMakeLists.txt` does `add_subdirectory(../../src ...)`, so it rebuilds the whole library inside its own build tree. Don't expect to share artifacts with `build_linux/`.
## Submodules and external paths
The `src/libs/` directory mixes vendored sources with three git submodules: `resiprocate` (SIP stack, sevana branch), `libsrtp`, and `libraries` (prebuilt platform binaries — OpenSSL 1.1, Boost headers, etc.). After clone:
```bash
git submodule update --init --recursive
```
## Architecture: how a call flows through the modules
The five modules under `src/engine/` form a vertical stack. Understanding the flow matters more than the file list (which is in `AGENTS.md`):
1. **`agent/`** — Public API surface. Hosts apps create one `AgentImpl`, push JSON command strings into `command()`, and pull JSON event strings out via `waitForData()`/`read()`. This is the only thing external code should touch.
2. **`endpoint/`** — Wraps reSIProcate. `UserAgent` (in `EP_Engine.h`) owns the SIP transports, registrations, and session lifecycle; `EP_Account` and `EP_Session` are the SIP-side objects the agent manipulates. `EP_AudioProvider`/`EP_DataProvider` bridge SIP sessions to media streams.
3. **`media/`** (`MT::` namespace) — Codec registry (`MT_CodecList`), per-call audio pipeline (`MT_AudioStream`, `MT_AudioReceiver`), RTP send (`MT_NativeRtpSender`), SRTP (`MT_SrtpHelper`), DTMF (`MT_Dtmf`). Codec wrappers like `MT_AmrCodec`/`MT_EvsCodec` adapt vendored libraries in `src/libs/` to the `MT::Codec` base.
4. **`audio/`** (`Audio::` namespace) — Cross-platform device I/O. `Audio::Interface` is the abstraction; concrete implementations are picked at compile time: `Audio_DirectSound` (Windows), `Audio_CoreAudio` (macOS/iOS), `Audio_AndroidOboe` (Android, via `libs/oboe`), `Audio_Null` (testing). Also hosts mixing, resampling, WAV I/O, AEC integration.
5. **`helper/`** (`HL::` namespace) — Cross-cutting primitives used by every other module: `HL_Sync` (mutex/event), `HL_NetworkSocket`, `HL_Log`, `HL_VariantMap` (used as the runtime config bag passed to `UserAgent`), `HL_Rtp`, `HL_IuUP` (3G Iu-UP framing), `HL_ThreadPool`, `HL_ByteBuffer`.
ICE/STUN lives separately under `src/libs/ice/` (it is compiled directly into the library, not as a subproject) and is wired into the media path for NAT traversal.
Compile-time tunables — sample rate (48 kHz), buffer sizes, RTP/codec payload types, media port range — are all in `src/engine/engine_config.h`. Runtime configuration flows through `HL::VariantMap` keyed by the `CONFIG_*` enum in `EP_Engine.h`.
Per-stream call-quality metrics (RTT, jitter per RFC 3550 §A.8, packet-loss timeline, RFC 2833 DTMF events, network MOS) are collected in `MT::Statistics` / `MT::JitterStatistics` in `src/engine/media/MT_Statistics.{h,cpp}` and surfaced through the agent's JSON event stream.
## Conventions worth knowing before editing
- **File prefixes encode the module**: `Agent_*`, `EP_*`, `MT_*`, `Audio_*`, `HL_*`. Match this when adding files.
- **Members use `m` prefix** (`mAgentMutex`, `mSessionMap`); smart-pointer typedefs use `P` prefix (`PSession`, `PVariantMap`).
- **Platform code is gated by `TARGET_WIN` / `TARGET_LINUX` / `TARGET_OSX` / `TARGET_ANDROID` / `TARGET_MUSL`**, set in `src/CMakeLists.txt`. Don't sniff `_WIN32`/`__linux__` directly.
- **Every source file carries the MPL 2.0 header** (see top of any existing `.cpp`). New files need the same block.
- **Thread safety is via `std::recursive_mutex`** (e.g. `mAgentMutex` guards the agent's public surface). Memory uses `std::shared_ptr` extensively — prefer the existing `P*` typedefs over raw pointers.
- The codebase recently migrated to **C++20 and `std::chrono`**; avoid reintroducing older idioms or hand-rolled time math.
## Patent caveat
AMR-NB/AMR-WB and EVS sources are included but no patent licenses are bundled. If a change touches `MT_AmrCodec`/`MT_EvsCodec` or their build flags, keep in mind users are expected to license these codecs themselves — don't enable them by default in contexts where that hasn't been arranged.
-373
View File
@@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
-141
View File
@@ -1,141 +0,0 @@
# RTPhone Platform
RTPhone is a comprehensive real-time communication (RTC) platform that provides a complete software stack for building VoIP/SIP-based communication applications. Developed by VoIP Objects (Sevana), RTPhone delivers production-ready voice communication capabilities with extensive codec support and cross-platform compatibility.
## Overview
RTPhone serves as a static library (`librtphone.a`) that can be integrated into larger telephony and communication systems. It provides a JSON-based command interface for easy integration and control, making it suitable for building softphones, PBX systems, WebRTC gateways, and carrier-grade voice solutions.
## Key Features
### Audio Codec Support
RTPhone supports an extensive range of audio codecs:
**Standard Codecs:**
- G.711 (A-law/¼-law)
- G.722 (16kHz wideband)
- G.729
- GSM (Full Rate, Half Rate, Enhanced Full Rate)
- iLBC (20ms/30ms)
- ISAC (16kHz/32kHz)
**Advanced Codecs:**
- AMR-NB/AMR-WB (Adaptive Multi-Rate Narrowband/Wideband) - please be aware - there is no patents for AMR codecs usage included ! You should acquire them on your own.
- EVS (Enhanced Voice Services) - 3GPP's latest codec. Again - please be aware - there is no patents for EVS codec usage included ! You should acquire them on your own.
- Opus - Modern low-latency codec
- Speex (with acoustic echo cancellation)
**Codec Features:**
- Bandwidth-efficient and octet-aligned modes
- IuUP (Iu User Plane) protocol support for 3G networks
- Dynamic codec switching
- Packet loss concealment (PLC)
- Comfort noise generation (CNG)
### Network & Protocol Support
**SIP Features:**
- Full SIP 2.0 implementation via reSIProcate
- Multiple transport protocols (UDP, TCP, TLS)
- Registration, authentication, and session management
- SIP MESSAGE, presence, and REFER support
**Media Transport:**
- RTP/RTCP for media streaming
- SRTP for secure media
- ICE for NAT traversal with STUN/TURN support
- WebRTC integration components
- IPv4 and IPv6 support
### Cross-Platform Audio Support
- DirectSound/WMME (Windows)
- Core Audio (macOS/iOS)
- ALSA/PulseAudio (Linux)
- Oboe (Android) for low-latency audio
- PortAudio fallback support
### Audio Quality Features
- 48kHz sample rate support
- Acoustic Echo Cancellation (AEC)
- Audio resampling and format conversion
- Multi-channel audio mixing
- Perceptual Voice Quality Assessment (PVQA)
## Architecture
The platform is organized into several core modules:
- **Engine/Agent**: JSON-based command interface
- **Engine/Endpoint**: SIP user agent implementation
- **Engine/Media**: Audio codec management and processing
- **Engine/Audio**: Cross-platform audio I/O handling
- **Engine/Helper**: Utility functions (networking, logging, threading)
## Supported Platforms
- Linux (x64, ARM/Raspberry Pi)
- Windows (32/64-bit)
- macOS
- Android (with Oboe integration)
- iOS
## Building
RTPhone uses a CMake-based build system with cross-compilation support:
### Linux
```bash
python3 build_linux.py
```
### Android
```bash
python3 build_android.py
# or
./build_android.sh
```
### Dependencies
- CMake 3.10+
- OpenSSL 1.1+
- Boost libraries
- Platform-specific audio libraries
## Recent Updates
Recent development has focused on:
- AMR codec parsing and decoding improvements
- Octet-aligned mode fixes for AMR-WB
- RTP SSRC handling enhancements
- Build system optimizations
- Code modernization to C++20
## Use Cases
RTPhone is good for building:
- VoIP softphones and mobile applications
- PBX and telephony server systems
- RTP proxies
- Carrier-grade voice communication platforms
- 3GPP/IMS-compliant systems
## Security
RTPhone includes comprehensive security features:
- OpenSSL 1.1 integration for encryption
- TLS transport layer security
- SRTP media encryption
- Certificate management support
## Integration
The platform provides a developer-friendly interface with:
- Event-driven architecture
- Comprehensive logging system
- Modern C++20 codebase
For detailed integration instructions and API documentation, please refer to the source code and header files in the `/src/engine/` directory.
## License
Our source code is licensed under the MPL license. Naturally, any third-party components we use are subject to their respective licenses.
-45
View File
@@ -1,45 +0,0 @@
#!/usr/bin/python3
from pathlib import Path
import os
import multiprocessing
import shutil
# Temporary build directory
DIR_BUILD = 'build_android'
# Android NDK home directory
NDK_HOME = os.environ['ANDROID_NDK_HOME']
VCPKG_ROOT = os.environ['VCPKG_ROOT']
# CMake toolchain file
TOOLCHAIN_FILE = f'{NDK_HOME}/build/cmake/android.toolchain.cmake'
TOOLCHAIN_FILE_VCPKG=f'{VCPKG_ROOT}/scripts/buildsystems/vcpkg.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} -DCMAKE_TOOLCHAIN_FILE={TOOLCHAIN_FILE_VCPKG} '
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()
-38
View File
@@ -1,38 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
import os
import multiprocessing
import shutil
# Temporary build directory
DIR_BUILD = 'build_linux'
# This directory
DIR_THIS = Path(__file__).parent.resolve()
# Path to app
DIR_SOURCE = (DIR_THIS / '../src').resolve()
def make_build() -> Path:
if Path(DIR_BUILD).exists():
shutil.rmtree(DIR_BUILD)
os.mkdir(DIR_BUILD)
os.chdir(DIR_BUILD)
# OPUS_X86_MAY_HAVE_SSE4_1 is for clang builds
cmd = f'cmake ../src -G Ninja -D OPUS_X86_MAY_HAVE_SSE4_1=ON'
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}')
-55
View File
@@ -1,55 +0,0 @@
#!/usr/bin/env python3
# This script is just to check if the library is buildable
from pathlib import Path
import os
import multiprocessing
import shutil
# Temporary build directory
DIR_BUILD = 'build_windows'
# This directory
DIR_THIS = Path(__file__).parent.resolve()
# Path to app
DIR_SOURCE = (DIR_THIS / '../src').resolve()
CMAKE_GENERATOR = '-G "Visual Studio 17 2022" -A x64'
# Not used yet
CMAKE_BUILD_TYPE = 'Debug'
def make_build() -> Path:
if Path(DIR_BUILD).exists():
shutil.rmtree(DIR_BUILD)
os.mkdir(DIR_BUILD)
os.chdir(DIR_BUILD)
if os.environ['VCPKG_ROOT']:
vcpkg_root = os.environ['VCPKG_ROOT']
else:
vcpkg_root = 'C:\\tools\\vcpkg'
if not Path(vcpkg_root).exists():
print(f'Failed to find vcpkg (OpenSSL libraries needed)')
exit(1)
cmd = f'cmake ../src {CMAKE_GENERATOR} -D CMAKE_TOOLCHAIN_FILE="{vcpkg_root}\\scripts\\buildsystems\\vcpkg.cmake"'
print(cmd)
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) / 'Debug' / 'rtphone.lib'
if __name__ == '__main__':
p = make_build()
print (f'Built: {p}')
-123
View File
@@ -1,123 +0,0 @@
#!/usr/bin/env python3
"""
Count C/C++ files and lines in the project.
Works on both Linux and Windows.
"""
import os
import sys
from pathlib import Path
# C/C++ file extensions
CPP_EXTENSIONS = {'.c', '.cc', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx'}
def is_cpp_file(filepath: Path) -> bool:
"""Check if file has a C/C++ extension."""
return filepath.suffix.lower() in CPP_EXTENSIONS
def count_lines_in_file(filepath: Path) -> int:
"""Count lines in a single file."""
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
return sum(1 for _ in f)
except (IOError, OSError):
return 0
def collect_cpp_files(root_dir: Path) -> list[Path]:
"""Collect all C/C++ files recursively from root directory."""
cpp_files = []
for path in root_dir.rglob('*'):
if path.is_file() and is_cpp_file(path):
cpp_files.append(path)
return cpp_files
def get_directory_stats(root_dir: Path, cpp_files: list[Path]) -> dict[str, dict]:
"""Get statistics grouped by immediate subdirectories."""
stats = {}
# Initialize stats for immediate subdirectories
for item in root_dir.iterdir():
if item.is_dir():
stats[item.name] = {'files': 0, 'lines': 0}
# Also count files directly in root
stats['.'] = {'files': 0, 'lines': 0}
for filepath in cpp_files:
try:
# Get relative path from root
rel_path = filepath.relative_to(root_dir)
# Determine which immediate subdirectory this file belongs to
if len(rel_path.parts) == 1:
# File is directly in root
dir_key = '.'
else:
# File is in a subdirectory
dir_key = rel_path.parts[0]
if dir_key in stats:
line_count = count_lines_in_file(filepath)
stats[dir_key]['files'] += 1
stats[dir_key]['lines'] += line_count
except (ValueError, IndexError):
continue
return stats
def main():
# Use current directory as root, or accept a path argument
if len(sys.argv) > 1:
root_dir = Path(sys.argv[1]).resolve()
else:
root_dir = Path.cwd()
if not root_dir.exists():
print(f"Error: Directory '{root_dir}' does not exist.")
sys.exit(1)
print(f"Scanning C/C++ files in: {root_dir}")
print("=" * 60)
# Collect all C/C++ files
cpp_files = collect_cpp_files(root_dir)
if not cpp_files:
print("No C/C++ files found.")
sys.exit(0)
# Get directory statistics
dir_stats = get_directory_stats(root_dir, cpp_files)
# Print per-directory statistics
print("\nPer-directory C/C++ statistics:")
print("-" * 60)
print(f"{'Directory':<30} {'Files':>10} {'Lines':>15}")
print("-" * 60)
total_files = 0
total_lines = 0
# Sort directories: root first, then alphabetically
sorted_dirs = sorted(dir_stats.keys(), key=lambda x: (x != '.', x.lower()))
for dir_name in sorted_dirs:
stat = dir_stats[dir_name]
if stat['files'] > 0:
display_name = '(root)' if dir_name == '.' else dir_name
print(f"{display_name:<30} {stat['files']:>10} {stat['lines']:>15,}")
total_files += stat['files']
total_lines += stat['lines']
print("-" * 60)
print(f"{'TOTAL':<30} {total_files:>10} {total_lines:>15,}")
print("=" * 60)
if __name__ == '__main__':
main()
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
SEND_MSG="/var/ci/conformance_ci/send_telegram_message.py"
mkdir -p build
cd build
# Configure
cmake ../src
if [ $? -ne 0 ]; then
/usr/bin/python3 $SEND_MSG "rtphone cmake failed. $BUILD_URL"
exit 1
fi
# Build
make -j2
if [ $? -ne 0 ]; then
/usr/bin/python3 $SEND_MSG "rtphone build failed. $BUILD_URL"
exit 1
fi
/usr/bin/python3 $SEND_MSG "rtphone builds ok. $BUILD_URL"
+192 -344
View File
@@ -1,381 +1,233 @@
cmake_minimum_required(VERSION 3.20)
project(rtphone) project(rtphone)
# Rely on C++ 20 cmake_minimum_required(VERSION 3.0)
set (CMAKE_CXX_STANDARD 20)
macro(configure_msvc_runtime)
if(MSVC)
# Default to statically-linked runtime.
if("${MSVC_RUNTIME}" STREQUAL "")
set(MSVC_RUNTIME "static")
endif()
# Set compiler options.
set(variables
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELWITHDEBINFO
)
if(${MSVC_RUNTIME} STREQUAL "static")
message(STATUS
"rtphone: MSVC -> forcing use of statically-linked runtime."
)
foreach(variable ${variables})
if(${variable} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${variable} "${${variable}}")
endif()
endforeach()
else()
message(STATUS
"rtphone: MSVC -> forcing use of dynamically-linked runtime."
)
foreach(variable ${variables})
if(${variable} MATCHES "/MT")
string(REGEX REPLACE "/MT" "/MD" ${variable} "${${variable}}")
endif()
endforeach()
endif()
foreach(variable ${variables})
string(REGEX REPLACE "/Z[iI7]" ""
${variable}
"${${variable}}")
set(${variable} "${${variable}} /Zi /Oy-")
endforeach()
endif()
endmacro()
# Rely on C++ 11
set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_STANDARD_REQUIRED ON)
set (L libs) set (rtphone_libs libs)
set (E engine) set (rtphone_engine engine)
option (USE_AMR_CODEC "Use AMR codec. Requires libraries." ON) set (USE_AMR_CODEC OFF CACHE BOOL "Use AMR codec. Requires libraries.")
option (USE_EVS_CODEC "Use EVS codec." ON) set (USE_EVS_CODEC OFF CACHE BOOL "Use EVS codec." )
option (USE_MUSL "Build with MUSL library" OFF) set (USE_OPUS_CODEC OFF CACHE BOOL "Use Opus codec." )
set (USE_PVQA_LIB OFF CACHE BOOL "Build with Sevana PVQA library" )
set (USE_AQUA_LIB OFF CACHE BOOL "Build with Sevana AQuA library" )
set (USE_MUSL OFF CACHE BOOL "Build with MUSL library" )
# PIC code by default # PIC code by default
set (CMAKE_POSITION_INDEPENDENT_CODE ON) set (CMAKE_POSITION_INDEPENDENT_CODE ON)
set (RUNTIME_CPU_CAPABILITY_DETECTION ON)
set (CMAKE_WARN_DEPRECATED OFF)
find_package(OpenSSL REQUIRED) if (NOT DEFINED LIB_PLATFORM)
set (OPENSSL_SSL OpenSSL::SSL) set (LIB_PLATFORM ${CMAKE_CURRENT_SOURCE_DIR}/../../libraries)
set (OPENSSL_CRYPTO OpenSSL::Crypto) endif()
message("Libraries: ${LIB_PLATFORM}")
set (OPENSSL_INCLUDE ${LIB_PLATFORM}/openssl/1.0/include)
message ("Using OpenSSL include files from ${OPENSSL_INCLUDE}") message ("Using OpenSSL include files from ${OPENSSL_INCLUDE}")
message ("Using OpenSSL libs: ${OPENSSL_SSL} and ${OPENSSL_CRYPTO}")
# include_directories(${OPENSSL_INCLUDE})
# Used defines for our project
set (DEFINES -DUSE_OPENSSL)
# Libraries for our project
set (LIBS_STATIC "")
set (LIBS_DYNAMIC "")
# Try to prefer static libraries anyway
set (CMAKE_FIND_LIBRARY_SUFFIXES .a .so .dylib)
# Windows-specific definitions
if (CMAKE_SYSTEM MATCHES "Windows*") if (CMAKE_SYSTEM MATCHES "Windows*")
set (DEFINES ${DEFINES} -DTARGET_WIN -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS -D_UNICODE -D_CRT_SECURE_NO_WARNINGS -DNOMINMAX) add_definitions (-DTARGET_WIN -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS -D_UNICODE -D_CRT_SECURE_NO_WARNINGS)
set (TARGET_WIN ON)
endif() endif()
# Linux-specific definitions
if (CMAKE_SYSTEM MATCHES "Linux*") if (CMAKE_SYSTEM MATCHES "Linux*")
set (DEFINES ${DEFINES} -DTARGET_LINUX -DHAVE_NETINET_IN_H) add_definitions (-DTARGET_LINUX)
set (TARGET_LINUX ON)
set (LIBS_STATIC ${LIBS_STATIC} dl)
endif() endif()
# macOS-specific definitions
if (CMAKE_SYSTEM MATCHES "Darwin*") if (CMAKE_SYSTEM MATCHES "Darwin*")
set (DEFINES ${DEFINES} -DTARGET_OSX) add_definitions (-DTARGET_OSX)
set (TARGET_OSX ON)
set (LIBS_STATIC ${LIBS_STATIC} dl)
endif() endif()
if (CMAKE_SYSTEM MATCHES "Android") if (CMAKE_SYSTEM MATCHES "Android")
# Disable opencore AMR codecs - there is no ready libraries in vcpkg right now
set (USE_AMR_CODEC OFF)
message("Adding the Oboe library") message("Adding the Oboe library")
set (OBOE_DIR libs/oboe) set (OBOE_DIR libs/oboe)
add_subdirectory (${OBOE_DIR} build_oboe) add_subdirectory (${OBOE_DIR} ./oboe)
include_directories (${OBOE_DIR}/include) include_directories (${OBOE_DIR}/include)
set (DEFINES ${DEFINES} -DTARGET_ANDROID -DHAVE_NETINET_IN_H)
set (TARGET_ANDROID ON)
set (LIBS_STATIC ${LIBS} oboe)
endif() endif()
if (USE_MUSL) if (USE_MUSL)
set (DEFINES ${DEFINES} -DTARGET_MUSL) add_definitions(-DTARGET_MUSL)
set (TARGET_MUSL ON) endif()
if (USE_AQUA_LIB)
message("Use AQuA library")
add_definitions( -DUSE_AQUA_LIBRARY )
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs/pvqa/include)
endif()
if (USE_PVQA_LIBRARY)
message("Use PVQA libraries")
add_definitions( -DUSE_PVQA_LIBRARY )
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/libs/pvqa/include
${CMAKE_CURRENT_SOURCE_DIR}/libs/pvqa++/include)
endif() endif()
set (RTPHONE_SOURCES set (RTPHONE_SOURCES
${E}/engine_config.h ${rtphone_engine}/media/MT_Statistics.cpp
${E}/media/MT_Statistics.cpp ${rtphone_engine}/media/MT_WebRtc.cpp
${E}/media/MT_WebRtc.cpp ${rtphone_engine}/media/MT_Stream.cpp
${E}/media/MT_Stream.cpp ${rtphone_engine}/media/MT_SrtpHelper.cpp
${E}/media/MT_SrtpHelper.cpp ${rtphone_engine}/media/MT_SingleAudioStream.cpp
${E}/media/MT_SingleAudioStream.cpp ${rtphone_engine}/media/MT_NativeRtpSender.cpp
${E}/media/MT_NativeRtpSender.cpp ${rtphone_engine}/media/MT_Dtmf.cpp
${E}/media/MT_Dtmf.cpp ${rtphone_engine}/media/MT_CodecList.cpp
${E}/media/MT_CodecList.cpp ${rtphone_engine}/media/MT_Codec.cpp
${E}/media/MT_Codec.cpp ${rtphone_engine}/media/MT_Box.cpp
${E}/media/MT_Box.cpp ${rtphone_engine}/media/MT_AudioStream.cpp
${E}/media/MT_AudioStream.cpp ${rtphone_engine}/media/MT_AudioReceiver.cpp
${E}/media/MT_AudioReceiver.cpp ${rtphone_engine}/media/MT_AudioCodec.cpp
${E}/media/MT_AudioCodec.cpp ${rtphone_engine}/media/MT_CngHelper.cpp
${E}/media/MT_CngHelper.cpp ${rtphone_engine}/agent/Agent_Impl.cpp
${E}/agent/Agent_Impl.cpp ${rtphone_engine}/agent/Agent_AudioManager.cpp
${E}/agent/Agent_Impl.h ${rtphone_engine}/endpoint/EP_Account.cpp
${E}/agent/Agent_AudioManager.cpp ${rtphone_engine}/endpoint/EP_AudioProvider.cpp
${E}/agent/Agent_AudioManager.h ${rtphone_engine}/endpoint/EP_DataProvider.cpp
${E}/endpoint/EP_Account.cpp ${rtphone_engine}/endpoint/EP_Engine.cpp
${E}/endpoint/EP_Account.h ${rtphone_engine}/endpoint/EP_NetworkQueue.cpp
${E}/endpoint/EP_AudioProvider.cpp ${rtphone_engine}/endpoint/EP_Observer.cpp
${E}/endpoint/EP_AudioProvider.h ${rtphone_engine}/endpoint/EP_Session.cpp
${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 ${rtphone_engine}/media/MT_Statistics.h
${E}/media/MT_WebRtc.h ${rtphone_engine}/media/MT_WebRtc.h
${E}/media/MT_Stream.h ${rtphone_engine}/media/MT_Stream.h
${E}/media/MT_SrtpHelper.h ${rtphone_engine}/media/MT_SrtpHelper.h
${E}/media/MT_SingleAudioStream.h ${rtphone_engine}/media/MT_SingleAudioStream.h
${E}/media/MT_NativeRtpSender.h ${rtphone_engine}/media/MT_NativeRtpSender.h
${E}/media/MT_Dtmf.h ${rtphone_engine}/media/MT_Dtmf.h
${E}/media/MT_CodecList.h ${rtphone_engine}/media/MT_CodecList.h
${E}/media/MT_Codec.h ${rtphone_engine}/media/MT_Codec.h
${E}/media/MT_Box.h ${rtphone_engine}/media/MT_Box.h
${E}/media/MT_AudioStream.h ${rtphone_engine}/media/MT_AudioStream.h
${E}/media/MT_AudioReceiver.h ${rtphone_engine}/media/MT_AudioReceiver.h
${E}/media/MT_AudioCodec.h ${rtphone_engine}/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 ${rtphone_engine}/media/MT_CngHelper.h
${E}/helper/HL_AsyncCommand.h ${rtphone_engine}/agent/Agent_Impl.h
${E}/helper/HL_Base64.h ${rtphone_engine}/agent/Agent_AudioManager.h
${E}/helper/HL_ByteBuffer.h ${rtphone_engine}/endpoint/EP_Account.h
${E}/helper/HL_Calculator.cpp ${rtphone_engine}/endpoint/EP_AudioProvider.h
${E}/helper/HL_Calculator.h ${rtphone_engine}/endpoint/EP_DataProvider.h
${E}/helper/HL_CrashRpt.cpp ${rtphone_engine}/endpoint/EP_Engine.h
${E}/helper/HL_CrashRpt.h ${rtphone_engine}/endpoint/EP_NetworkQueue.h
${E}/helper/HL_CsvReader.cpp ${rtphone_engine}/endpoint/EP_Observer.h
${E}/helper/HL_CsvReader.h ${rtphone_engine}/endpoint/EP_Session.h
${E}/helper/HL_Epoll.cpp
${E}/helper/HL_Epoll.h
${E}/helper/HL_Exception.h
${E}/helper/HL_File.cpp
${E}/helper/HL_File.h
${E}/helper/HL_HepSupport.cpp
${E}/helper/HL_HepSupport.h
${E}/helper/HL_InternetAddress.h
${E}/helper/HL_IuUP.cpp
${E}/helper/HL_IuUP.h
${E}/helper/HL_Log.cpp
${E}/helper/HL_Log.h
${E}/helper/HL_NetworkFrame.cpp
${E}/helper/HL_NetworkFrame.h
${E}/helper/HL_NetworkSocket.cpp
${E}/helper/HL_NetworkSocket.h
${E}/helper/HL_Optional.hpp
${E}/helper/HL_OsVersion.cpp
${E}/helper/HL_OsVersion.h
${E}/helper/HL_Pointer.cpp
${E}/helper/HL_Pointer.h
${E}/helper/HL_Process.cpp
${E}/helper/HL_Process.h
${E}/helper/HL_Rtp.cpp
${E}/helper/HL_Rtp.h
${E}/helper/HL_Singletone.cpp
${E}/helper/HL_Singletone.h
${E}/helper/HL_SocketHeap.cpp
${E}/helper/HL_SocketHeap.h
${E}/helper/HL_Statistics.cpp
${E}/helper/HL_Statistics.h
${E}/helper/HL_StreamState.h
${E}/helper/HL_String.cpp
${E}/helper/HL_String.h
${E}/helper/HL_Sync.cpp
${E}/helper/HL_Sync.h
${E}/helper/HL_ThreadPool.cpp
${E}/helper/HL_ThreadPool.h
${E}/helper/HL_Time.cpp
${E}/helper/HL_Time.h
${E}/helper/HL_Types.h
${E}/helper/HL_Types.cpp
${E}/helper/HL_Usb.cpp
${E}/helper/HL_Usb.h
${E}/helper/HL_Uuid.cpp
${E}/helper/HL_Uuid.h
${E}/helper/HL_VariantMap.cpp
${E}/helper/HL_VariantMap.h
${E}/helper/HL_Xcap.cpp
${E}/helper/HL_Xcap.h
${E}/audio/Audio_Resampler.cpp
${E}/audio/Audio_Resampler.h
${E}/audio/Audio_Quality.cpp
${E}/audio/Audio_Quality.h
${E}/audio/Audio_Mixer.cpp
${E}/audio/Audio_Mixer.h
${E}/audio/Audio_Interface.cpp
${E}/audio/Audio_Interface.h
${E}/audio/Audio_Helper.cpp
${E}/audio/Audio_Helper.h
${E}/audio/Audio_DataWindow.cpp
${E}/audio/Audio_DataWindow.h
${E}/audio/Audio_DevicePair.cpp
${E}/audio/Audio_DevicePair.h
${E}/audio/Audio_Player.cpp
${E}/audio/Audio_Player.h
${E}/audio/Audio_Null.cpp
${E}/audio/Audio_Null.h
${E}/audio/Audio_CoreAudio.cpp
${E}/audio/Audio_CoreAudio.h
${E}/audio/Audio_DirectSound.cpp
${E}/audio/Audio_DirectSound.h
${E}/audio/Audio_AndroidOboe.cpp
${E}/audio/Audio_AndroidOboe.h
${E}/audio/Audio_WavFile.cpp
${E}/audio/Audio_WavFile.h
${L}/ice/hmac_sha1_impl.cpp
${L}/ice/hmac_sha1_impl.h
${L}/ice/ICEAction.h
${L}/ice/ICEAddress.cpp
${L}/ice/ICEAddress.h
${L}/ice/ICEAuthTransaction.cpp
${L}/ice/ICEAuthTransaction.h
${L}/ice/ICEBinding.cpp
${L}/ice/ICEBinding.h
${L}/ice/ICEBox.cpp
${L}/ice/ICEBox.h
${L}/ice/ICEBoxImpl.cpp
${L}/ice/ICEBoxImpl.h
${L}/ice/ICEByteBuffer.cpp
${L}/ice/ICEByteBuffer.h
${L}/ice/ICECandidate.cpp
${L}/ice/ICECandidate.h
${L}/ice/ICECandidatePair.cpp
${L}/ice/ICECandidatePair.h
${L}/ice/ICECheckList.cpp
${L}/ice/ICECheckList.h
${L}/ice/ICECRC32.cpp
${L}/ice/ICECRC32.h
${L}/ice/ICEError.cpp
${L}/ice/ICEError.h
${L}/ice/ICEEvent.h
${L}/ice/ICELog.cpp
${L}/ice/ICELog.h
${L}/ice/ICEMD5.cpp
${L}/ice/ICEMD5.h
${L}/ice/ICENetworkHelper.cpp
${L}/ice/ICENetworkHelper.h
${L}/ice/ICEPacketTimer.cpp
${L}/ice/ICEPacketTimer.h
${L}/ice/ICEPlatform.cpp
${L}/ice/ICEPlatform.h
${L}/ice/ICERelaying.cpp
${L}/ice/ICERelaying.h
${L}/ice/ICESession.cpp
${L}/ice/ICESession.h
${L}/ice/ICESHA1.cpp
${L}/ice/ICESHA1.h
${L}/ice/ICESocket.h
${L}/ice/ICEStream.cpp
${L}/ice/ICEStream.h
${L}/ice/ICEStunAttributes.cpp
${L}/ice/ICEStunAttributes.h
${L}/ice/ICEStunConfig.cpp
${L}/ice/ICEStunConfig.h
${L}/ice/ICEStunMessage.cpp
${L}/ice/ICEStunMessage.h
${L}/ice/ICEStunTransaction.cpp
${L}/ice/ICEStunTransaction.h
${L}/ice/ICESync.cpp
${L}/ice/ICESync.h
${L}/ice/ICETime.cpp
${L}/ice/ICETime.h
${L}/ice/ICETransactionList.cpp
${L}/ice/ICETransactionList.h
${L}/ice/ICETypes.h
${L}/ice/md5_impl.cpp
${L}/ice/md5_impl.h
) )
if (USE_AMR_CODEC)
add_definitions(-DUSE_AMR_CODEC)
set(RTPHONE_SOURCES ${RTPHONE_SOURCES} ${rtphone_engine}/media/MT_AmrCodec.cpp ${rtphone_engine}/media/MT_AmrCodec.h)
endif()
if (USE_EVS_CODEC)
add_definitions(-DUSE_EVS_CODEC)
set(RTPHONE_SOURCES ${RTPHONE_SOURCES} ${rtphone_engine}/media/MT_EvsCodec.cpp ${rtphone_engine}/media/MT_EvsCodec.h)
endif()
if (USE_OPUS_CODEC)
add_definitions(-DUSE_OPUS_CODEC)
endif()
add_library (rtphone STATIC ${RTPHONE_SOURCES}) add_library (rtphone STATIC ${RTPHONE_SOURCES})
add_subdirectory(${L}/resiprocate) add_subdirectory(${rtphone_libs}/resiprocate)
add_subdirectory(${L}/jrtplib/src) add_subdirectory(${rtphone_libs}/ice)
add_subdirectory(${L}/libg729) add_subdirectory(${rtphone_libs}/jrtplib/src)
add_subdirectory(${rtphone_libs}/libg729)
if (USE_EVS_CODEC) if (USE_EVS_CODEC)
add_subdirectory(${L}/libevs) add_subdirectory(${rtphone_libs}/libevs)
endif() endif()
add_subdirectory(${L}/libgsm) add_subdirectory(${rtphone_libs}/libgsm)
add_subdirectory(${L}/gsmhr) add_subdirectory(${rtphone_libs}/gsmhr)
add_subdirectory(${L}/g722) add_subdirectory(${rtphone_libs}/g722)
add_subdirectory(${L}/speexdsp) add_subdirectory(${rtphone_libs}/speexdsp)
add_subdirectory(${L}/libsrtp) add_subdirectory(${rtphone_libs}/srtp)
add_subdirectory(${L}/webrtc) add_subdirectory(${rtphone_libs}/webrtc)
add_subdirectory(${L}/opus) add_subdirectory(${rtphone_engine}/helper)
add_subdirectory(${rtphone_engine}/audio)
add_subdirectory(${rtphone_engine}/media)
# Suppose the subproject defines target "mylib" set (LIBS ice_stack jrtplib g729_codec gsm_codec
if(MSVC) gsmhr_codec g722_codec srtp resiprocate helper_lib audio_lib webrtc speexdsp
# target_compile_options(opus PRIVATE /O2 /DNDEBUG) uuid)
# Optional: enable whole program optimization on MSVC
# target_compile_options(mylib PRIVATE /GL) if (CMAKE_SYSTEM MATCHES "Win*")
# target_link_options(mylib PRIVATE /LTCG) set (LIBS ${LIBS} )
else() else ()
target_compile_options(opus PRIVATE -O3 -DNDEBUG) set (LIBS ${LIBS} dl uuid)
endif ()
if (CMAKE_SYSTEM MATCHES "Android")
set (LIBS ${LIBS} oboe)
endif() endif()
set (LIBS_STATIC ${LIBS_STATIC} jrtplib g729_codec gsm_codec opus
gsmhr_codec g722_codec srtp3 resiprocate webrtc speexdsp)
if (USE_AMR_CODEC) if (USE_AMR_CODEC)
message("Media: AMR NB and WB codecs will be included") set (LIBS ${LIBS})
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/libs/opencore-amr build_opencore) endif (USE_AMR_CODEC)
set (OPENCORE_AMRWB opencore-amrwb)
set (OPENCORE_AMRNB opencore-amrnb)
set (DEFINES ${DEFINES} -DUSE_AMR_CODEC) target_link_libraries(rtphone
set (LIBS_STATIC ${LIBS_STATIC} ${OPENCORE_AMRNB} ${OPENCORE_AMRWB}) ice_stack jrtplib g729_codec gsm_codec
endif() gsmhr_codec g722_codec srtp resiprocate
helper_lib
audio_lib
webrtc
speexdsp
uuid
${OPENSSL_SSL}
${OPENSSL_CRYPTO}
${LIBS} )
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)
# PRIVATE, not PUBLIC: rtphone is a STATIC library, so these link options are
# never used to build rtphone itself and must not propagate to consumers.
# As PUBLIC they leaked into every consumer's LINK_FLAGS as an adjacent
# "-Wl,-Bstatic -Wl,-Bdynamic" pair (the wrapped libraries land in a separate
# LINK_LIBRARIES section, so nothing is actually wrapped). The trailing
# -Bdynamic forced the linker back into dynamic-search mode, which broke
# fully-static consumers (e.g. vq-core built with SERVER_STATIC_LINKING=ON:
# "attempted static link of dynamic object libz.so").
target_link_options(rtphone PRIVATE -Wl,-Bstatic)
target_compile_options(rtphone PUBLIC -Wno-deprecated -Wno-deprecated-declarations)
endif()
target_link_libraries(rtphone PUBLIC ${LIBS_STATIC})
if (TARGET_LINUX)
target_link_options(rtphone PRIVATE -Wl,-Bdynamic)
endif()
target_include_directories(rtphone target_include_directories(rtphone
PUBLIC PUBLIC
@@ -383,19 +235,15 @@ target_include_directories(rtphone
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/engine> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/engine>
${CMAKE_CURRENT_SOURCE_DIR}/libs ${CMAKE_CURRENT_SOURCE_DIR}/libs
${LIB_PLATFORM}/opus/include ${LIB_PLATFORM}/opus/include
${E}/helper
${E}/audio
${E}/media
${L}
${L}/ice
PRIVATE PRIVATE
${L}/libevs/lib_com ${CMAKE_CURRENT_SOURCE_DIR}/libs/
${L}/libevs/lib_enc ${CMAKE_CURRENT_SOURCE_DIR}/libs/libevs/lib_com
${L}/libevs/lib_dec ${CMAKE_CURRENT_SOURCE_DIR}/libs/libevs/lib_enc
${L}/speex/include ${CMAKE_CURRENT_SOURCE_DIR}/libs/libevs/lib_dec
${L}/libs/json ${CMAKE_CURRENT_SOURCE_DIR}/libs/speex/include
${CMAKE_CURRENT_SOURCE_DIR}/libs/opus/include/
${CMAKE_CURRENT_SOURCE_DIR}/libs/json
) )
find_package(OpenSSL REQUIRED) # For MSVC static builds
target_link_libraries(rtphone PUBLIC OpenSSL::SSL) configure_msvc_runtime()
target_link_libraries(rtphone PUBLIC OpenSSL::Crypto)
+116 -133
View File
@@ -1,199 +1,182 @@
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Agent_AudioManager.h" #include "Agent_AudioManager.h"
#include "../engine/audio/Audio_WavFile.h" #include "../engine/audio/Audio_WavFile.h"
#include "../engine/helper/HL_String.h"
#include "../engine/audio/Audio_Null.h" #include "../engine/audio/Audio_Null.h"
#include "HL_String.h"
#if defined(TARGET_ANDROID) #if defined(TARGET_ANDROID)
# include "../engine/audio/Audio_Android.h" # include "../engine/audio/Audio_Android.h"
#endif #endif
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "AudioManager"
// ---------------- AudioManager -------------
//static AudioManager GAudioManager;
AudioManager::AudioManager() AudioManager::AudioManager()
:mTerminal(nullptr), mAudioMonitoring(nullptr) :mTerminal(nullptr)
{ {
mPlayer.setDelegate(this); mPlayer.setDelegate(this);
} }
AudioManager::~AudioManager() AudioManager::~AudioManager()
{ {
// stop(); //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) void AudioManager::setTerminal(MT::Terminal* terminal)
{ {
mTerminal = terminal; mTerminal = terminal;
} }
MT::Terminal* AudioManager::terminal() MT::Terminal* AudioManager::terminal()
{ {
return mTerminal; 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) #define LOCK_MANAGER std::unique_lock<std::mutex> l(mGuard)
void AudioManager::start(int usageId) void AudioManager::start(int usageId)
{ {
assert(mTerminal); assert(mTerminal);
LOCK_MANAGER; LOCK_MANAGER;
ICELogInfo(<< "Start main audio with usage id " << usageId);
ICELogInfo(<< "Start main audio with usage id " << usageId); if (mUsage.obtain(usageId) > 1)
return;
if (mUsage.obtain(usageId) > 1) if (Audio::OsEngine::instance())
return; Audio::OsEngine::instance()->open();
// Maybe it is time to initialize global audio support if (!mAudioInput || !mAudioOutput)
if (Audio::OsEngine::instance()) {
Audio::OsEngine::instance()->open(); // Disable AEC for now - because PVQA conflicts with speex AEC.
std::shared_ptr<Audio::Enumerator> enumerator(Audio::Enumerator::make(usageId == atNull));
if (!mTerminal->audio())
mTerminal->setAudio(std::make_shared<Audio::DevicePair>(false, true));
if (!mAudioInput || !mAudioOutput) if (!mAudioInput)
{ {
// Disable AEC for now - because PVQA conflicts with speex AEC. enumerator->open(Audio::myMicrophone);
std::shared_ptr<Audio::Enumerator> enumerator(Audio::Enumerator::make(usageId == atNull)); int inputIndex = enumerator->indexOfDefaultDevice();
if (!mTerminal->audio())
{
auto audio = std::make_shared<Audio::DevicePair>();
audio->setAgc(true);
audio->setAec(false);
audio->setMonitoring(mAudioMonitoring);
mTerminal->setAudio(audio); // 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());
if (!mAudioInput) mTerminal->audio()->setInput(mAudioInput);
{ }
enumerator->open(Audio::myMicrophone);
int inputIndex = enumerator->indexOfDefaultDevice();
// Construct default platform input device if (!mAudioOutput)
if (usageId != atNull) {
mAudioInput = Audio::PInputDevice(Audio::InputDevice::make(enumerator->idAt(inputIndex))); Audio::Enumerator *enumerator = Audio::Enumerator::make(usageId == atNull);
else enumerator->open(Audio::mySpeaker);
mAudioInput = Audio::PInputDevice(new Audio::NullInputDevice()); int outputIndex = enumerator->indexOfDefaultDevice();
}
// Bind input to the terminal's device pair regardless of whether it was
// just constructed or externally injected via setAudioInput().
mTerminal->audio()->setInput(mAudioInput);
if (!mAudioOutput) // Construct and set terminal's audio pair output device
{ if (usageId != atNull)
Audio::Enumerator *enumerator = Audio::Enumerator::make(usageId == atNull); {
enumerator->open(Audio::mySpeaker); if (outputIndex >= enumerator->count())
int outputIndex = enumerator->indexOfDefaultDevice(); outputIndex = 0;
// Construct default platform output device mAudioOutput = Audio::POutputDevice(
if (usageId != atNull) Audio::OutputDevice::make(enumerator->idAt(outputIndex)));
{ }
if (outputIndex >= enumerator->count()) else
outputIndex = 0; mAudioOutput = Audio::POutputDevice(new Audio::NullOutputDevice());
mAudioOutput = Audio::POutputDevice( mTerminal->audio()->setOutput(mAudioOutput);
Audio::OutputDevice::make(enumerator->idAt(outputIndex))); }
} }
else
mAudioOutput = Audio::POutputDevice(new Audio::NullOutputDevice());
}
mTerminal->audio()->setOutput(mAudioOutput);
}
// Open audio // Open audio
if (mAudioInput) if (mAudioInput)
mAudioInput->open(); mAudioInput->open();
if (mAudioOutput) if (mAudioOutput)
mAudioOutput->open(); mAudioOutput->open();
} }
void AudioManager::close() void AudioManager::close()
{ {
mUsage.clear(); mUsage.clear();
if (mAudioInput) if (mAudioInput)
{ {
mAudioInput->close(); mAudioInput->close();
mAudioInput.reset(); mAudioInput.reset();
} }
if (mAudioOutput) if (mAudioOutput)
{ {
mAudioOutput->close(); mAudioOutput->close();
mAudioOutput.reset(); mAudioOutput.reset();
} }
mPlayer.setOutput(Audio::POutputDevice()); mPlayer.setOutput(Audio::POutputDevice());
} }
void AudioManager::stop(int usageId) void AudioManager::stop(int usageId)
{ {
LOCK_MANAGER; LOCK_MANAGER;
ICELogInfo( << "Stop main audio with usage id " << usageId);
if (mTerminal)
{
if (mTerminal->audio())
mTerminal->audio()->player().release(usageId);
}
ICELogInfo( << "Stop main audio with usage id " << usageId); if (!mUsage.release(usageId))
if (mTerminal) {
{ close();
if (mTerminal->audio())
mTerminal->audio()->player().release(usageId);
}
if (!mUsage.release(usageId)) // Reset device pair on terminal side
{ mTerminal->setAudio(Audio::PDevicePair());
close();
// Reset device pair on terminal side if (Audio::OsEngine::instance())
mTerminal->setAudio(Audio::PDevicePair()); Audio::OsEngine::instance()->close();
}
if (Audio::OsEngine::instance())
Audio::OsEngine::instance()->close();
}
}
void AudioManager::setAudioInput(Audio::PInputDevice input)
{
LOCK_MANAGER;
mAudioInput = std::move(input);
}
void AudioManager::setAudioOutput(Audio::POutputDevice output)
{
LOCK_MANAGER;
mAudioOutput = std::move(output);
} }
void AudioManager::startPlayFile(int usageId, const std::string& path, AudioTarget target, LoopMode lm, int timelimit) void AudioManager::startPlayFile(int usageId, const std::string& path, AudioTarget target, LoopMode lm, int timelimit)
{ {
// Check if file exists // Check if file exists
Audio::PWavFileReader r = std::make_shared<Audio::WavFileReader>(); Audio::PWavFileReader r = std::make_shared<Audio::WavFileReader>();
#ifdef TARGET_WIN #ifdef TARGET_WIN
r->open(strx::makeTstring(path)); r->open(StringHelper::makeTstring(path));
#else #else
r->open(path); r->open(path);
#endif #endif
if (!r->isOpened()) if (!r->isOpened())
{ {
ICELogError(<< "Cannot open file to play"); ICELogError(<< "Cannot open file to play");
return; return;
} }
// Delegate processing to existing audio device pair manager // Delegate processing to existing audio device pair manager
mTerminal->audio()->player().add(usageId, r, lm == lmLoopAudio, timelimit); mTerminal->audio()->player().add(usageId, r, lm == lmLoopAudio, timelimit);
start(usageId); start(usageId);
} }
void AudioManager::stopPlayFile(int usageId) void AudioManager::stopPlayFile(int usageId)
{ {
stop(usageId); stop(usageId);
mPlayer.release(usageId); mPlayer.release(usageId);
} }
void AudioManager::onFilePlayed(Audio::Player::PlaylistItem& item) void AudioManager::onFilePlayed(Audio::Player::PlaylistItem& item)
@@ -202,9 +185,9 @@ void AudioManager::onFilePlayed(Audio::Player::PlaylistItem& item)
void AudioManager::process() void AudioManager::process()
{ {
mPlayer.releasePlayed(); mPlayer.releasePlayed();
std::vector<int> ids; std::vector<int> ids;
mTerminal->audio()->player().retrieveUsageIds(ids); mTerminal->audio()->player().retrieveUsageIds(ids);
for (int id : ids) for (unsigned i=0; i<ids.size(); i++)
stop(id); stop(ids[i]);
} }
+55 -63
View File
@@ -8,25 +8,28 @@
#include "../engine/audio/Audio_Interface.h" #include "../engine/audio/Audio_Interface.h"
#include "../engine/audio/Audio_Player.h" #include "../engine/audio/Audio_Player.h"
#include "../engine/endpoint/EP_Engine.h"
#include "../engine/media/MT_Box.h" #include "../engine/media/MT_Box.h"
#include "../engine/helper/HL_Log.h"
#include "../engine/helper/HL_Sync.h"
enum enum
{ {
AudioPrefix_Ring = 1, AudioPrefix_Ring = 1,
AudioPrefix_Zero, AudioPrefix_Zero,
AudioPrefix_One, AudioPrefix_One,
AudioPrefix_Two, AudioPrefix_Two,
AudioPrefix_Three, AudioPrefix_Three,
AudioPrefix_Four, AudioPrefix_Four,
AudioPrefix_Five, AudioPrefix_Five,
AudioPrefix_Six, AudioPrefix_Six,
AudioPrefix_Seven, AudioPrefix_Seven,
AudioPrefix_Eight, AudioPrefix_Eight,
AudioPrefix_Nine, AudioPrefix_Nine,
AudioPrefix_Asterisk, AudioPrefix_Asterisk,
AudioPrefix_Diez AudioPrefix_Diez
}; };
#define AudioSessionCoeff 64 #define AudioSessionCoeff 64
@@ -34,62 +37,51 @@ enum
class AudioManager: public Audio::Player::EndOfAudioDelegate class AudioManager: public Audio::Player::EndOfAudioDelegate
{ {
public: public:
AudioManager(); AudioManager();
virtual ~AudioManager(); virtual ~AudioManager();
static AudioManager& instance();
// Enforces to close audio devices. Used to shutdown AudioManager on exit from application
void close();
// static AudioManager& instance(); // Terminal and settings must be available for AudioManager
void setTerminal(MT::Terminal* terminal);
MT::Terminal* terminal();
// Enforces to close audio devices. Used to shutdown AudioManager on exit from application // Start/stop methods relies on usage counter; only first start and last stop opens/closes devices actually
void close(); void start(int usageId);
void stop(int usageId);
enum AudioTarget
{
atNull,
atReceiver,
atRinger
};
// Terminal and settings must be available for AudioManager enum LoopMode
void setTerminal(MT::Terminal* terminal); {
MT::Terminal* terminal(); lmLoopAudio,
lmNoloop
};
void setAudioMonitoring(Audio::DataConnection* monitoring); void startPlayFile(int usageId, const std::string& path, AudioTarget target, LoopMode lm, int timelimit = 0);
Audio::DataConnection* audioMonitoring(); void stopPlayFile(int usageId);
// Start/stop methods relies on usage counter; only first start and last stop opens/closes devices actually void onFilePlayed(Audio::Player::PlaylistItem& item);
void start(int usageId);
void stop(int usageId); // Must be called from main loop to release used audio devices
void process();
// Inject a custom input device. Must be called before start(): when set,
// start() skips construction of the default platform microphone. Pass an
// empty pointer to clear the override.
void setAudioInput(Audio::PInputDevice input);
void setAudioOutput(Audio::POutputDevice output);
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: protected:
Audio::PInputDevice mAudioInput; Audio::PInputDevice mAudioInput;
Audio::POutputDevice mAudioOutput; Audio::POutputDevice mAudioOutput;
Audio::Player mPlayer; Audio::Player mPlayer;
MT::Terminal* mTerminal; MT::Terminal* mTerminal;
Audio::DataConnection* mAudioMonitoring;
std::map<int, int> UsageMap; std::map<int, int> UsageMap;
UsageCounter mUsage; UsageCounter mUsage;
std::mutex mGuard; std::mutex mGuard;
}; };
#endif #endif
+278 -179
View File
@@ -3,12 +3,17 @@
#include "helper/HL_String.h" #include "helper/HL_String.h"
#include "helper/HL_StreamState.h" #include "helper/HL_StreamState.h"
#include "helper/HL_VariantMap.h" #include "helper/HL_VariantMap.h"
// #include "helper/HL_CsvReader.h" #include "helper/HL_CsvReader.h"
// #include "helper/HL_Base64.h" #include "helper/HL_Base64.h"
#include "media/MT_CodecList.h" #include <fstream>
#include "audio/Audio_Null.h"
// #include <fstream>
#if defined(USE_PVQA_LIBRARY)
# include "pvqa++.h"
#endif
#if defined(USE_AQUA_LIBRARY)
# include "aqua++.h"
#endif
const std::string Status_Ok = "ok"; const std::string Status_Ok = "ok";
const std::string Status_SessionNotFound = "session not found"; const std::string Status_SessionNotFound = "session not found";
@@ -17,9 +22,8 @@ const std::string Status_FailedToOpenFile = "failed to open file";
const std::string Status_NoActiveProvider = "no active provider"; const std::string Status_NoActiveProvider = "no active provider";
const std::string Status_NoMediaAction = "no valid media action"; const std::string Status_NoMediaAction = "no valid media action";
const std::string Status_NoCommand = "no valid command"; const std::string Status_NoCommand = "no valid command";
const std::string Status_NoAudioManager = "no audio manager";
#define LOG_SUBSYSTEM "agent" #define LOG_SUBSYSTEM "Agent"
AgentImpl::AgentImpl() AgentImpl::AgentImpl()
:mShutdown(false), mEventListChangeCondVar() :mShutdown(false), mEventListChangeCondVar()
@@ -35,24 +39,6 @@ AgentImpl::~AgentImpl()
stopAgentAndThread(); 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() void AgentImpl::run()
{ {
while (!mShutdown) while (!mShutdown)
@@ -78,88 +64,82 @@ std::string AgentImpl::command(const std::string& command)
return ""; return "";
std::string cmd = d["command"].asString(); std::string cmd = d["command"].asString();
if (cmd != "wait_for_event" && cmd != "agent_add_root_cert") if (cmd != "wait_for_event")
{ {
ICELogInfo(<< command); ICELogInfo(<< command);
} }
if (cmd == "config") if (cmd == "config")
processConfig(d, answer); processConfig(d, answer);
else else
if (cmd == "start") if (cmd == "start")
processStart(d, answer); processStart(d, answer);
else else
if (cmd == "stop") if (cmd == "stop")
processStop(d, answer); processStop(d, answer);
else else
if (cmd == "account_create") if (cmd == "account_create")
processCreateAccount(d, answer); processCreateAccount(d, answer);
else else
if (cmd == "account_start") if (cmd == "account_start")
processStartAccount(d, answer); processStartAccount(d, answer);
else else
if (cmd == "account_setuserinfo") if (cmd == "account_setuserinfo")
processSetUserInfoToAccount(d, answer); processSetUserInfoToAccount(d, answer);
else else
if (cmd == "session_create") { if (cmd == "session_create")
// For Bugsnag test processCreateSession(d, answer);
// int* v = nullptr; else
// *v = 0; if (cmd == "session_start")
processCreateSession(d, answer); processStartSession(d, answer);
} else
else if (cmd == "session_stop")
if (cmd == "session_start") processStopSession(d, answer);
processStartSession(d, answer); else
else if (cmd == "session_accept")
if (cmd == "session_stop") processAcceptSession(d, answer);
processStopSession(d, answer); else
else if (cmd == "session_destroy")
if (cmd == "session_accept") processDestroySession(d, answer);
processAcceptSession(d, answer); else
else if (cmd == "session_use_stream")
if (cmd == "session_destroy") processUseStreamForSession(d, answer);
processDestroySession(d, answer); else
else if (cmd == "wait_for_event")
if (cmd == "session_use_stream") processWaitForEvent(d, answer);
processUseStreamForSession(d, answer); else
else if (cmd == "session_get_media_stats")
if (cmd == "wait_for_event") processGetMediaStats(d, answer);
processWaitForEvent(d, answer); else
else if (cmd == "agent_network_changed")
if (cmd == "session_get_media_stats") processNetworkChanged(d, answer);
processGetMediaStats(d, answer); else
else if (cmd == "agent_add_root_cert")
if (cmd == "agent_network_changed") processAddRootCert(d, answer);
processNetworkChanged(d, answer); else
else if (cmd == "detach_log")
if (cmd == "agent_add_root_cert") {
processAddRootCert(d, answer); GLogger.closeFile();
else answer["status"] = Status_Ok;
if (cmd == "detach_log") }
{ else
GLogger.closeFile(); if (cmd == "attach_log")
answer["status"] = Status_Ok; {
} GLogger.openFile();
else answer["status"] = Status_Ok;
if (cmd == "attach_log") }
{ else
GLogger.openFile(); if (cmd == "log_message")
answer["status"] = Status_Ok; processLogMessage(d, answer);
} else
else {
if (cmd == "log_message") answer["status"] = Status_NoCommand;
processLogMessage(d, answer); }
else
{
answer["status"] = Status_NoCommand;
}
} }
catch(std::exception& e) catch(std::exception& e)
{ {
answer["status"] = e.what(); answer["status"] = e.what();
} }
std::string result = answer.toStyledString(); return answer.toStyledString();
return result;
} }
bool AgentImpl::waitForData(int /*milliseconds*/) bool AgentImpl::waitForData(int /*milliseconds*/)
@@ -176,11 +156,28 @@ void AgentImpl::processConfig(JsonCpp::Value &d, JsonCpp::Value &answer)
{ {
std::unique_lock<std::recursive_mutex> l(mAgentMutex); std::unique_lock<std::recursive_mutex> l(mAgentMutex);
#if !defined(TARGET_ANDROID) && defined(USE_PVQA_LIBRARY)
// It works for desktop OSes only
// Because Android requires special initializing procedure (valid JNI environment context)
std::string pvqaLicense = d["pvqa-license"].asString(), pvqaConfig = d["pvqa-config"].asString();
if (!pvqaLicense.empty() && !pvqaConfig.empty())
sevana::pvqa::initialize(pvqaLicense, pvqaConfig);
#endif
#if !defined(TARGET_ANDROID) && defined(USE_AQUA_LIBRARY)
std::string aquaLicense = d["aqua-license"].asString();
if (!aquaLicense.empty())
sevana::aqua::initialize(aquaLicense.c_str(), aquaLicense.size());
#endif
std::string transport = d["transport"].asString(); 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_TRANSPORT] = (transport == "any") ? TransportType_Any : (transport == "udp" ? TransportType_Udp : (transport == "tcp" ? TransportType_Tcp : TransportType_Tls));
config()[CONFIG_IPV4] = d["ipv4"].asBool(); config()[CONFIG_IPV4] = d["ipv4"].asBool();
config()[CONFIG_IPV6] = d["ipv6"].asBool(); config()[CONFIG_IPV6] = d["ipv6"].asBool();
if (transport == "tls")
config()[CONFIG_SIPS] = true;
// Log file // Log file
std::string logfile = d["logfile"].asString(); std::string logfile = d["logfile"].asString();
ice::Logger& logger = ice::GLogger; ice::Logger& logger = ice::GLogger;
@@ -192,8 +189,7 @@ void AgentImpl::processConfig(JsonCpp::Value &d, JsonCpp::Value &answer)
mUseNativeAudio = d["nativeaudio"].asBool(); mUseNativeAudio = d["nativeaudio"].asBool();
config()[CONFIG_OWN_DNS] = d["dns_servers"].asString(); config()[CONFIG_OWN_DNS] = d["dns_servers"].asString();
config()[CONFIG_SIPS] = d["secure"].asBool() || transport == "tls"; config()[CONFIG_SIPS] = d["secure"].asBool();
config()[CONFIG_STUNSERVER_IP] = d["stun_server"].asString();
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
} }
@@ -214,8 +210,7 @@ void AgentImpl::processStart(JsonCpp::Value& request, JsonCpp::Value &answer)
SocketHeap::instance().start(); SocketHeap::instance().start();
// Initialize terminal // Initialize terminal
auto settings = MT::CodecList::Settings::getClientSettings(); MT::CodecList::Settings settings;
mTerminal = std::make_shared<MT::Terminal>(settings); mTerminal = std::make_shared<MT::Terminal>(settings);
// Enable/disable codecs // Enable/disable codecs
@@ -224,15 +219,19 @@ void AgentImpl::processStart(JsonCpp::Value& request, JsonCpp::Value &answer)
for (int i=0; i<cl.count(); i++) for (int i=0; i<cl.count(); i++)
priorityConfig->at(i) = 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; config()[CONFIG_CODEC_PRIORITY] = priorityConfig;
// Enable audio // Enable audio
mAudioManager = std::make_shared<AudioManager>(); mAudioManager = std::make_shared<AudioManager>();
mAudioManager->setTerminal(mTerminal.get()); mAudioManager->setTerminal(mTerminal.get());
if (mAudioMonitoring)
mAudioManager->setAudioMonitoring(mAudioMonitoring);
// Do not start audio manager here. Start right before call. // Do not start here. Start right before call.
// Initialize endpoint // Initialize endpoint
start(); start();
@@ -257,11 +256,9 @@ void AgentImpl::processCreateAccount(JsonCpp::Value &d, JsonCpp::Value& answer)
(*c)[CONFIG_USERNAME] = d["username"].asString(); (*c)[CONFIG_USERNAME] = d["username"].asString();
(*c)[CONFIG_PASSWORD] = d["password"].asString(); (*c)[CONFIG_PASSWORD] = d["password"].asString();
(*c)[CONFIG_DOMAIN] = d["domain"].asString(); (*c)[CONFIG_DOMAIN] = d["domain"].asString();
if (d.isMember("domain_port"))
(*c)[CONFIG_DOMAINPORT] = d["domain_port"].asInt();
(*c)[CONFIG_EXTERNALIP] = d["use_external_ip"].asBool(); (*c)[CONFIG_EXTERNALIP] = d["use_external_ip"].asBool();
auto nameAndPort = strx::parseHost(d["stun_server"].asString(), 3478); auto nameAndPort = StringHelper::parseHost(d["stun_server"].asString(), 3478);
(*c)[CONFIG_STUNSERVER_NAME] = nameAndPort.first; (*c)[CONFIG_STUNSERVER_NAME] = nameAndPort.first;
(*c)[CONFIG_STUNSERVER_PORT] = nameAndPort.second; (*c)[CONFIG_STUNSERVER_PORT] = nameAndPort.second;
@@ -329,15 +326,10 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
{ {
// Agent was not started // Agent was not started
ICELogError(<< "No audio manager installed."); ICELogError(<< "No audio manager installed.");
answer["status"] = Status_NoAudioManager; answer["status"] = "Audio manager not started. Most probably agent is not started.";
return; return;
} }
if (request["use_null_mic"].asBool())
mAudioManager->setAudioInput(std::make_shared<Audio::NullInputDevice>());
if (request["use_null_spk"].asBool())
mAudioManager->setAudioOutput(std::make_shared<Audio::NullOutputDevice>());
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull); mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
auto sessionIter = mSessionMap.find(request["session_id"].asInt()); auto sessionIter = mSessionMap.find(request["session_id"].asInt());
@@ -348,7 +340,7 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
PDataProvider audioProvider = std::make_shared<AudioProvider>(*this, *mTerminal); PDataProvider audioProvider = std::make_shared<AudioProvider>(*this, *mTerminal);
audioProvider->setState(audioProvider->state() | static_cast<int>(StreamState::Grabbing) | static_cast<int>(StreamState::Playing)); audioProvider->setState(audioProvider->state() | static_cast<int>(StreamState::Grabbing) | static_cast<int>(StreamState::Playing));
/*#if defined(USE_AQUA_LIBRARY) #if defined(USE_AQUA_LIBRARY)
std::string path_faults = request["path_faults"].asString(); std::string path_faults = request["path_faults"].asString();
sevana::aqua::config config = { sevana::aqua::config config = {
@@ -372,8 +364,8 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
}; };
// 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"; // 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()) /*if (temp_path.size())
// config += " -fau " + temp_path; config += " -fau " + temp_path; */
auto qc = std::make_shared<sevana::aqua>(); auto qc = std::make_shared<sevana::aqua>();
if (!qc->is_open()) if (!qc->is_open())
@@ -386,7 +378,6 @@ void AgentImpl::processStartSession(JsonCpp::Value& request, JsonCpp::Value& ans
mAquaMap[sessionIter->first] = qc; mAquaMap[sessionIter->first] = qc;
dynamic_cast<AudioProvider*>(audioProvider.get())->configureMediaObserver(this, (void*)qc.get()); dynamic_cast<AudioProvider*>(audioProvider.get())->configureMediaObserver(this, (void*)qc.get());
#endif #endif
*/
// TODO: support SRTP via StreamState::Srtp option in audio provider state // TODO: support SRTP via StreamState::Srtp option in audio provider state
@@ -429,39 +420,28 @@ void AgentImpl::processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& an
auto sessionIter = mSessionMap.find(request["session_id"].asInt()); auto sessionIter = mSessionMap.find(request["session_id"].asInt());
if (sessionIter != mSessionMap.end()) if (sessionIter != mSessionMap.end())
{ {
if (!mAudioManager) // Ensure audio manager is here
{ mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
ICELogError(<< "No audio manager installed.");
answer["status"] = Status_NoAudioManager;
}
else
{
// Ensure audio manager is here
if (request["use_null_mic"].asBool())
mAudioManager->setAudioInput(std::make_shared<Audio::NullInputDevice>());
if (request["use_null_spk"].asBool())
mAudioManager->setAudioOutput(std::make_shared<Audio::NullOutputDevice>());
mAudioManager->start(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
// Accept session on SIP level // Accept session on SIP level
PSession session = sessionIter->second; PSession session = sessionIter->second;
// Get user headers // Get user headers
Session::UserHeaders info; Session::UserHeaders info;
JsonCpp::Value& arg = request["userinfo"]; JsonCpp::Value& arg = request["userinfo"];
std::vector<std::string> keys = arg.getMemberNames(); std::vector<std::string> keys = arg.getMemberNames();
for (const std::string& k: keys) for (const std::string& k: keys)
info[k] = arg[k].asString(); info[k] = arg[k].asString();
session->setUserHeaders(info); session->setUserHeaders(info);
// Accept finally // Accept finally
session->accept(); session->accept();
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
}
} }
else else
answer["status"] = Status_SessionNotFound; answer["status"] = Status_SessionNotFound;
} }
void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer) void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer)
@@ -472,26 +452,23 @@ void AgentImpl::processDestroySession(JsonCpp::Value& request, JsonCpp::Value& a
auto sessionIter = mSessionMap.find(sessionId); auto sessionIter = mSessionMap.find(sessionId);
if (sessionIter != mSessionMap.end()) if (sessionIter != mSessionMap.end())
mSessionMap.erase(sessionIter); mSessionMap.erase(sessionIter);
//#if defined(USE_AQUA_LIBRARY) #if defined(USE_AQUA_LIBRARY)
// closeAqua(sessionId); closeAqua(sessionId);
//#endif #endif
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
} }
void AgentImpl::processWaitForEvent(JsonCpp::Value &request, JsonCpp::Value &answer) void AgentImpl::processWaitForEvent(JsonCpp::Value &request, JsonCpp::Value &answer)
{ {
// Deliberately does NOT take mAgentMutex: events are produced by the worker std::unique_lock<std::recursive_mutex> l(mAgentMutex);
// thread inside process(), which needs mAgentMutex. Holding it here would
// stall all SIP/media processing for the whole timeout and guarantee that
// the awaited event can never arrive during the wait.
int timeout = 0; //int x = 0;
if (request.isMember("timeout")) //int y = 1/x;
timeout = request["timeout"].asInt();
int timeout = request["timeout"].asInt();
std::unique_lock<std::mutex> eventLock(mEventListMutex); std::unique_lock<std::mutex> eventLock(mEventListMutex);
mEventListChangeCondVar.wait_for(eventLock, chrono::milliseconds(timeout), if (mEventList.empty())
[this]() { return !mEventList.empty(); }); mEventListChangeCondVar.wait_for(eventLock, chrono::milliseconds(timeout));
if (!mEventList.empty()) if (!mEventList.empty())
{ {
@@ -501,6 +478,41 @@ void AgentImpl::processWaitForEvent(JsonCpp::Value &request, JsonCpp::Value &ans
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
} }
#if defined(USE_PVQA_LIBRARY)
static JsonCpp::Value CsvReportToJson(const std::string& report)
{
JsonCpp::Value detectorValues;
std::istringstream iss(report);
CsvReader reader(iss);
std::vector<std::string> cells;
if (reader.readLine(cells))
{
JsonCpp::Value detectorNames;
for (size_t nameIndex = 0; nameIndex < cells.size(); nameIndex++)
detectorNames[static_cast<int>(nameIndex)] = StringHelper::trim(cells[nameIndex]);
// Put first line name of columns
detectorValues[0] = detectorNames;
int rowIndex = 1;
while (reader.readLine(cells))
{
// Skip last column for now
JsonCpp::Value row;
for (size_t valueIndex = 0; valueIndex < cells.size(); valueIndex++)
{
bool isFloat = true;
float v = StringHelper::toFloat(cells[valueIndex], 0.0, &isFloat);
if (isFloat)
row[static_cast<int>(valueIndex)] = static_cast<double>(v);
else
row[static_cast<int>(valueIndex)] = cells[valueIndex];
}
detectorValues[rowIndex++] = row;
}
}
return detectorValues;
}
#endif
void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer) void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer)
{ {
@@ -511,14 +523,25 @@ void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& an
{ {
PSession session = sessionIter->second; PSession session = sessionIter->second;
VariantMap result; VariantMap result;
session->getSessionInfo(Session::InfoOptions::Detailed, bool includePvqa = request["include_pvqa"].asBool();
#if defined(USE_AQUA_LIBRARY)
bool includeAqua = request["include_aqua"].asBool();
std::string aquaReference = request["aqua_reference_audio"].asString();
#endif
session->getSessionInfo(includePvqa ? Session::InfoOptions::Detailed : Session::InfoOptions::Standard,
result); result);
if (result.exists(SessionInfo_AudioCodec)) if (result.exists(SessionInfo_AudioCodec))
answer["codec"] = result[SessionInfo_AudioCodec].asStdString(); answer["codec"] = result[SessionInfo_AudioCodec].asStdString();
if (result.exists(SessionInfo_NetworkMos)) if (result.exists(SessionInfo_NetworkMos))
answer["network_mos"] = result[SessionInfo_NetworkMos].asFloat(); answer["network_mos"] = result[SessionInfo_NetworkMos].asFloat();
if (result.exists(SessionInfo_LostRtp)) #if defined(USE_PVQA_LIBRARY)
if (result.exists(SessionInfo_PvqaMos))
answer["pvqa_mos"] = result[SessionInfo_PvqaMos].asFloat();
if (result.exists(SessionInfo_PvqaReport))
answer["pvqa_report"] = CsvReportToJson(result[SessionInfo_PvqaReport].asStdString());
#endif
if (result.exists(SessionInfo_PacketLoss))
answer["rtp_lost"] = result[SessionInfo_LostRtp].asInt(); answer["rtp_lost"] = result[SessionInfo_LostRtp].asInt();
if (result.exists(SessionInfo_DroppedRtp)) if (result.exists(SessionInfo_DroppedRtp))
answer["rtp_dropped"] = result[SessionInfo_DroppedRtp].asInt(); answer["rtp_dropped"] = result[SessionInfo_DroppedRtp].asInt();
@@ -535,13 +558,79 @@ void AgentImpl::processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& an
answer["rtt"] = result[SessionInfo_Rtt].asFloat(); answer["rtt"] = result[SessionInfo_Rtt].asFloat();
if (result.exists(SessionInfo_BitrateSwitchCounter)) if (result.exists(SessionInfo_BitrateSwitchCounter))
answer["bitrate_switch_counter"] = result[SessionInfo_BitrateSwitchCounter].asInt(); answer["bitrate_switch_counter"] = result[SessionInfo_BitrateSwitchCounter].asInt();
if (result.exists(SessionInfo_CngCounter))
answer["cng_counter"] = result[SessionInfo_CngCounter].asInt();
if (result.exists(SessionInfo_SSRC)) if (result.exists(SessionInfo_SSRC))
answer["rtp_ssrc"] = result[SessionInfo_SSRC].asInt(); answer["rtp_ssrc"] = result[SessionInfo_SSRC].asInt();
if (result.exists(SessionInfo_RemotePeer)) if (result.exists(SessionInfo_RemotePeer))
answer["rtp_remotepeer"] = result[SessionInfo_RemotePeer].asStdString(); answer["rtp_remotepeer"] = result[SessionInfo_RemotePeer].asStdString();
#if defined(USE_AQUA_LIBRARY)
if (includeAqua)
{
answer["incoming_audio"] = mAquaIncoming.hexstring();
answer["incoming_audio_samplerate"] = AUDIO_SAMPLERATE;
answer["incoming_audio_channels"] = AUDIO_CHANNELS;
ICELogInfo(<< "Running AQuA analyzer.");
ByteBuffer referenceAudio;
// Read AQuA reference audio from file if available
if (aquaReference.empty())
{
ICELogCritical(<< "AQuA reference audio file is not set, skipping analyzing.");
}
else {
auto sa = mAquaMap[sessionIter->first];
if (sa) {
Audio::WavFileReader reader;
reader.open(StringHelper::makeTstring(aquaReference));
if (reader.isOpened()) {
char buffer[1024];
int wasRead = 0;
do {
wasRead = reader.read(buffer, 1024);
if (wasRead > 0)
referenceAudio.appendBuffer(buffer, wasRead);
} while (wasRead == 1024);
}
else {
ICELogCritical(<< "Failed to read AQuA reference audio, error code: " << reader.lastError());
}
sevana::aqua::audio_buffer test(mAquaIncoming.data(), mAquaIncoming.size()),
reference(referenceAudio.data(), referenceAudio.size());
test.mRate = AUDIO_SAMPLERATE;
reference.mRate = AUDIO_SAMPLERATE;
test.mChannels = AUDIO_CHANNELS;
reference.mChannels = AUDIO_CHANNELS;
ICELogInfo(
<< "Comparing test audio " << mAquaIncoming.size() << " bytes with reference audio " << referenceAudio.size() << " bytes.");
auto r = sa->compare(reference, test);
if (r.mErrorCode) {
ICELogInfo(
<< "Error code: " << r.mErrorCode << ", msg: " << r.mErrorMessage);
} else {
ICELogInfo(<< "MOS: " << r.mMos << ", faults: " << r.mFaultsText);
}
answer["aqua_mos"] = r.mMos;
answer["aqua_report"] = r.mFaultsText;
/*std::string aqua_audio_text;
if (Base64::Encode(std::string(reinterpret_cast<const char*>(mAquaIncoming.data()), mAquaIncoming.size()), &aqua_audio_text))
{
answer["aqua_audio"] = aqua_audio_text;
}*/
if (r.mErrorCode) {
answer["aqua_error_code"] = r.mErrorCode;
answer["aqua_error_message"] = r.mErrorMessage;
}
closeAqua(sessionIter->first);
}
}
// Remove test audio
mAquaIncoming.clear(); mAquaOutgoing.clear();
}
#endif
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
} }
else else
@@ -562,16 +651,18 @@ void AgentImpl::processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answ
std::string pem = request["cert"].asString(); std::string pem = request["cert"].asString();
std::string::size_type pb = 0, pe = 0; 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) { for(pb = pem.find(BeginCertificate, pb), pe = pem.find(EndCertificate, pe);
std::string cert = pem.substr(pb, pe - pb + EndCertificate.size()); pb != std::string::npos && pe != std::string::npos;
addRootCert(ByteBuffer(cert.c_str(), cert.size())); pb = pem.find(BeginCertificate, pb + BeginCertificate.size()), pe = pem.find(EndCertificate, pe + EndCertificate.size()))
{
// Get single certificate
std::string cert = pem.substr(pb, pe + EndCertificate.size());
//int size = cert.size();
addRootCert(ByteBuffer(cert.c_str(), cert.size()));
pb = ++pe; // Delete processed part
} pem.erase(0, pe + EndCertificate.size());
} }
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
@@ -638,7 +729,7 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val
// Parse command // Parse command
std::string actionText = request["media_action"].asString(), std::string actionText = request["media_action"].asString(),
directionText = request["media_direction"].asString(); directionText = request["media_direction"].asString();
MT::Stream::MediaDirection direction = directionText == "incoming" ? MT::Stream::MediaDirection::Incoming MT::Stream::MediaDirection direction = directionText == "incoming" ? MT::Stream::MediaDirection::Incoming
: MT::Stream::MediaDirection::Outgoing; : MT::Stream::MediaDirection::Outgoing;
@@ -659,7 +750,7 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val
else else
{ {
Audio::PWavFileReader reader = std::make_shared<Audio::WavFileReader>(); Audio::PWavFileReader reader = std::make_shared<Audio::WavFileReader>();
if (!reader->open(strx::makeTstring(path))) if (!reader->open(StringHelper::makeTstring(path)))
answer["status"] = Status_FailedToOpenFile; answer["status"] = Status_FailedToOpenFile;
else else
{ {
@@ -680,7 +771,7 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val
else else
{ {
Audio::PWavFileWriter writer = std::make_shared<Audio::WavFileWriter>(); Audio::PWavFileWriter writer = std::make_shared<Audio::WavFileWriter>();
if (!writer->open(strx::makeTstring(path), AUDIO_SAMPLERATE, AUDIO_CHANNELS)) if (!writer->open(StringHelper::makeTstring(path), AUDIO_SAMPLERATE, AUDIO_CHANNELS))
answer["status"] = Status_FailedToOpenFile; answer["status"] = Status_FailedToOpenFile;
else else
{ {
@@ -696,21 +787,32 @@ void AgentImpl::processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Val
answer["status"] = Status_Ok; answer["status"] = Status_Ok;
} }
else else
answer["status"] = Status_NoCommand; answer["status"] = Status_AccountNotFound;
} }
else else
answer["status"] = Status_NoMediaAction; answer["status"] = Status_NoMediaAction;
} }
} }
#if defined(USE_AQUA_LIBRARY)
void AgentImpl::onMedia(const void* data, int length, MT::Stream::MediaDirection direction, void* context, void* userTag) void AgentImpl::onMedia(const void* data, int length, MT::Stream::MediaDirection direction, void* context, void* userTag)
{ {
/*switch (direction) /* if (mIncomingAudioDump && direction == MT::Stream::MediaDirection::Incoming)
mIncomingAudioDump->write(data, length);
if (mOutgoingAudioDump && direction == MT::Stream::MediaDirection::Outgoing)
mOutgoingAudioDump->write(data, length);*/
// User tag points to accumulator object which includes
// auto* sa = reinterpret_cast<sevana::aqua*>(userTag);
switch (direction)
{ {
case MT::Stream::MediaDirection::Incoming: mAquaIncoming.appendBuffer(data, length); break; case MT::Stream::MediaDirection::Incoming: mAquaIncoming.appendBuffer(data, length); break;
case MT::Stream::MediaDirection::Outgoing: mAquaOutgoing.appendBuffer(data, length); break; case MT::Stream::MediaDirection::Outgoing: mAquaOutgoing.appendBuffer(data, length); break;
}*/ }
} }
#endif
// Called on new incoming session; providers shoukld // Called on new incoming session; providers shoukld
@@ -718,13 +820,11 @@ void AgentImpl::onMedia(const void* data, int length, MT::Stream::MediaDirection
PDataProvider AgentImpl::onProviderNeeded(const std::string& name) PDataProvider AgentImpl::onProviderNeeded(const std::string& name)
{ {
assert(mTerminal);
EVENT_WITH_NAME("provider_needed"); EVENT_WITH_NAME("provider_needed");
v["provider_name"] = name; v["provider_name"] = name;
addEvent(v); addEvent(v);
return std::make_shared<AudioProvider>(*this, *mTerminal); return PDataProvider(new AudioProvider(*this, *mTerminal));
} }
// Called on new session offer // Called on new session offer
@@ -746,8 +846,7 @@ void AgentImpl::onSessionTerminated(PSession s, int responsecode, int reason)
if (mOutgoingAudioDump) if (mOutgoingAudioDump)
mOutgoingAudioDump->close(); mOutgoingAudioDump->close();
*/ */
if (mAudioManager) mAudioManager->stop(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
mAudioManager->stop(mUseNativeAudio ? AudioManager::atReceiver : AudioManager::atNull);
// Gather statistics before // Gather statistics before
EVENT_WITH_NAME("session_terminated"); EVENT_WITH_NAME("session_terminated");
v["session_id"] = s->id(); v["session_id"] = s->id();
@@ -870,12 +969,12 @@ void AgentImpl::addEvent(const JsonCpp::Value& v)
} }
#if defined(USE_AQUA_LIBRARY) #if defined(USE_AQUA_LIBRARY)
/*void AgentImpl::closeAqua(int sessionId) void AgentImpl::closeAqua(int sessionId)
{ {
auto aquaIter = mAquaMap.find(sessionId); auto aquaIter = mAquaMap.find(sessionId);
if (aquaIter != mAquaMap.end()) { if (aquaIter != mAquaMap.end()) {
aquaIter->second->close(); aquaIter->second->close();
mAquaMap.erase(aquaIter); mAquaMap.erase(aquaIter);
} }
}*/ }
#endif #endif
+93 -82
View File
@@ -13,117 +13,128 @@
#include "Agent_AudioManager.h" #include "Agent_AudioManager.h"
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
#include <atomic> #if defined(USE_AQUA_LIBRARY)
# include "aqua++.h"
#endif
#if defined(USE_PVQA_LIBRARY)
# include "pvqa++.h"
#endif
class AgentImpl: public UserAgent, public MT::Stream::MediaObserver class AgentImpl: public UserAgent
#if defined(USE_AQUA_LIBRARY)
, public MT::Stream::MediaObserver
#endif
{ {
protected: protected:
std::recursive_mutex mAgentMutex; std::recursive_mutex mAgentMutex;
std::mutex mEventListMutex; std::mutex mEventListMutex;
std::condition_variable mEventListChangeCondVar; std::condition_variable mEventListChangeCondVar;
std::vector<JsonCpp::Value> mEventList; std::vector<JsonCpp::Value> mEventList;
bool mUseNativeAudio = false; bool mUseNativeAudio = false;
typedef std::map<int, PAccount> AccountMap; typedef std::map<int, PAccount> AccountMap;
AccountMap mAccountMap; AccountMap mAccountMap;
typedef std::map<int, PSession> SessionMap; typedef std::map<int, PSession> SessionMap;
SessionMap mSessionMap; SessionMap mSessionMap;
#if defined(USE_AQUA_LIBRARY)
// Keys are the same as used in mSessionMap
typedef std::map<int, std::shared_ptr<sevana::aqua>> AquaMap;
AquaMap mAquaMap;
ByteBuffer mAquaIncoming, mAquaOutgoing;
void closeAqua(int sessionId);
#endif
std::shared_ptr<std::thread> mThread; std::shared_ptr<std::thread> mThread;
std::atomic<bool> mShutdown; volatile bool mShutdown;
std::shared_ptr<MT::Terminal> mTerminal; std::shared_ptr<MT::Terminal> mTerminal;
std::shared_ptr<AudioManager> mAudioManager; std::shared_ptr<AudioManager> mAudioManager;
Audio::DataConnection* mAudioMonitoring = nullptr; //Audio::PWavFileWriter mIncomingAudioDump, mOutgoingAudioDump;
void run(); void run();
void addEvent(const JsonCpp::Value& v); void addEvent(const JsonCpp::Value& v);
void processConfig(JsonCpp::Value& request, JsonCpp::Value& answer); void processConfig(JsonCpp::Value& request, JsonCpp::Value& answer);
void processStart(JsonCpp::Value& request, JsonCpp::Value& answer); void processStart(JsonCpp::Value& request, JsonCpp::Value& answer);
void processStop(JsonCpp::Value& request, JsonCpp::Value& answer); void processStop(JsonCpp::Value& request, JsonCpp::Value& answer);
void processCreateAccount(JsonCpp::Value& request, JsonCpp::Value& answer); void processCreateAccount(JsonCpp::Value& request, JsonCpp::Value& answer);
void processStartAccount(JsonCpp::Value& request, JsonCpp::Value& answer); void processStartAccount(JsonCpp::Value& request, JsonCpp::Value& answer);
void processSetUserInfoToAccount(JsonCpp::Value& request, JsonCpp::Value& answer); void processSetUserInfoToAccount(JsonCpp::Value& request, JsonCpp::Value& answer);
void processCreateSession(JsonCpp::Value& request, JsonCpp::Value& answer); void processCreateSession(JsonCpp::Value& request, JsonCpp::Value& answer);
void processStartSession(JsonCpp::Value& request, JsonCpp::Value& answer); void processStartSession(JsonCpp::Value& request, JsonCpp::Value& answer);
void processStopSession(JsonCpp::Value& request, JsonCpp::Value& answer); void processStopSession(JsonCpp::Value& request, JsonCpp::Value& answer);
void processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& answer); void processAcceptSession(JsonCpp::Value& request, JsonCpp::Value& answer);
void processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer); void processDestroySession(JsonCpp::Value& request, JsonCpp::Value& answer);
void processWaitForEvent(JsonCpp::Value& request, JsonCpp::Value& answer); void processWaitForEvent(JsonCpp::Value& request, JsonCpp::Value& answer);
void processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer); void processGetMediaStats(JsonCpp::Value& request, JsonCpp::Value& answer);
void processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Value& answer); void processUseStreamForSession(JsonCpp::Value& request, JsonCpp::Value& answer);
void processNetworkChanged(JsonCpp::Value& request, JsonCpp::Value& answer); void processNetworkChanged(JsonCpp::Value& request, JsonCpp::Value& answer);
void processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answer); void processAddRootCert(JsonCpp::Value& request, JsonCpp::Value& answer);
void processLogMessage(JsonCpp::Value& request, JsonCpp::Value& answer); void processLogMessage(JsonCpp::Value& request, JsonCpp::Value& answer);
void stopAgentAndThread(); void stopAgentAndThread();
public: public:
AgentImpl(); AgentImpl();
~AgentImpl(); ~AgentImpl();
std::string command(const std::string& command); std::string command(const std::string& command);
bool waitForData(int milliseconds); bool waitForData(int milliseconds);
std::string read(); std::string read();
// Get access to internal audio manager. Value can be nullptr. // UserAgent overrides
const std::shared_ptr<AudioManager>& audioManager() const; // Called on new incoming session; providers shoukld
PDataProvider onProviderNeeded(const std::string& name) override;
void setAudioMonitoring(Audio::DataConnection* monitoring); // Called on new session offer
Audio::DataConnection* monitoring() const; void onNewSession(PSession s) override;
// UserAgent overrides // Called when session is terminated
// Called on new incoming session; providers shoukld void onSessionTerminated(PSession s, int responsecode, int reason) override;
PDataProvider onProviderNeeded(const std::string& name) override;
// Called on new session offer // Called when session is established ok i.e. after all ICE signalling is finished
void onNewSession(PSession s) override; // Conntype is type of establish event - EV_SIP or EV_ICE
void onSessionEstablished(PSession s, int conntype, const RtpPair<InternetAddress>& p) override;
// Called when session is terminated void onSessionProvisional(PSession s, int code) override;
void onSessionTerminated(PSession s, int responsecode, int reason) override;
// Called when session is established ok i.e. after all ICE signalling is finished // Called when user agent started
// Conntype is type of establish event - EV_SIP or EV_ICE void onStart(int errorcode) override;
void onSessionEstablished(PSession s, int conntype, const RtpPair<InternetAddress>& p) override;
void onSessionProvisional(PSession s, int code) override; // Called when user agent stopped
void onStop() override;
// Called when user agent started // Called when account registered
void onStart(int errorcode) override; void onAccountStart(PAccount account) override;
// Called when user agent stopped // Called when account removed or failed (non zero error code)
void onStop() override; void onAccountStop(PAccount account, int error) override;
// Called when account registered // Called when connectivity checks failed.
void onAccountStart(PAccount account) override; void onConnectivityFailed(PSession s) override;
// Called when account removed or failed (non zero error code) // Called when new candidate is gathered
void onAccountStop(PAccount account, int error) override; void onCandidateGathered(PSession s, const char* address) override;
// Called when connectivity checks failed. // Called when network change detected
void onConnectivityFailed(PSession s) override; void onNetworkChange(PSession s) override;
// Called when new candidate is gathered // Called when all candidates are gathered
void onCandidateGathered(PSession s, const char* address) override; void onGathered(PSession s) override;
// Called when network change detected // Called when new connectivity check is finished
void onNetworkChange(PSession s) override; void onCheckFinished(PSession s, const char* description) override;
// Called when all candidates are gathered // Called when log message must be recorded
void onGathered(PSession s) override; void onLog(const char* msg) override;
// Called when new connectivity check is finished // Called when problem with SIP connection(s) detected
void onCheckFinished(PSession s, const char* description) override; void onSipConnectionFailed() override;
// Called when log message must be recorded #if defined(USE_AQUA_LIBRARY)
void onLog(const char* msg) 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;
// Called when problem with SIP connection(s) detected #endif
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 #endif
+2 -3
View File
@@ -365,9 +365,8 @@ void AndroidInputDevice::DeviceCallback(SLAndroidSimpleBufferQueueItf bq, void *
// ------------ AndroidOutputDevice ----------------- // ------------ AndroidOutputDevice -----------------
AndroidOutputDevice::AndroidOutputDevice(int devId) AndroidOutputDevice::AndroidOutputDevice(int devId)
{ {
ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << strx::toHex(this)); ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << StringHelper::toHex(this));
} }
AndroidOutputDevice::~AndroidOutputDevice() AndroidOutputDevice::~AndroidOutputDevice()
{ {
ICELogDebug(<< "Deleting AndroidOutputDevice."); ICELogDebug(<< "Deleting AndroidOutputDevice.");
@@ -596,4 +595,4 @@ void AndroidOutputDevice::DeviceCallback(SLAndroidSimpleBufferQueueItf bq, void*
{} {}
} }
#endif // TARGET_ANDROID #endif // TARGET_ANDROID
+3 -3
View File
@@ -10,7 +10,7 @@
#ifdef TARGET_ANDROID #ifdef TARGET_ANDROID
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "Audio"
using namespace Audio; using namespace Audio;
@@ -136,7 +136,7 @@ int AndroidInputDevice::readBuffer(void* buffer)
// ------------ AndroidOutputDevice ----------------- // ------------ AndroidOutputDevice -----------------
AndroidOutputDevice::AndroidOutputDevice(int devId) AndroidOutputDevice::AndroidOutputDevice(int devId)
{ {
ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << strx::toHex(this)); ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << StringHelper::toHex(this));
} }
AndroidOutputDevice::~AndroidOutputDevice() AndroidOutputDevice::~AndroidOutputDevice()
{ {
@@ -242,4 +242,4 @@ void AndroidOutputDevice::onErrorAfterClose(oboe::AudioStream *stream, oboe::Res
// soundEngine.restart(); // please check oboe samples for soundEngine.restart(); call // soundEngine.restart(); // please check oboe samples for soundEngine.restart(); call
} }
} }
#endif // TARGET_ANDROID #endif // TARGET_ANDROID
+1 -1
View File
@@ -13,7 +13,7 @@
using namespace Audio; using namespace Audio;
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "CoreAudio"
enum enum
{ {
+103 -135
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2018 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -9,204 +9,172 @@
using namespace Audio; using namespace Audio;
DataWindow::DataWindow() DataWindow::DataWindow()
{} {
mFilled = 0;
mData = NULL;
mCapacity = 0;
}
DataWindow::~DataWindow() DataWindow::~DataWindow()
{ {
if (mData) if (mData)
{ free(mData);
free(mData);
mData = nullptr;
}
} }
void DataWindow::setCapacity(size_t capacity) void DataWindow::setCapacity(int capacity)
{ {
Lock l(mMutex); Lock l(mMutex);
int tail = capacity - mCapacity;
// The window only ever grows; a smaller request keeps the current buffer. mData = (char*)realloc(mData, capacity);
if (capacity <= mCapacity) if (tail > 0)
return; memset(mData + mCapacity, 0, tail);
mCapacity = capacity;
size_t tail = capacity - mCapacity;
char* buffer = mData;
mData = (char*)realloc(mData, capacity);
if (!mData)
{
// Realloc failed
mData = buffer;
throw std::bad_alloc();
}
if (tail > 0)
memset(mData + mCapacity, 0, tail);
mCapacity = capacity;
} }
void DataWindow::addZero(size_t length) void DataWindow::addZero(int length)
{ {
Lock l(mMutex); Lock l(mMutex);
if (length > mCapacity) if (length > mCapacity)
length = mCapacity; length = mCapacity;
int avail = mCapacity - mFilled; int avail = mCapacity - mFilled;
if (avail < length) if (avail < length)
{ {
memmove(mData, mData + length - avail, mFilled - (length - avail)); memmove(mData, mData + length - avail, mFilled - (length - avail));
mFilled -= length - avail; mFilled -= length - avail;
} }
memset(mData + mFilled, 0, length); memset(mData + mFilled, 0, length);
mFilled += length; mFilled += length;
} }
void DataWindow::add(const void* data, size_t length) void DataWindow::add(const void* data, int length)
{ {
Lock l(mMutex); Lock l(mMutex);
if (length > mCapacity)
{
// Use latest bytes from data buffer in this case.
data = (char*)data + length - mCapacity;
length = mCapacity;
}
if (length > mCapacity) // Check how much free space we have
{ int avail = mCapacity - mFilled;
// Use latest bytes from data buffer in this case.
data = (char*)data + length - mCapacity;
length = mCapacity;
}
// Check how much free space we have if (avail < length)
int avail = mCapacity - mFilled; {
// Find the portion of data to move & save
int delta = length - avail;
if (avail < length) // Move the data
{ if (mFilled - delta > 0)
// Find the portion of data to move & save memmove(mData, mData + delta, mFilled - delta);
int delta = length - avail; mFilled -= delta;
}
// Move the data memcpy(mData + mFilled, data, length);
if (mFilled - delta > 0) mFilled += length;
memmove(mData, mData + delta, mFilled - delta);
mFilled -= delta;
}
memcpy(mData + mFilled, data, length);
mFilled += length;
} }
void DataWindow::add(short sample) void DataWindow::add(short sample)
{ {
add(&sample, sizeof sample); add(&sample, sizeof sample);
} }
void DataWindow::erase(size_t length) void DataWindow::erase(int length)
{ {
Lock l(mMutex); Lock l(mMutex);
if (length > mFilled) if (length > mFilled)
length = mFilled; length = mFilled;
if (length != mFilled) if (length != mFilled)
memmove(mData, mData + length, mFilled - length); memmove(mData, mData + length, mFilled - length);
mFilled -= length; mFilled -= length;
} }
const char* DataWindow::data() const const char* DataWindow::data() const
{ {
return mData; return mData;
} }
char* DataWindow::mutableData() char* DataWindow::mutableData()
{ {
return mData; return mData;
} }
void DataWindow::clear() void DataWindow::clear()
{ {
Lock l(mMutex); Lock l(mMutex);
mFilled = 0; mFilled = 0;
} }
short DataWindow::shortAt(size_t index) const short DataWindow::shortAt(int index) const
{ {
Lock l(mMutex); Lock l(mMutex);
assert(index < mFilled / 2); assert(index < mFilled / 2);
return ((short*)mData)[index]; return ((short*)mData)[index];
} }
void DataWindow::setShortAt(short value, size_t index) void DataWindow::setShortAt(short value, int index)
{ {
Lock l(mMutex); Lock l(mMutex);
assert(index < mFilled / 2); assert(index < mFilled / 2);
((short*)mData)[index] = value; ((short*)mData)[index] = value;
} }
size_t DataWindow::read(void* buffer, size_t length) int DataWindow::read(void* buffer, int length)
{ {
Lock l(mMutex); Lock l(mMutex);
if (length > mFilled) if (length > mFilled)
length = mFilled; length = mFilled;
if (length) if (length)
{ {
if (buffer) if (buffer)
memcpy(buffer, mData, length); memcpy(buffer, mData, length);
if (length < mFilled) if (length < mFilled)
memmove(mData, mData+length, mFilled - length); memmove(mData, mData+length, mFilled - length);
mFilled -= length; mFilled -= length;
} }
return length; return length;
} }
size_t DataWindow::filled() const int DataWindow::filled() const
{ {
Lock l(mMutex); Lock l(mMutex);
return mFilled; return mFilled;
} }
void DataWindow::setFilled(size_t filled) void DataWindow::setFilled(int filled)
{ {
Lock l(mMutex); Lock l(mMutex);
if (filled > mCapacity) mFilled = filled;
throw std::bad_alloc();
mFilled = filled;
} }
size_t DataWindow::capacity() const int DataWindow::capacity() const
{ {
Lock l(mMutex); Lock l(mMutex);
return mCapacity; return mCapacity;
} }
void DataWindow::zero(size_t length) void DataWindow::zero(int length)
{ {
Lock l(mMutex); Lock l(mMutex);
assert(length <= mCapacity); assert(length <= mCapacity);
mFilled = length; mFilled = length;
memset(mData, 0, mFilled); memset(mData, 0, mFilled);
}
size_t DataWindow::moveTo(DataWindow& dst, size_t size)
{
Lock l(mMutex);
size_t avail = std::min(size, (size_t)filled());
if (avail != 0)
{
dst.add(mData, avail);
erase(avail);
}
return avail;
}
std::chrono::milliseconds DataWindow::getTimeLength(const Audio::Format& fmt) const
{
Lock l(mMutex);
return std::chrono::milliseconds(mFilled / sizeof(short) / fmt.channels() / (fmt.rate()/ 1000));
} }
void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src) void DataWindow::makeStereoFromMono(DataWindow& dst, DataWindow& src)
{ {
Lock lockDst(dst.mMutex), lockSrc(src.mMutex); Lock lockDst(dst.mMutex), lockSrc(src.mMutex);
dst.setCapacity(src.filled()*2); dst.setCapacity(src.filled()*2);
short* input = (short*)src.mutableData(); short* input = (short*)src.mutableData();
short* output = (short*)dst.mutableData(); short* output = (short*)dst.mutableData();
for (int i=0; i<src.filled()/2; i++) for (int i=0; i<src.filled()/2; i++)
output[i*2] = output[i*2+1] = input[i]; output[i*2] = output[i*2+1] = input[i];
dst.mFilled = src.filled() * 2; dst.mFilled = src.filled() * 2;
} }
+22 -26
View File
@@ -8,44 +8,40 @@
#include "../helper/HL_ByteBuffer.h" #include "../helper/HL_ByteBuffer.h"
#include "../helper/HL_Sync.h" #include "../helper/HL_Sync.h"
#include "Audio_Interface.h"
namespace Audio namespace Audio
{ {
class DataWindow class DataWindow
{ {
public: public:
DataWindow(); DataWindow();
~DataWindow(); ~DataWindow();
void setCapacity(size_t capacity); void setCapacity(int capacity);
size_t capacity() const; int capacity() const;
void addZero(size_t length); void addZero(int length);
void add(const void* data, size_t length); void add(const void* data, int length);
void add(short sample); void add(short sample);
size_t read(void* buffer, size_t length); int read(void* buffer, int length);
void erase(size_t length); void erase(int length = -1);
const char* data() const; const char* data() const;
char* mutableData(); char* mutableData();
size_t filled() const; int filled() const;
void setFilled(size_t filled); void setFilled(int filled);
void clear(); void clear();
short shortAt(size_t index) const; short shortAt(int index) const;
void setShortAt(short value, size_t index); void setShortAt(short value, int index);
void zero(size_t length); void zero(int length);
size_t moveTo(DataWindow& dst, size_t size /* in bytes*/ );
std::chrono::milliseconds getTimeLength(const Format& fmt) const;
static void makeStereoFromMono(DataWindow& dst, DataWindow& src); static void makeStereoFromMono(DataWindow& dst, DataWindow& src);
protected: protected:
mutable Mutex mMutex; mutable Mutex mMutex;
char* mData = nullptr; char* mData;
size_t mFilled = 0; int mFilled;
size_t mCapacity = 0; int mCapacity;
}; };
} }
#endif #endif
+16 -66
View File
@@ -1,20 +1,20 @@
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#define NOMINMAX
#include "Audio_DevicePair.h" #include "Audio_DevicePair.h"
#include <algorithm> #include <algorithm>
#include <assert.h> #include <assert.h>
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "Audio"
using namespace Audio; using namespace Audio;
// --- DevicePair --- // --- DevicePair ---
DevicePair::DevicePair() DevicePair::DevicePair(bool aec, bool agc)
:mConfig(nullptr), mDelegate(nullptr), mAec(false), mAgc(false), mAecFilter(AUDIO_MIC_BUFFER_LENGTH*10, AUDIO_MIC_BUFFER_LENGTH, AUDIO_SAMPLERATE), mAgcFilter(AUDIO_CHANNELS), :mConfig(NULL), mDelegate(NULL), mAec(aec), mAgc(agc), 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)); mInputBuffer.setCapacity(AUDIO_MIC_BUFFER_SIZE * (AUDIO_MIC_BUFFER_COUNT + 1));
mOutputBuffer.setCapacity(AUDIO_SPK_BUFFER_SIZE * (AUDIO_SPK_BUFFER_COUNT + 1)); mOutputBuffer.setCapacity(AUDIO_SPK_BUFFER_SIZE * (AUDIO_SPK_BUFFER_COUNT + 1));
@@ -28,50 +28,26 @@ DevicePair::~DevicePair()
if (mInput) if (mInput)
{ {
if (mInput->connection() == this) if (mInput->connection() == this)
mInput->setConnection(nullptr); mInput->setConnection(NULL);
mInput.reset(); mInput.reset();
} }
if (mOutput) if (mOutput)
{ {
if (mOutput->connection() == this) if (mOutput->connection() == this)
mOutput->setConnection(nullptr); mOutput->setConnection(NULL);
mOutput.reset(); 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() VariantMap* DevicePair::config()
{ {
return mConfig; return mConfig;
} }
DevicePair& DevicePair::setConfig(VariantMap* config) void DevicePair::setConfig(VariantMap* config)
{ {
mConfig = config; mConfig = config;
return *this;
} }
PInputDevice DevicePair::input() PInputDevice DevicePair::input()
@@ -79,17 +55,15 @@ PInputDevice DevicePair::input()
return mInput; return mInput;
} }
DevicePair& DevicePair::setInput(PInputDevice input) void DevicePair::setInput(PInputDevice input)
{ {
if (mInput == input) if (mInput == input)
return *this; return;
mInput = input; mInput = input;
mInput->setConnection(this); mInput->setConnection(this);
if (mDelegate) if (mDelegate)
mDelegate->deviceChanged(this); mDelegate->deviceChanged(this);
return *this;
} }
POutputDevice DevicePair::output() POutputDevice DevicePair::output()
@@ -97,17 +71,14 @@ POutputDevice DevicePair::output()
return mOutput; return mOutput;
} }
DevicePair& DevicePair::setOutput(POutputDevice output) void DevicePair::setOutput(POutputDevice output)
{ {
if (output == mOutput) if (output == mOutput)
return *this; return;
mOutput = output; mOutput = output;
mOutput->setConnection(this); mOutput->setConnection(this);
if (mDelegate) if (mDelegate)
mDelegate->deviceChanged(this); mDelegate->deviceChanged(this);
return *this;
} }
bool DevicePair::start() bool DevicePair::start()
@@ -117,7 +88,6 @@ bool DevicePair::start()
result = mInput->open(); result = mInput->open();
if (mOutput && result) if (mOutput && result)
result &= mOutput->open(); result &= mOutput->open();
return result; return result;
} }
@@ -129,10 +99,9 @@ void DevicePair::stop()
mOutput->close(); mOutput->close();
} }
DevicePair& DevicePair::setDelegate(Delegate* dc) void DevicePair::setDelegate(Delegate* dc)
{ {
mDelegate = dc; mDelegate = dc;
return *this;
} }
DevicePair::Delegate* DevicePair::delegate() DevicePair::Delegate* DevicePair::delegate()
@@ -140,17 +109,6 @@ DevicePair::Delegate* DevicePair::delegate()
return mDelegate; return mDelegate;
} }
DevicePair& DevicePair::setMonitoring(DataConnection* monitoring)
{
mMonitoring = monitoring;
return *this;
}
DataConnection* DevicePair::monitoring()
{
return mMonitoring;
}
Player& DevicePair::player() Player& DevicePair::player()
{ {
return mPlayer; return mPlayer;
@@ -227,7 +185,6 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length)
{ {
memset(mOutput10msBuffer.mutableData(), 0, (size_t)mOutput10msBuffer.capacity()); memset(mOutput10msBuffer.mutableData(), 0, (size_t)mOutput10msBuffer.capacity());
// Ask audio data on main AUDIO_SAMPLERATE frequency
if (mDelegate) if (mDelegate)
mDelegate->onSpkData(Format(), mOutput10msBuffer.mutableData(), mOutput10msBuffer.capacity()); mDelegate->onSpkData(Format(), mOutput10msBuffer.mutableData(), mOutput10msBuffer.capacity());
@@ -240,12 +197,8 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length)
// Resample these 10 milliseconds it to native format // Resample these 10 milliseconds it to native format
size_t wasProcessed = 0; size_t wasProcessed = 0;
size_t wasProduced = mSpkResampler.resample(Format().mRate, size_t wasProduced = mSpkResampler.resample(nativeFormat.mRate, mOutput10msBuffer.data(), mOutput10msBuffer.capacity(), wasProcessed, f.mRate,
mOutput10msBuffer.data(), mOutputNativeData.mutableData() + mOutputNativeData.filled(), mOutputNativeData.capacity() - mOutputNativeData.filled());
mOutput10msBuffer.capacity(),
wasProcessed, f.mRate,
mOutputNativeData.mutableData() + mOutputNativeData.filled(),
mOutputNativeData.capacity() - mOutputNativeData.filled());
mOutputNativeData.setFilled(mOutputNativeData.filled() + wasProduced); mOutputNativeData.setFilled(mOutputNativeData.filled() + wasProduced);
#ifdef CONSOLE_LOGGING #ifdef CONSOLE_LOGGING
printf("Resampled %d to %d\n", wasProcessed, wasProduced); printf("Resampled %d to %d\n", wasProcessed, wasProduced);
@@ -261,10 +214,6 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length)
mOutputNativeData.read(buffer, length); 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)) #define AEC_FRAME_SIZE (AUDIO_CHANNELS * (AUDIO_SAMPLERATE / 1000) * AEC_FRAME_TIME * sizeof(short))
// AEC filter wants frames. // AEC filter wants frames.
@@ -275,6 +224,7 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length)
mAecFilter.toSpeaker(mAecSpkBuffer.mutableData() + AEC_FRAME_SIZE * frameIndex); mAecFilter.toSpeaker(mAecSpkBuffer.mutableData() + AEC_FRAME_SIZE * frameIndex);
mAecSpkBuffer.erase(nrOfFrames * AEC_FRAME_SIZE); mAecSpkBuffer.erase(nrOfFrames * AEC_FRAME_SIZE);
} }
//ICELogMedia(<< "Audio::DevicePair::onSpkData() end")
} }
void DevicePair::processMicData(const Format& f, void* buffer, int length) void DevicePair::processMicData(const Format& f, void* buffer, int length)
+7 -11
View File
@@ -26,32 +26,29 @@ namespace Audio
virtual void deviceChanged(DevicePair* dpair) = 0; virtual void deviceChanged(DevicePair* dpair) = 0;
}; };
DevicePair(); DevicePair(bool aec = true, bool agc = true);
virtual ~DevicePair(); virtual ~DevicePair();
DevicePair& setAec(bool aec); void setAec(bool aec);
bool aec(); bool aec();
DevicePair& setAgc(bool agc); void setAgc(bool agc);
bool agc(); bool agc();
VariantMap* config(); VariantMap* config();
DevicePair& setConfig(VariantMap* config); void setConfig(VariantMap* config);
PInputDevice input(); PInputDevice input();
DevicePair& setInput(PInputDevice input); void setInput(PInputDevice input);
POutputDevice output(); POutputDevice output();
DevicePair& setOutput(POutputDevice output); void setOutput(POutputDevice output);
bool start(); bool start();
void stop(); void stop();
DevicePair& setDelegate(Delegate* dc); void setDelegate(Delegate* dc);
Delegate* delegate(); Delegate* delegate();
DevicePair& setMonitoring(DataConnection* monitoring);
DataConnection* monitoring();
Player& player(); Player& player();
protected: protected:
@@ -66,7 +63,6 @@ namespace Audio
Player mPlayer; Player mPlayer;
UniversalResampler mMicResampler, mSpkResampler; UniversalResampler mMicResampler, mSpkResampler;
DataWindow mInputBuffer, mOutputBuffer, mAecSpkBuffer, mInputResampingData, mOutputNativeData, mOutput10msBuffer; DataWindow mInputBuffer, mOutputBuffer, mAecSpkBuffer, mInputResampingData, mOutputNativeData, mOutput10msBuffer;
DataConnection* mMonitoring;
#ifdef DUMP_NATIVEOUTPUT #ifdef DUMP_NATIVEOUTPUT
std::shared_ptr<WavFileWriter> mNativeOutputDump; std::shared_ptr<WavFileWriter> mNativeOutputDump;
+1 -1
View File
@@ -22,7 +22,7 @@
#define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17) #define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17)
#define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18) #define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18)
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "DirectSound"
using namespace Audio; using namespace Audio;
+2 -2
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2025 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -6,7 +6,7 @@
#ifndef __AUDIO_DSOUND_H #ifndef __AUDIO_DSOUND_H
#define __AUDIO_DSOUND_H #define __AUDIO_DSOUND_H
#include "../engine_config.h" #include "../config.h"
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
+5 -2
View File
@@ -29,6 +29,10 @@ TimeSource::TimeSource(int quantTime, int nrOfQuants)
mTailTime = 0; mTailTime = 0;
} }
TimeSource::~TimeSource()
{
}
void TimeSource::start() void TimeSource::start()
{ {
#ifdef TARGET_WIN #ifdef TARGET_WIN
@@ -98,12 +102,11 @@ unsigned TimeSource::time()
#if defined(TARGET_ANDROID) #if defined(TARGET_ANDROID)
assert(0); assert(0);
#endif #endif
return 0;
} }
// --- StubTimer --- // --- StubTimer ---
StubTimer::StubTimer(int bufferTime, int bufferCount) StubTimer::StubTimer(int bufferTime, int bufferCount)
:mBufferTime(bufferTime), mBufferCount(bufferCount), mTimeSource(bufferTime, bufferCount), mActive(false), mCurrentTime(0) :mBufferTime(bufferTime), mBufferCount(bufferCount), mTimeSource(bufferTime, bufferCount), mActive(false)
{ {
#ifdef TARGET_WIN #ifdef TARGET_WIN
mStubSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL); mStubSignal = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+1 -1
View File
@@ -47,7 +47,7 @@ namespace Audio
public: public:
TimeSource(int quantTime, int nrOfQuants); TimeSource(int quantTime, int nrOfQuants);
~TimeSource() = default; ~TimeSource();
void start(); void start();
void stop(); void stop();
+43 -69
View File
@@ -7,7 +7,7 @@
#define __AUDIO_INTERFACE_H #define __AUDIO_INTERFACE_H
#include <string> #include <string>
#include "../engine_config.h" #include "../config.h"
#include "../helper/HL_Types.h" #include "../helper/HL_Types.h"
#include "../helper/HL_VariantMap.h" #include "../helper/HL_VariantMap.h"
#include "../helper/HL_Pointer.h" #include "../helper/HL_Pointer.h"
@@ -16,86 +16,60 @@
namespace Audio namespace Audio
{ {
enum enum
{ {
myMicrophone = 1, myMicrophone = 1,
mySpeaker = 2 mySpeaker = 2
}; };
struct Format struct Format
{ {
int mRate; int mRate;
int mChannels; int mChannels;
Format() Format()
:mRate(AUDIO_SAMPLERATE), mChannels(AUDIO_CHANNELS) :mRate(AUDIO_SAMPLERATE), mChannels(AUDIO_CHANNELS)
{} {}
Format(int rate, int channels) Format(int rate, int channels)
:mRate(rate), mChannels(channels) :mRate(rate), mChannels(channels)
{} {}
size_t samplesFromSize(size_t length) const size_t samplesFromSize(size_t length) const
{ {
return length / 2 / mChannels; return length / 2 / mChannels;
} }
// Returns milliseconds // Returns milliseconds
float timeFromSize(size_t length) const float timeFromSize(size_t length) const
{ {
return float(samplesFromSize(length) / (mRate / 1000.0)); return float(samplesFromSize(length) / (mRate / 1000.0));
} }
float sizeFromTime(size_t milliseconds) const float sizeFromTime(size_t milliseconds) const
{ {
return float((milliseconds * mRate) / 500.0 * mChannels); return float((milliseconds * mRate) / 500.0 * mChannels);
}
size_t sizeFromTime(std::chrono::milliseconds ms) const
{
return sizeFromTime(ms.count());
} }
std::string toString() std::string toString()
{ {
char buffer[64]; char buffer[64];
std::snprintf(buffer, sizeof(buffer), "%dHz %dch", mRate, mChannels); sprintf(buffer, "%dHz %dch", mRate, mChannels);
return std::string(buffer); return std::string(buffer);
} }
};
bool operator == (const Format& rhs) const class DataConnection
{ {
return mRate == rhs.mRate && mChannels == rhs.mChannels; public:
}
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 onMicData(const Format& format, const void* buffer, int length) = 0;
virtual void onSpkData(const Format& format, void* buffer, int length) = 0; virtual void onSpkData(const Format& format, void* buffer, int length) = 0;
}; };
class Device class Device
{ {
public: public:
Device(); Device();
virtual ~Device(); virtual ~Device();
@@ -105,34 +79,34 @@ public:
virtual bool open() = 0; virtual bool open() = 0;
virtual void close() = 0; virtual void close() = 0;
virtual Format getFormat() = 0; virtual Format getFormat() = 0;
protected: protected:
DataConnection* mConnection; DataConnection* mConnection;
}; };
class InputDevice: public Device
class InputDevice: public Device {
{ public:
public:
InputDevice(); InputDevice();
virtual ~InputDevice(); virtual ~InputDevice();
static InputDevice* make(int devId); static InputDevice* make(int devId);
}; };
typedef std::shared_ptr<InputDevice> PInputDevice; typedef std::shared_ptr<InputDevice> PInputDevice;
class OutputDevice: public Device class OutputDevice: public Device
{ {
public: public:
OutputDevice(); OutputDevice();
virtual ~OutputDevice(); virtual ~OutputDevice();
static OutputDevice* make(int devId); static OutputDevice* make(int devId);
}; };
typedef std::shared_ptr<OutputDevice> POutputDevice; typedef std::shared_ptr<OutputDevice> POutputDevice;
class Enumerator class Enumerator
{ {
public: public:
Enumerator(); Enumerator();
virtual ~Enumerator(); virtual ~Enumerator();
int nameToIndex(const std::tstring& name); int nameToIndex(const std::tstring& name);
@@ -146,16 +120,16 @@ public:
virtual int indexOfDefaultDevice() = 0; virtual int indexOfDefaultDevice() = 0;
static Enumerator* make(bool useNull = false); static Enumerator* make(bool useNull = false);
}; };
class OsEngine class OsEngine
{ {
public: public:
virtual void open() = 0; virtual void open() = 0;
virtual void close() = 0; virtual void close() = 0;
static OsEngine* instance(); static OsEngine* instance();
}; };
}; };
#endif #endif
+9 -29
View File
@@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../engine_config.h" #include "../config.h"
#include "../helper/HL_Exception.h" #include "../helper/HL_Exception.h"
#include "../helper/HL_Log.h" #include "../helper/HL_Log.h"
@@ -12,7 +12,7 @@
#include "Audio_Mixer.h" #include "Audio_Mixer.h"
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "Mixer"
using namespace Audio; using namespace Audio;
Mixer::Stream::Stream() Mixer::Stream::Stream()
@@ -94,7 +94,6 @@ Mixer::~Mixer()
void Mixer::unregisterChannel(void* channel) void Mixer::unregisterChannel(void* channel)
{ {
Lock l(mMutex);
for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++) for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++)
{ {
Stream& c = mChannelList[i]; Stream& c = mChannelList[i];
@@ -109,7 +108,6 @@ void Mixer::unregisterChannel(void* channel)
void Mixer::clear(void* context, unsigned ssrc) void Mixer::clear(void* context, unsigned ssrc)
{ {
Lock l(mMutex);
for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++) for (int i=0; i<AUDIO_MIX_CHANNEL_COUNT; i++)
{ {
Stream& c = mChannelList[i]; Stream& c = mChannelList[i];
@@ -143,13 +141,12 @@ Mixer::Stream* Mixer::allocateChannel(void* context, unsigned ssrc)
return NULL; return NULL;
} }
void Mixer::addPcm(void* context, unsigned ssrc, void Mixer::addPcm(void* context, unsigned ssrc,
const void* inputData, int inputLength, const void* inputData, int inputLength,
int inputRate, bool fadeOut) int inputRate, bool fadeOut)
{ {
assert(inputRate == 8000 || inputRate == 16000 || inputRate == 32000); assert(inputRate == 8000 || inputRate == 16000 || inputRate == 32000);
Lock l(mMutex);
int i; int i;
// Locate a channel // Locate a channel
@@ -175,7 +172,6 @@ void Mixer::addPcm(void* context, unsigned ssrc, Audio::DataWindow& w, int rate,
{ {
assert(rate == 8000 || rate == 16000 || rate == 32000 || rate == 48000); assert(rate == 8000 || rate == 16000 || rate == 32000 || rate == 48000);
Lock l(mMutex);
int i; int i;
// Locate a channel // Locate a channel
@@ -200,8 +196,6 @@ void Mixer::addPcm(void* context, unsigned ssrc, Audio::DataWindow& w, int rate,
void Mixer::mix() void Mixer::mix()
{ {
Lock l(mMutex);
// Current sample // Current sample
int sample = 0; int sample = 0;
@@ -316,11 +310,9 @@ void Mixer::mix()
int Mixer::getPcm(void* outputData, int outputLength) int Mixer::getPcm(void* outputData, int outputLength)
{ {
Lock l(mMutex);
if (mOutput.filled() < outputLength) if (mOutput.filled() < outputLength)
mix(); mix();
//ICELogSpecial(<<"Mixer has " << mOutput.filled() << " available bytes"); //ICELogSpecial(<<"Mixer has " << mOutput.filled() << " available bytes");
memset(outputData, 0, outputLength); memset(outputData, 0, outputLength);
return mOutput.read(outputData, outputLength); return mOutput.read(outputData, outputLength);
@@ -328,26 +320,14 @@ int Mixer::getPcm(void* outputData, int outputLength)
int Mixer::mixAndGetPcm(Audio::DataWindow& output) int Mixer::mixAndGetPcm(Audio::DataWindow& output)
{ {
Lock l(mMutex);
// Mix // Mix
mix(); mix();
size_t avail = mOutput.filled(); // Set output space
if (!avail) output.setCapacity(mOutput.filled());
{
output.setFilled(0);
return 0;
}
// Make sure output has enough space (setCapacity only ever grows the window) // Read mixed data to output
if (output.capacity() < avail) return mOutput.read(output.mutableData(), output.capacity());
output.setCapacity(avail);
// Read mixed data to output and publish the real byte count
size_t got = mOutput.read(output.mutableData(), avail);
output.setFilled(got);
return static_cast<int>(got);
} }
int Mixer::available() int Mixer::available()
+1 -1
View File
@@ -6,7 +6,7 @@
#ifndef _RX_MIXER_H #ifndef _RX_MIXER_H
#define _RX_MIXER_H #define _RX_MIXER_H
#include "../engine_config.h" #include "../config.h"
#include "../helper/HL_ByteBuffer.h" #include "../helper/HL_ByteBuffer.h"
#include "../helper/HL_Sync.h" #include "../helper/HL_Sync.h"
#include "Audio_Resampler.h" #include "Audio_Resampler.h"
+68 -84
View File
@@ -1,158 +1,142 @@
#include "Audio_Null.h" #include "Audio_Null.h"
#include "helper/HL_Log.h" #include "helper/HL_Log.h"
#include <assert.h> #include <assert.h>
#include <chrono> #define LOG_SUBSYSTEM "NULL audio"
#define LOG_SUBSYSTEM "audio"
using namespace Audio; using namespace Audio;
using namespace std::chrono_literals;
NullTimer::NullTimer(std::chrono::milliseconds interval, Delegate *delegate, const char* name) NullTimer::NullTimer(int interval, Delegate *delegate, const char* name)
:mShutdown(false), mDelegate(delegate), mInterval(interval), mThreadName(name) :mShutdown(false), mDelegate(delegate), mInterval(interval), mThreadName(name)
{ {
start(); start();
} }
NullTimer::~NullTimer() NullTimer::~NullTimer()
{ {
stop(); stop();
} }
void NullTimer::start() void NullTimer::start()
{ {
mShutdown = false; mShutdown = false;
mWorkerThread = std::thread(&NullTimer::run, this); mWorkerThread = std::thread(&NullTimer::run, this);
} }
void NullTimer::stop() void NullTimer::stop()
{ {
mShutdown = true; mShutdown = true;
if (mWorkerThread.joinable()) if (mWorkerThread.joinable())
mWorkerThread.join(); mWorkerThread.join();
} }
void NullTimer::run() void NullTimer::run()
{ {
mTail = 0us; mTail = 0;
while (!mShutdown) while (!mShutdown)
{
// Get current timestamp
std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
while (mTail >= mInterval * 1000)
{ {
// Get current timestamp if (mDelegate)
std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now(); mDelegate->onTimerSignal(*this);
mTail -= mInterval * 1000;
while (mTail >= mInterval)
{
if (mDelegate)
mDelegate->onTimerSignal(*this);
mTail -= mInterval;
}
// Sleep for mInterval - mTail milliseconds
std::this_thread::sleep_for(mInterval - mTail);
mTail = mTail + std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - timestamp);
} }
// 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::NullInputDevice() NullInputDevice::NullInputDevice()
:mBuffer(nullptr) :mBuffer(nullptr)
{ {
} }
NullInputDevice::~NullInputDevice() NullInputDevice::~NullInputDevice()
{ {
internalClose(); close();
} }
bool NullInputDevice::open() bool NullInputDevice::open()
{ {
ICELogInfo(<< "Starting NullInputDevice for " << AUDIO_MIC_BUFFER_LENGTH << "ms buffers"); mBuffer = malloc(AUDIO_MIC_BUFFER_SIZE);
mBuffer = malloc(AUDIO_MIC_BUFFER_SIZE); memset(mBuffer, 0, AUDIO_MIC_BUFFER_SIZE);
memset(mBuffer, 0, AUDIO_MIC_BUFFER_SIZE); mTimeCounter = 0; mDataCounter = 0;
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");
// Creation of timer starts it also. So first onTimerSignal can come even before open() returns. return true;
mTimer = std::make_shared<NullTimer>(std::chrono::milliseconds(AUDIO_MIC_BUFFER_LENGTH), this, "null_mic");
return true;
}
void NullInputDevice::internalClose()
{
ICELogInfo(<< "Stopping NullInputDevice");
mTimer.reset();
if (mBuffer)
{
free(mBuffer);
mBuffer = nullptr;
}
ICELogInfo( << "Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes.");
} }
void NullInputDevice::close() void NullInputDevice::close()
{ {
internalClose(); mTimer.reset();
if (mBuffer)
{
free(mBuffer);
mBuffer = nullptr;
}
ICELogInfo(<<"Pseudocaptured " << mTimeCounter << " milliseconds , " << mDataCounter << " bytes.");
} }
Format NullInputDevice::getFormat() Format NullInputDevice::getFormat()
{ {
assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE); assert (Format().sizeFromTime(AUDIO_MIC_BUFFER_LENGTH) == AUDIO_MIC_BUFFER_SIZE);
return Format();
return {}; // Return library-define default format
} }
void NullInputDevice::onTimerSignal(NullTimer& timer) void NullInputDevice::onTimerSignal(NullTimer& timer)
{ {
mTimeCounter += AUDIO_MIC_BUFFER_LENGTH; mTimeCounter += AUDIO_MIC_BUFFER_LENGTH;
mDataCounter += AUDIO_MIC_BUFFER_SIZE; mDataCounter += AUDIO_MIC_BUFFER_SIZE;
if (mConnection) if (mConnection)
mConnection->onMicData(getFormat(), mBuffer, AUDIO_MIC_BUFFER_SIZE); mConnection->onMicData(getFormat(), mBuffer, AUDIO_MIC_BUFFER_SIZE);
} }
// --------------------- NullOutputDevice -------------------------- // --------------------- NullOutputDevice --------------------------
NullOutputDevice::NullOutputDevice() NullOutputDevice::NullOutputDevice()
:mBuffer(nullptr) :mBuffer(nullptr)
{ {
} }
NullOutputDevice::~NullOutputDevice() NullOutputDevice::~NullOutputDevice()
{ {
internalClose(); close();
} }
bool NullOutputDevice::open() bool NullOutputDevice::open()
{ {
mTimeCounter = 0; mDataCounter = 0; mTimeCounter = 0; mDataCounter = 0;
mBuffer = malloc(AUDIO_SPK_BUFFER_SIZE); mBuffer = malloc(AUDIO_SPK_BUFFER_SIZE);
// Creation of timer starts it also. So first onSpkData() can come before open() returns even. // Creation of timer starts it also. So first onSpkData() can come before open() returns even.
mTimer = std::make_shared<NullTimer>(std::chrono::milliseconds(AUDIO_SPK_BUFFER_LENGTH), this, "null_spk"); mTimer = std::make_shared<NullTimer>(AUDIO_SPK_BUFFER_LENGTH, this, "NullSpeakerThread");
return true; return true;
}
void NullOutputDevice::internalClose()
{
mTimer.reset();
free(mBuffer); mBuffer = nullptr;
ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes.");
} }
void NullOutputDevice::close() void NullOutputDevice::close()
{ {
internalClose(); mTimer.reset();
free(mBuffer); mBuffer = nullptr;
ICELogInfo(<< "Pseudoplayed " << mTimeCounter << " milliseconds, " << mDataCounter << " bytes.");
} }
Format NullOutputDevice::getFormat() Format NullOutputDevice::getFormat()
{ {
assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE); assert (Format().sizeFromTime(AUDIO_SPK_BUFFER_LENGTH) == AUDIO_SPK_BUFFER_SIZE);
return Format(); return Format();
} }
void NullOutputDevice::onTimerSignal(NullTimer &timer) void NullOutputDevice::onTimerSignal(NullTimer &timer)
{ {
mTimeCounter += AUDIO_SPK_BUFFER_LENGTH; mTimeCounter += AUDIO_SPK_BUFFER_LENGTH;
mDataCounter += AUDIO_SPK_BUFFER_SIZE; mDataCounter += AUDIO_SPK_BUFFER_SIZE;
if (mConnection) if (mConnection)
mConnection->onSpkData(getFormat(), mBuffer, AUDIO_SPK_BUFFER_SIZE); mConnection->onSpkData(getFormat(), mBuffer, AUDIO_SPK_BUFFER_SIZE);
} }
// ---------------------- NullEnumerator -------------------------- // ---------------------- NullEnumerator --------------------------
@@ -170,25 +154,25 @@ void NullEnumerator::close()
int NullEnumerator::count() int NullEnumerator::count()
{ {
return 1; return 1;
} }
std::tstring NullEnumerator::nameAt(int index) std::tstring NullEnumerator::nameAt(int index)
{ {
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
return L"null"; return L"null";
#else #else
return "null"; return "null";
#endif #endif
} }
int NullEnumerator::idAt(int index) int NullEnumerator::idAt(int index)
{ {
return 0; return 0;
} }
int NullEnumerator::indexOfDefaultDevice() int NullEnumerator::indexOfDefaultDevice()
{ {
return 0; return 0;
} }
+26 -31
View File
@@ -2,46 +2,43 @@
#define __AUDIO_NULL_H #define __AUDIO_NULL_H
#include <thread> #include <thread>
#include <chrono>
#include "Audio_Interface.h" #include "Audio_Interface.h"
namespace Audio namespace Audio
{ {
class NullTimer class NullTimer
{ {
public: public:
class Delegate class Delegate
{ {
public: public:
virtual void onTimerSignal(NullTimer& timer) = 0; virtual void onTimerSignal(NullTimer& timer) = 0;
}; };
protected: protected:
std::thread mWorkerThread; std::thread mWorkerThread;
std::atomic_bool mShutdown = {false}; volatile bool mShutdown;
Delegate* mDelegate = nullptr; Delegate* mDelegate;
std::chrono::milliseconds mInterval; // Interval - wanted number of milliseconds int mInterval, // Interval - wanted number of milliseconds
std::chrono::microseconds mTail; // Number of milliseconds that can be sent immediately to sink mTail; // Number of milliseconds that can be sent immediately to sink
std::string mThreadName; std::string mThreadName;
void start(); void start();
void stop(); void stop();
void run(); void run();
public: public:
/* Interval is in milliseconds. */ /* Interval is in milliseconds. */
NullTimer(std::chrono::milliseconds interval, Delegate* delegate, const char* name = nullptr); NullTimer(int interval, Delegate* delegate, const char* name = nullptr);
~NullTimer(); ~NullTimer();
}; };
class NullInputDevice: public InputDevice, public NullTimer::Delegate class NullInputDevice: public InputDevice, public NullTimer::Delegate
{ {
protected: protected:
void* mBuffer = nullptr; void* mBuffer = nullptr;
std::shared_ptr<NullTimer> mTimer; std::shared_ptr<NullTimer> mTimer;
int64_t mTimeCounter = 0, mDataCounter = 0; int64_t mTimeCounter = 0, mDataCounter = 0;
void internalClose(); public:
public:
NullInputDevice(); NullInputDevice();
virtual ~NullInputDevice(); virtual ~NullInputDevice();
@@ -50,17 +47,15 @@ public:
Format getFormat() override; Format getFormat() override;
void onTimerSignal(NullTimer& timer) override; void onTimerSignal(NullTimer& timer) override;
}; };
class NullOutputDevice: public OutputDevice, public NullTimer::Delegate class NullOutputDevice: public OutputDevice, public NullTimer::Delegate
{ {
protected: protected:
std::shared_ptr<NullTimer> mTimer; std::shared_ptr<NullTimer> mTimer;
void* mBuffer = nullptr; void* mBuffer = nullptr;
int64_t mDataCounter = 0, mTimeCounter = 0; int64_t mDataCounter = 0, mTimeCounter = 0;
public:
void internalClose();
public:
NullOutputDevice(); NullOutputDevice();
virtual ~NullOutputDevice(); virtual ~NullOutputDevice();
@@ -69,11 +64,11 @@ public:
Format getFormat() override; Format getFormat() override;
void onTimerSignal(NullTimer& timer) override; void onTimerSignal(NullTimer& timer) override;
}; };
class NullEnumerator: public Enumerator class NullEnumerator: public Enumerator
{ {
public: public:
NullEnumerator(); NullEnumerator();
~NullEnumerator(); ~NullEnumerator();
@@ -84,8 +79,8 @@ public:
std::tstring nameAt(int index) override; std::tstring nameAt(int index) override;
int idAt(int index) override; int idAt(int index) override;
int indexOfDefaultDevice() override; int indexOfDefaultDevice() override;
};
};
} }
#endif #endif
+1 -1
View File
@@ -7,7 +7,7 @@
#include "../helper/HL_Log.h" #include "../helper/HL_Log.h"
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "Player"
using namespace Audio; using namespace Audio;
// -------------- Player ----------- // -------------- Player -----------
+2 -5
View File
@@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../engine_config.h" #include "../config.h"
#include "Audio_Quality.h" #include "Audio_Quality.h"
#include "../helper/HL_Exception.h" #include "../helper/HL_Exception.h"
#include "../helper/HL_Types.h" #include "../helper/HL_Types.h"
@@ -17,10 +17,7 @@
using namespace Audio; using namespace Audio;
#ifndef SHRT_MAX #define SHRT_MAX 32767 /* maximum (signed) short value */
# define SHRT_MAX 32767 /* maximum (signed) short value */
#endif
AgcFilter::AgcFilter(int channels) AgcFilter::AgcFilter(int channels)
{ {
static const float DefaultLevel = 0.8f; static const float DefaultLevel = 0.8f;
+1 -1
View File
@@ -5,7 +5,7 @@
#ifndef __AUDIO_QUALITY_H #ifndef __AUDIO_QUALITY_H
#define __AUDIO_QUALITY_H #define __AUDIO_QUALITY_H
#include "../engine_config.h" #include "../config.h"
#include "../helper/HL_Sync.h" #include "../helper/HL_Sync.h"
#include <vector> #include <vector>
+11 -15
View File
@@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../engine_config.h" #include "../config.h"
#include "Audio_Resampler.h" #include "Audio_Resampler.h"
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
@@ -18,7 +18,9 @@ namespace Audio
SpeexResampler::SpeexResampler() SpeexResampler::SpeexResampler()
{} :mContext(NULL), mErrorCode(0), mSourceRate(0), mDestRate(0), mLastSample(0)
{
}
void SpeexResampler::start(int channels, int sourceRate, int destRate) void SpeexResampler::start(int channels, int sourceRate, int destRate)
{ {
@@ -49,11 +51,6 @@ void SpeexResampler::stop()
} }
} }
bool SpeexResampler::isOpened() const
{
return mContext != nullptr;
}
SpeexResampler::~SpeexResampler() SpeexResampler::~SpeexResampler()
{ {
stop(); stop();
@@ -116,25 +113,24 @@ size_t SpeexResampler::processBuffer(const void* src, size_t sourceLength, size_
return outLen * sizeof(short) * mChannels; return outLen * sizeof(short) * mChannels;
} }
int SpeexResampler::sourceRate() const int SpeexResampler::sourceRate()
{ {
return mSourceRate; return mSourceRate;
} }
int SpeexResampler::destRate() const int SpeexResampler::destRate()
{ {
return mDestRate; return mDestRate;
} }
size_t SpeexResampler::getDestLength(size_t sourceLen) const size_t SpeexResampler::getDestLength(size_t sourceLen)
{ {
return size_t(sourceLen * (float(mDestRate) / mSourceRate) + 0.5f); return size_t(sourceLen * (float(mDestRate) / mSourceRate) + 0.5f) / 2 * 2;
} }
size_t SpeexResampler::getSourceLength(size_t destLen) const 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) / 2 * 2;
return size_t(destLen * (float(mSourceRate) / mDestRate) + 0.5f);
} }
// Returns instance + speex resampler size in bytes // Returns instance + speex resampler size in bytes
@@ -277,7 +273,7 @@ PResampler UniversalResampler::findResampler(int sourceRate, int destRate)
PResampler r; PResampler r;
if (resamplerIter == mResamplerMap.end()) if (resamplerIter == mResamplerMap.end())
{ {
r = std::make_shared<Resampler>(); r = PResampler(new Resampler());
r->start(AUDIO_CHANNELS, sourceRate, destRate); r->start(AUDIO_CHANNELS, sourceRate, destRate);
mResamplerMap[RatePair(sourceRate, destRate)] = r; mResamplerMap[RatePair(sourceRate, destRate)] = r;
} }
+10 -12
View File
@@ -24,25 +24,23 @@ namespace Audio
void start(int channels, int sourceRate, int destRate); void start(int channels, int sourceRate, int destRate);
void stop(); void stop();
bool isOpened() const;
size_t processBuffer(const void* source, size_t sourceLength, size_t& sourceProcessed, size_t processBuffer(const void* source, size_t sourceLength, size_t& sourceProcessed,
void* dest, size_t destCapacity); void* dest, size_t destCapacity);
int sourceRate() const; int sourceRate();
int destRate() const; int destRate();
size_t getDestLength(size_t sourceLen) const; size_t getDestLength(size_t sourceLen);
size_t getSourceLength(size_t destLen) const; size_t getSourceLength(size_t destLen);
// Returns instance + speex encoder size in bytes // Returns instance + speex encoder size in bytes
size_t getSize() const; size_t getSize() const;
protected: protected:
void* mContext = nullptr; void* mContext;
int mErrorCode = 0; int mErrorCode;
int mSourceRate = 0, int mSourceRate,
mDestRate = 0, mDestRate,
mChannels = 0; mChannels;
short mLastSample = 0; short mLastSample;
}; };
typedef SpeexResampler Resampler; typedef SpeexResampler Resampler;
+254 -290
View File
@@ -7,10 +7,9 @@
#include "helper/HL_Exception.h" #include "helper/HL_Exception.h"
#include "helper/HL_String.h" #include "helper/HL_String.h"
#include "helper/HL_Log.h" #include "helper/HL_Log.h"
#include "../engine_config.h" #include "../config.h"
#include <memory.h> #include <memory.h>
#include <assert.h>
#ifndef WORD #ifndef WORD
# define WORD unsigned short # define WORD unsigned short
@@ -20,19 +19,19 @@
#endif #endif
typedef struct { typedef struct {
WORD wFormatTag; WORD wFormatTag;
WORD nChannels; WORD nChannels;
DWORD nSamplesPerSec; DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec; DWORD nAvgBytesPerSec;
WORD nBlockAlign; WORD nBlockAlign;
WORD wBitsPerSample; WORD wBitsPerSample;
WORD cbSize; WORD cbSize;
} }
WaveFormatEx; WaveFormatEx;
#define WAVE_FORMAT_PCM 1 #define WAVE_FORMAT_PCM 1
#define LOG_SUBSYSTEM "audio" #define LOG_SUBSYSTEM "WavFileReader"
#define LOCK std::unique_lock<std::recursive_mutex> lock(mFileMtx); #define LOCK std::unique_lock<std::recursive_mutex> lock(mFileMtx);
@@ -40,9 +39,9 @@ using namespace Audio;
// ---------------------- WavFileReader ------------------------- // ---------------------- WavFileReader -------------------------
WavFileReader::WavFileReader() WavFileReader::WavFileReader()
:mSamplerate(0), mLastError(0), mChannels(0), mBits(0), mDataLength(0) :mHandle(nullptr), mRate(0), mLastError(0)
{ {
mDataOffset = 0; mDataOffset = 0;
} }
WavFileReader::~WavFileReader() WavFileReader::~WavFileReader()
@@ -53,243 +52,207 @@ WavFileReader::~WavFileReader()
std::string WavFileReader::readChunk() std::string WavFileReader::readChunk()
{ {
char name[5] = {0}; char name[5];
readBuffer(name, 4); if (fread(name, 1, 4, mHandle) != 4)
THROW_READERROR;
std::string result = name; name[4] = 0;
uint32_t size = 0; std::string result = name;
readBuffer(&size, 4); unsigned size;
if (fread(&size, 4, 1, mHandle) != 1)
THROW_READERROR;
if (result == "data") if (result == "fact")
mDataLength = size; fread(&mDataLength, 4, 1, mHandle);
else else
// Skip the chunk body; RIFF chunks are word-aligned, so odd sizes carry a pad byte if (result != "data")
mInput->seekg(std::streamoff(size + (size & 1)), std::ios_base::cur); fseek(mHandle, size, SEEK_CUR);
else
return result; mDataLength = size;
return result;
} }
void WavFileReader::readBuffer(void* buffer, size_t sz) bool WavFileReader::open(const std::tstring& filename)
{ {
auto p = mInput->tellg(); LOCK;
mInput->read(reinterpret_cast<char*>(buffer), sz); try
if (mInput->tellg() - p != sz) {
throw Exception(ERR_WAVFILE_FAILED); #ifdef WIN32
} mHandle = _wfopen(filename.c_str(), L"rb");
#else
size_t WavFileReader::tryReadBuffer(void* buffer, size_t sz) mHandle = fopen(StringHelper::makeUtf8(filename).c_str(), "rb");
{ #endif
auto p = mInput->tellg(); if (NULL == mHandle)
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) #if defined(TARGET_ANDROID) || defined(TARGET_LINUX) || defined(TARGET_OSX)
mLastError = errno; mLastError = errno;
#endif #endif
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
mLastError = GetLastError(); mLastError = GetLastError();
#endif #endif
return false; 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));
// Only 16-bit PCM is supported: the read path feeds the data
// directly into a 16-bit resampler.
if (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(...) mLastError = 0;
{
mInput.reset(); // Read the .WAV header
mLastError = static_cast<unsigned>(-1); char riff[4];
} if (fread(riff, 4, 1, mHandle) < 1)
return isOpened(); THROW_READERROR;
if (!(riff[0] == 'R' && riff[1] == 'I' && riff[2] == 'F' && riff[3] == 'F'))
THROW_READERROR;
// Read the file size
unsigned int filesize = 0;
if (fread(&filesize, 4, 1, mHandle) < 1)
THROW_READERROR;
char wavefmt[9];
if (fread(wavefmt, 8, 1, mHandle) < 1)
THROW_READERROR;
wavefmt[8] = 0;
if (strcmp(wavefmt, "WAVEfmt ") != 0)
THROW_READERROR;
unsigned fmtSize = 0;
if (fread(&fmtSize, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned fmtStart = ftell(mHandle);
unsigned short formattag = 0;
if (fread(&formattag, 2, 1, mHandle) < 1)
THROW_READERROR;
if (formattag != 1/*WAVE_FORMAT_PCM*/)
THROW_READERROR;
mChannels = 0;
if (fread(&mChannels, 2, 1, mHandle) < 1)
THROW_READERROR;
mRate = 0;
if (fread(&mRate, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned int avgbytespersec = 0;
if (fread(&avgbytespersec, 4, 1, mHandle) < 1)
THROW_READERROR;
unsigned short blockalign = 0;
if (fread(&blockalign, 2, 1, mHandle) < 1)
THROW_READERROR;
mBits = 0;
if (fread(&mBits, 2, 1, mHandle) < 1)
THROW_READERROR;
if (mBits !=8 && mBits != 16)
THROW_READERROR;
// Read the "chunk"
fseek(mHandle, fmtStart + fmtSize, SEEK_SET);
//unsigned pos = ftell(mHandle);
mDataLength = 0;
while (readChunk() != "data")
;
mFileName = filename;
mDataOffset = ftell(mHandle);
mResampler.start(AUDIO_CHANNELS, mRate, AUDIO_SAMPLERATE);
}
catch(...)
{
fclose(mHandle); mHandle = nullptr;
mLastError = static_cast<unsigned>(-1);
}
return isOpened();
} }
void WavFileReader::close() void WavFileReader::close()
{ {
LOCK; LOCK;
mInput.reset();
if (nullptr != mHandle)
fclose(mHandle);
mHandle = nullptr;
} }
int WavFileReader::samplerate() const int WavFileReader::rate() const
{ {
return mSamplerate; return mRate;
} }
int WavFileReader::channels() const unsigned WavFileReader::read(void* buffer, unsigned bytes)
{ {
return mChannels; return read((short*)buffer, bytes / (AUDIO_CHANNELS * 2)) * AUDIO_CHANNELS * 2;
} }
size_t WavFileReader::read(void* buffer, size_t bytes) unsigned WavFileReader::read(short* buffer, unsigned samples)
{ {
return read((short*)buffer, bytes / (AUDIO_CHANNELS * 2)) * AUDIO_CHANNELS * 2; LOCK;
}
size_t WavFileReader::readRaw(void* buffer, size_t bytes) if (!mHandle)
{ return 0;
return readRaw((short*)buffer, bytes / channels() / sizeof(short)) * channels() * sizeof(short);
}
size_t WavFileReader::read(short* buffer, size_t samples) // Get number of samples that must be read from source file
{ int requiredBytes = mResampler.getSourceLength(samples) * mChannels * mBits / 8;
LOCK; void* temp = alloca(requiredBytes);
memset(temp, 0, requiredBytes);
if (!mInput) // Find required size of input buffer
return 0; if (mDataLength)
{
unsigned filePosition = ftell(mHandle);
// Get number of samples that must be read from source file // Check how much data we can read
size_t requiredBytes = mResampler.getSourceLength(samples) * mChannels * mBits / 8; unsigned fileAvailable = mDataLength + mDataOffset - filePosition;
bool useHeap = requiredBytes > sizeof mTempBuffer; requiredBytes = (int)fileAvailable < requiredBytes ? (int)fileAvailable : requiredBytes;
void* temp; }
if (useHeap)
temp = malloc(requiredBytes);
else
temp = mTempBuffer;
memset(temp, 0, requiredBytes); /*int readSamples = */fread(temp, 1, requiredBytes, mHandle);// / mChannels / (mBits / 8);
size_t processedBytes = 0;
size_t result = mResampler.processBuffer(temp, requiredBytes, processedBytes,
buffer, samples * 2 * AUDIO_CHANNELS);
// Find required size of input buffer return result / 2 / AUDIO_CHANNELS;
if (mDataLength)
{
auto filePosition = mInput->tellg();
// Check how much data we can read
std::streamoff dataEnd = std::streamoff(mDataLength) + mDataOffset;
size_t fileAvailable = filePosition < dataEnd ? size_t(dataEnd - filePosition) : 0;
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
std::streamoff dataEnd = std::streamoff(mDataLength) + mDataOffset;
size_t fileAvailable = filePosition < dataEnd ? size_t(dataEnd - filePosition) : 0;
requiredBytes = fileAvailable < requiredBytes ? fileAvailable : requiredBytes;
}
size_t readBytes = tryReadBuffer(buffer, requiredBytes);
return readBytes / channels() / sizeof(short);
} }
bool WavFileReader::isOpened() bool WavFileReader::isOpened()
{ {
LOCK; LOCK;
if (!mInput)
return false; return (mHandle != 0);
return mInput->is_open();
} }
void WavFileReader::rewind() void WavFileReader::rewind()
{ {
LOCK; LOCK;
if (mInput)
mInput->seekg(mDataOffset); if (mHandle)
fseek(mHandle, mDataOffset, SEEK_SET);
} }
std::filesystem::path WavFileReader::path() const std::tstring WavFileReader::filename() const
{ {
LOCK; LOCK;
return mPath;
return mFileName;
} }
size_t WavFileReader::size() const unsigned WavFileReader::size() const
{ {
LOCK; LOCK;
return mDataLength;
return mDataLength;
} }
unsigned WavFileReader::lastError() const unsigned WavFileReader::lastError() const
{ {
return mLastError; return mLastError;
} }
// ------------------------- WavFileWriter ------------------------- // ------------------------- WavFileWriter -------------------------
#define LOG_SUBSYTEM "WavFileWriter" #define LOG_SUBSYTEM "WavFileWriter"
@@ -297,134 +260,135 @@ unsigned WavFileReader::lastError() const
#define BITS_PER_CHANNEL 16 #define BITS_PER_CHANNEL 16
WavFileWriter::WavFileWriter() WavFileWriter::WavFileWriter()
:mLengthOffset(0), mSamplerate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0) :mHandle(nullptr), mLengthOffset(0), mRate(AUDIO_SAMPLERATE), mChannels(1), mWritten(0)
{} {
}
WavFileWriter::~WavFileWriter() WavFileWriter::~WavFileWriter()
{ {
close(); close();
} }
void WavFileWriter::checkWriteResult(int result) void WavFileWriter::checkWriteResult(int result)
{ {
if (result < 1) if (result < 1)
throw Exception(ERR_WAVFILE_FAILED, errno); throw Exception(ERR_WAVFILE_FAILED, errno);
} }
void WavFileWriter::writeBuffer(const void* buffer, size_t sz) bool WavFileWriter::open(const std::tstring& filename, int rate, int channels)
{ {
if (!mOutput) LOCK;
return;
auto p = mOutput->tellp(); close();
mOutput->write(reinterpret_cast<const char*>(buffer), sz);
if (mOutput->tellp() - p != sz) mRate = rate;
throw Exception(ERR_WAVFILE_FAILED); mChannels = channels;
}
bool WavFileWriter::open(const std::filesystem::path& p, int samplerate, int channels) #ifdef WIN32
{ mHandle = _wfopen(filename.c_str(), L"wb");
LOCK; #else
close(); mHandle = fopen(StringHelper::makeUtf8(filename).c_str(), "wb");
mSamplerate = samplerate; #endif
mChannels = channels; if (nullptr == mHandle)
{
ICELogError(<< "Failed to create .wav file: filename = " << StringHelper::makeUtf8(filename) << " , error = " << errno);
return false;
}
mOutput = std::make_unique<std::ofstream>(p, std::ios::binary | std::ios::trunc); // Write the .WAV header
if (!mOutput->is_open()) const char* riff = "RIFF";
{ checkWriteResult( fwrite(riff, 4, 1, mHandle) );
int errorcode = errno;
ICELogError(<< "Failed to create .wav file: filename = " << p << " , error = " << errorcode); // Write the file size
mOutput.reset(); unsigned int filesize = 0;
return false; checkWriteResult( fwrite(&filesize, 4, 1, mHandle) );
}
// Write the .WAV header const char* wavefmt = "WAVEfmt ";
const char* riff = "RIFF"; checkWriteResult( fwrite(wavefmt, 8, 1, mHandle) );
writeBuffer(riff, 4);
// Write the file size // Set the format description
uint32_t filesize = 0; DWORD dwFmtSize = 16; /*= 16L*/;
writeBuffer(&filesize, sizeof filesize); checkWriteResult( fwrite(&dwFmtSize, sizeof(dwFmtSize), 1, mHandle) );
const char* wavefmt = "WAVEfmt "; WaveFormatEx format;
writeBuffer(wavefmt, 8); format.wFormatTag = WAVE_FORMAT_PCM;
checkWriteResult( fwrite(&format.wFormatTag, sizeof(format.wFormatTag), 1, mHandle) );
format.nChannels = mChannels;
checkWriteResult( fwrite(&format.nChannels, sizeof(format.nChannels), 1, mHandle) );
// Set the format description format.nSamplesPerSec = mRate;
uint32_t dwFmtSize = 16; /*= 16L*/; checkWriteResult( fwrite(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec), 1, mHandle) );
writeBuffer(&dwFmtSize, sizeof(dwFmtSize));
format.nAvgBytesPerSec = mRate * 2 * mChannels;
checkWriteResult( fwrite(&format.nAvgBytesPerSec, sizeof(format.nAvgBytesPerSec), 1, mHandle) );
WaveFormatEx format; format.nBlockAlign = 2 * mChannels;
format.wFormatTag = WAVE_FORMAT_PCM; checkWriteResult( fwrite(&format.nBlockAlign, sizeof(format.nBlockAlign), 1, mHandle) );
writeBuffer(&format.wFormatTag, sizeof(format.wFormatTag));
format.wBitsPerSample = BITS_PER_CHANNEL;
checkWriteResult( fwrite(&format.wBitsPerSample, sizeof(format.wBitsPerSample), 1, mHandle) );
const char* data = "data";
checkWriteResult( fwrite(data, 4, 1, mHandle));
format.nChannels = mChannels; mFileName = filename;
writeBuffer(&format.nChannels, sizeof(format.nChannels)); mWritten = 0;
format.nSamplesPerSec = mSamplerate; mLengthOffset = ftell(mHandle);
writeBuffer(&format.nSamplesPerSec, sizeof(format.nSamplesPerSec)); checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
format.nAvgBytesPerSec = mSamplerate * 2 * mChannels; return isOpened();
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() void WavFileWriter::close()
{ {
LOCK; LOCK;
mOutput.reset();
if (mHandle)
{
fclose(mHandle);
mHandle = nullptr;
}
} }
size_t WavFileWriter::write(const void* buffer, size_t bytes) size_t WavFileWriter::write(const void* buffer, size_t bytes)
{ {
LOCK; LOCK;
if (!mOutput) if (!mHandle)
return 0; return 0;
// Seek the end of file - here new data will be written // Seek the end of file
mOutput->seekp(0, std::ios_base::end); fseek(mHandle, 0, SEEK_END);
mWritten += bytes; mWritten += bytes;
// Write the data // Write the data
writeBuffer(buffer, bytes); fwrite(buffer, bytes, 1, mHandle);
// Write file length // Write file length
mOutput->seekp(4, std::ios_base::beg); fseek(mHandle, 4, SEEK_SET);
uint32_t fl = mWritten + 36; int32_t fl = mWritten + 36;
writeBuffer(&fl, sizeof(fl)); fwrite(&fl, sizeof(fl), 1, mHandle);
// Write data length
fseek(mHandle, static_cast<long>(mLengthOffset), SEEK_SET);
checkWriteResult( fwrite(&mWritten, 4, 1, mHandle) );
// Write data length return bytes;
mOutput->seekp(mLengthOffset, std::ios_base::beg);
writeBuffer(&mWritten, sizeof(mWritten));
return bytes;
} }
bool WavFileWriter::isOpened() const bool WavFileWriter::isOpened()
{ {
LOCK; LOCK;
return mOutput && mOutput->is_open();
return (mHandle != nullptr);
} }
std::filesystem::path WavFileWriter::path() const std::tstring WavFileWriter::filename()
{ {
LOCK; LOCK;
return mPath;
return mFileName;
} }
+40 -50
View File
@@ -12,84 +12,74 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <filesystem>
#include <fstream>
namespace Audio namespace Audio
{ {
class WavFileReader class WavFileReader
{ {
protected: protected:
uint16_t mChannels = 0; FILE* mHandle;
uint16_t mBits = 0; short mChannels;
int mSamplerate = 0; short mBits;
std::filesystem::path mPath; int mRate;
mutable std::recursive_mutex mFileMtx; std::tstring mFileName;
size_t mDataOffset = 0; mutable std::recursive_mutex mFileMtx;
size_t mDataLength = 0; unsigned mDataOffset;
Resampler mResampler; unsigned mDataLength;
unsigned mLastError = 0; Resampler mResampler;
std::unique_ptr<std::ifstream> mInput; unsigned mLastError;
uint8_t mTempBuffer[16384];
std::string readChunk(); std::string readChunk();
void readBuffer(void* buffer, size_t sz); // This raises an exception if sz bytes are not read public:
size_t tryReadBuffer(void* buffer, size_t sz); // This doesn't raise an exception
public:
WavFileReader(); WavFileReader();
~WavFileReader(); ~WavFileReader();
bool open(const std::filesystem::path& p); bool open(const std::tstring& filename);
void close(); void close();
bool isOpened(); bool isOpened();
void rewind(); void rewind();
int samplerate() const; int rate() const;
int channels() const;
// This method returns number of read bytes // This method returns number of read bytes
size_t read(void* buffer, size_t bytes); unsigned read(void* buffer, unsigned bytes);
size_t readRaw(void* buffer, size_t bytes);
// This method returns number of read samples // This method returns number of read samples
size_t read(short* buffer, size_t samples); unsigned read(short* buffer, unsigned samples);
size_t readRaw(short* buffer, size_t samples); std::tstring filename() const;
unsigned size() const;
std::filesystem::path path() const;
size_t size() const;
unsigned lastError() const; unsigned lastError() const;
}; };
typedef std::shared_ptr<WavFileReader> PWavFileReader; typedef std::shared_ptr<WavFileReader> PWavFileReader;
class WavFileWriter class WavFileWriter
{ {
protected: protected:
std::unique_ptr<std::ofstream> mOutput; /// Handle of audio file. FILE* mHandle; /// Handle of audio file.
std::filesystem::path mPath; /// Path to requested audio file. std::tstring mFileName; /// Path to requested audio file.
mutable std::recursive_mutex mFileMtx; /// Mutex to protect this instance. std::recursive_mutex mFileMtx; /// Mutex to protect this instance.
size_t mWritten = 0; /// Amount of written data (in bytes) int mWritten; /// Amount of written data (in bytes)
size_t mLengthOffset = 0; /// Position of length field. int mLengthOffset; /// Position of length field.
int mSamplerate = 0, int mRate,
mChannels = 0; mChannels;
void checkWriteResult(int result); void checkWriteResult(int result);
void writeBuffer(const void* buffer, size_t sz);
public: public:
WavFileWriter(); WavFileWriter();
~WavFileWriter(); ~WavFileWriter();
bool open(const std::filesystem::path& p, int samplerate, int channels); bool open(const std::tstring& filename, int rate, int channels);
void close(); void close();
bool isOpened() const; bool isOpened();
size_t write(const void* buffer, size_t bytes); size_t write(const void* buffer, size_t bytes);
std::filesystem::path path() const; std::tstring filename();
}; };
typedef std::shared_ptr<WavFileWriter> PWavFileWriter; typedef std::shared_ptr<WavFileWriter> PWavFileWriter;
} }
+1 -1
View File
@@ -8,7 +8,7 @@
#ifdef TARGET_WIN #ifdef TARGET_WIN
#include "../engine_config.h" #include "../config.h"
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
+1 -16
View File
@@ -1,42 +1,27 @@
project (audio_lib) project (audio_lib)
# Rely on C++ 11 # Rely on C++ 11
set (CMAKE_CXX_STANDARD 20) set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set (AUDIOLIB_SOURCES set (AUDIOLIB_SOURCES
Audio_Resampler.cpp Audio_Resampler.cpp
Audio_Resampler.h
Audio_Quality.cpp Audio_Quality.cpp
Audio_Quality.h
Audio_Mixer.cpp Audio_Mixer.cpp
Audio_Mixer.h
Audio_Interface.cpp Audio_Interface.cpp
Audio_Interface.h
Audio_Helper.cpp Audio_Helper.cpp
Audio_Helper.h
Audio_DataWindow.cpp Audio_DataWindow.cpp
Audio_DataWindow.h
Audio_DevicePair.cpp Audio_DevicePair.cpp
Audio_DevicePair.h
Audio_Player.cpp Audio_Player.cpp
Audio_Player.h
Audio_Null.cpp Audio_Null.cpp
Audio_Null.h
Audio_CoreAudio.cpp Audio_CoreAudio.cpp
Audio_CoreAudio.h
Audio_DirectSound.cpp Audio_DirectSound.cpp
Audio_DirectSound.h
Audio_AndroidOboe.cpp
Audio_AndroidOboe.h
Audio_WavFile.cpp Audio_WavFile.cpp
Audio_WavFile.h
) )
add_library(audio_lib ${AUDIOLIB_SOURCES}) add_library(audio_lib ${AUDIOLIB_SOURCES})
set_property(TARGET audio_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
## ##
target_include_directories(audio_lib target_include_directories(audio_lib
+113
View File
@@ -0,0 +1,113 @@
/* Copyright(C) 2007-2020 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __TOOLKIT_CONFIG_H
#define __TOOLKIT_CONFIG_H
#define USE_SPEEX_AEC
// TODO: test implementation with webrtc aec; be careful - it needs fixes!
//#define USE_WEBRTC_AEC
#define USER
#define AUDIO_SAMPLE_WIDTH 16
#define AUDIO_CHANNELS 1
// Samplerate must be 8 / 16 / 24 / 32 / 48 KHz
#define AUDIO_SAMPLERATE 8000
#define AUDIO_MIC_BUFFER_COUNT 16
#define AUDIO_MIC_BUFFER_LENGTH 10
#define AUDIO_MIC_BUFFER_SIZE (AUDIO_MIC_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
#define AUDIO_SPK_BUFFER_COUNT 16
#define AUDIO_SPK_BUFFER_LENGTH 10
#define AUDIO_SPK_BUFFER_SIZE (AUDIO_SPK_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
#define AUDIO_MIX_CHANNEL_COUNT 16
#define AUDIO_DEVICEPAIR_INPUTBUFFER 16384
// Avoid too high resampler quality - it can take many CPU and cause gaps in playing
#define AUDIO_RESAMPLER_QUALITY 1
#define AEC_FRAME_TIME 10
#define AEC_TAIL_TIME 160
// Defined these two lines to get dumping of audio input/output
//#define AUDIO_DUMPINPUT
//#define AUDIO_DUMPOUTPUT
#define UA_REGISTRATION_TIME 3600
#define UA_MEDIA_PORT_START 20000
#define UA_MEDIA_PORT_FINISH 30000
#define UA_MAX_UDP_PACKET_SIZE 576
#define UA_PUBLICATION_ID "314"
#define MT_SAMPLERATE AUDIO_SAMPLERATE
#define MT_MAXAUDIOFRAME 1440
#define MT_MAXRTPPACKET 1500
#define MT_DTMF_END_PACKETS 3
#define RTP_BUFFER_HIGH 24480
#define RTP_BUFFER_LOW 10
#define RTP_BUFFER_PREBUFFER 80
#define RTP_DECODED_CAPACITY 2048
#define DEFAULT_SUBSCRIPTION_TIME 1200
#define DEFAULT_SUBSCRIPTION_REFRESHTIME 500
#define PRESENCE_IN_REG_HEADER "PresenceInReg"
// Maximum UDP packet length
#define MAX_UDPPACKET_SIZE 65535
#define MAX_VALID_UDPPACKET_SIZE 2048
// AMR codec defines - it requires USE_AMR_CODEC defined
// #define USE_AMR_CODEC
#define MT_AMRNB_PAYLOADTYPE 112
#define MT_AMRNB_CODECNAME "amr"
#define MT_AMRNB_OCTET_PAYLOADTYPE 113
#define MT_AMRWB_PAYLOADTYPE 96
#define MT_AMRWB_CODECNAME "amr-wb"
#define MT_AMRWB_OCTET_PAYLOADTYPE 97
#define MT_GSMEFR_PAYLOADTYPE 126
#define MT_GSMEFR_CODECNAME "GERAN-EFR"
#define MT_EVS_PAYLOADTYPE 127
#define MT_EVS_CODECNAME "EVS"
// OPUS codec defines
// #define USE_OPUS_CODEC
#define MT_OPUS_CODEC_PT 106
// ILBC codec defines
#define MT_ILBC20_PAYLOADTYPE -1
#define MT_ILBC30_PAYLOADTYPE -1
// ISAC codec defines
#define MT_ISAC16K_PAYLOADTYPE -1
#define MT_ISAC32K_PAYLOADTYPE -1
// GSM HR payload type
#define MT_GSMHR_PAYLOADTYPE -1
// Mirror buffer capacity
#define MT_MIRROR_CAPACITY 32768
// Mirror buffer readiness threshold - 50 milliseconds
#define MT_MIRROR_PREBUFFER (MT_SAMPLERATE / 10)
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
# define TEXT(X) X
#endif
// In milliseconds
#define MT_SEVANA_FRAME_TIME 680
#endif
File diff suppressed because it is too large Load Diff
+82 -82
View File
@@ -24,118 +24,118 @@ class Session;
class Account: public resip::DnsResultSink class Account: public resip::DnsResultSink
{ {
friend class UserAgent; friend class UserAgent;
friend class NATDecorator; friend class NATDecorator;
public: public:
Account(PVariantMap config, UserAgent& agent); Account(PVariantMap config, UserAgent& agent);
~Account(); ~Account();
void start(); void start();
void stop(); void stop();
void refresh(); void refresh();
bool active(); bool active();
int id() const; int id() const;
enum class RegistrationState enum class RegistrationState
{ {
None, None,
Registering, Registering,
Reregistering, Reregistering,
Registered, Registered,
Unregistering Unregistering
}; };
RegistrationState registrationState(); RegistrationState registrationState();
/* Publishes new presence information */ /* Publishes new presence information */
void publishPresence(bool online, const std::string& content, int seconds = 600); void publishPresence(bool online, const std::string& content, int seconds = 600);
/* Stops publishing of presence */ /* Stops publishing of presence */
void stopPublish(); void stopPublish();
/* Starts observing on specified target / package */ /* Starts observing on specified target / package */
PClientObserver observe(const std::string& target, const std::string& package, void* tag); PClientObserver observe(const std::string& target, const std::string& package, void* tag);
/* Queues message to peer with specified mime type. Returns ID of message. */ /* 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); int sendMsg(const std::string& peer, const void* ptr, unsigned length, const std::string& mime, void* tag);
/* Returns name of account - <sip:user@domain> */ /* Returns name of account - <sip:user@domain> */
std::string name(); std::string name();
/* Updates account with configuration */ /* Updates account with configuration */
void setup(VariantMap& config); void setup(VariantMap& config);
/* Returns corresponding resiprocate profile */ /* Returns corresponding resiprocate profile */
std::shared_ptr<resip::UserProfile> getUserProfile() const { return mProfile; } resip::SharedPtr<resip::UserProfile> getUserProfile() const { return mProfile; }
typedef std::map<std::string, std::string> UserInfo; typedef std::map<std::string, std::string> UserInfo;
void setUserInfo(const UserInfo& info); void setUserInfo(const UserInfo& info);
UserInfo getUserInfo() const; UserInfo getUserInfo() const;
protected: protected:
PVariantMap mConfig; PVariantMap mConfig;
// Registration // Registration
ResipSession* mRegistration; ResipSession* mRegistration;
resip::ClientRegistrationHandle mRegistrationHandle; resip::ClientRegistrationHandle mRegistrationHandle;
resip::ClientPublicationHandle mPublication; resip::ClientPublicationHandle mPublication;
resip::TransportType mUsedTransport; resip::TransportType mUsedTransport;
RegistrationState mRegistrationState; RegistrationState mRegistrationState;
ice::NetworkAddress mExternalAddress; ice::NetworkAddress mExternalAddress;
std::shared_ptr<resip::UserProfile> mProfile; resip::SharedPtr<resip::UserProfile> mProfile;
UserAgent& mAgent; UserAgent& mAgent;
bool mPresenceOnline; bool mPresenceOnline;
std::string mPresenceContent; std::string mPresenceContent;
// Timer to refresh STUN server IP // Timer to refresh STUN server IP
ice::ICEScheduleTimer mRefreshStunServerIpTimer; ice::ICEScheduleTimer mRefreshStunServerIpTimer;
// Cached auth // Cached auth
resip::Auth mCachedAuth; resip::Auth mCachedAuth;
// Id of account // Id of account
int mId; int mId;
// User info about current state // User info about current state
UserInfo mUserInfo; UserInfo mUserInfo;
// List of client subscriptions sent from this account // List of client subscriptions sent from this account
typedef std::set<PClientObserver> ClientObserverSet; typedef std::set<PClientObserver> ClientObserverSet;
ClientObserverSet mClientObserverSet; ClientObserverSet mClientObserverSet;
void process(); void process();
// Method queries new stun server ip from dns (if stun server is specified as dns name) // Method queries new stun server ip from dns (if stun server is specified as dns name)
void queryStunServerIp(); void queryStunServerIp();
bool isResponsibleFor(const resip::NameAddr& addr); bool isResponsibleFor(const resip::NameAddr& addr);
enum class SecureScheme enum class SecureScheme
{ {
SipsAndTls, SipsAndTls,
SipsOnly, SipsOnly,
TlsOnly, TlsOnly,
Nothing Nothing
}; };
resip::NameAddr contact(SecureScheme ss = SecureScheme::SipsOnly); resip::NameAddr contact(SecureScheme ss = SecureScheme::SipsOnly);
// This method prepares configuration, creates ice stack and sets ownership to session // This method prepares configuration, creates ice stack and sets ownership to session
void prepareIceStack(Session* session, ice::AgentRole role); void prepareIceStack(Session* session, ice::AgentRole role);
void onSuccess(resip::ClientRegistrationHandle h, const resip::SipMessage& response); void onSuccess(resip::ClientRegistrationHandle h, const resip::SipMessage& response);
void onRemoved(resip::ClientRegistrationHandle h, const resip::SipMessage& response); void onRemoved(resip::ClientRegistrationHandle h, const resip::SipMessage& response);
void onFailure(resip::ClientRegistrationHandle, const resip::SipMessage& response); void onFailure(resip::ClientRegistrationHandle, const resip::SipMessage& response);
#pragma region DnsResultSink implementation #pragma region DnsResultSink implementation
void onDnsResult(const resip::DNSResult<resip::DnsHostRecord>&); void onDnsResult(const resip::DNSResult<resip::DnsHostRecord>&);
void onDnsResult(const resip::DNSResult<resip::DnsAAAARecord>&); void onDnsResult(const resip::DNSResult<resip::DnsAAAARecord>&);
void onDnsResult(const resip::DNSResult<resip::DnsSrvRecord>&); void onDnsResult(const resip::DNSResult<resip::DnsSrvRecord>&);
void onDnsResult(const resip::DNSResult<resip::DnsNaptrRecord>&); void onDnsResult(const resip::DNSResult<resip::DnsNaptrRecord>&);
void onDnsResult(const resip::DNSResult<resip::DnsCnameRecord>&); void onDnsResult(const resip::DNSResult<resip::DnsCnameRecord>&);
#pragma endregion #pragma endregion
static int generateId(); static int generateId();
static std::atomic_int IdGenerator; static resip::AtomicCounter IdGenerator;
}; };
typedef std::shared_ptr<Account> PAccount; typedef std::shared_ptr<Account> PAccount;
+38 -23
View File
@@ -14,7 +14,7 @@
#include "../helper/HL_Log.h" #include "../helper/HL_Log.h"
#include "../helper/HL_String.h" #include "../helper/HL_String.h"
#define LOG_SUBSYSTEM "engine" #define LOG_SUBSYSTEM "AudioProvider"
AudioProvider::AudioProvider(UserAgent& agent, MT::Terminal& terminal) AudioProvider::AudioProvider(UserAgent& agent, MT::Terminal& terminal)
:mUserAgent(agent), mTerminal(terminal), mState(0), :mUserAgent(agent), mTerminal(terminal), mState(0),
@@ -64,7 +64,7 @@ void AudioProvider::configureMediaObserver(MT::Stream::MediaObserver *observer,
} }
// Processes incoming data // Processes incoming data
void AudioProvider::processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& source) void AudioProvider::processData(PDatagramSocket s, const void* dataBuffer, int dataSize, InternetAddress& source)
{ {
if (!mActiveStream) if (!mActiveStream)
return; return;
@@ -77,7 +77,7 @@ void AudioProvider::processData(const PDatagramSocket& s, const void* dataBuffe
} }
// This method is called by user agent to send ICE packet from mediasocket // 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) void AudioProvider::sendData(PDatagramSocket s, InternetAddress& destination, const void* buffer, unsigned int size)
{ {
s->sendDatagram(destination, buffer, size); s->sendDatagram(destination, buffer, size);
} }
@@ -93,14 +93,13 @@ void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, Sdp
// Check if SRTP suite is found already or not // Check if SRTP suite is found already or not
if (mSrtpSuite == SRTP_NONE) if (mSrtpSuite == SRTP_NONE)
{ {
// RFC 4568 requires a unique tag per crypto attribute; use the suite id.
for (int suite = SRTP_AES_128_AUTH_80; suite <= SRTP_LAST; suite++) for (int suite = SRTP_AES_128_AUTH_80; suite <= SRTP_LAST; suite++)
sdp.addAttribute("crypto", resip::Data(createCryptoAttribute((SrtpSuite)suite, suite))); sdp.addAttribute("crypto", resip::Data(createCryptoAttribute((SrtpSuite)suite)));
} }
else else
// Answer/re-offer: echo the tag of the negotiated attribute. sdp.addAttribute("crypto", resip::Data(createCryptoAttribute(mSrtpSuite)));
sdp.addAttribute("crypto", resip::Data(createCryptoAttribute(mSrtpSuite, mSrtpTag)));
} }
#if defined(USE_RESIP_INTEGRATION)
// Use CodecListPriority mCodecPriority adapter to work with codec priorities // Use CodecListPriority mCodecPriority adapter to work with codec priorities
if (mAvailableCodecs.empty()) if (mAvailableCodecs.empty())
@@ -115,7 +114,7 @@ void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, Sdp
if (mRemoteTelephoneCodec) if (mRemoteTelephoneCodec)
sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent); sdp.addCodec(resip::SdpContents::Session::Codec::TelephoneEvent);
} }
#endif
// Publish stream state // Publish stream state
const char* attr = nullptr; const char* attr = nullptr;
@@ -126,8 +125,6 @@ void AudioProvider::updateSdpOffer(resip::SdpContents::Session::Medium& sdp, Sdp
{ {
case msSendonly: attr = "recvonly"; break; case msSendonly: attr = "recvonly"; break;
case msInactive: attr = "recvonly"; break; case msInactive: attr = "recvonly"; break;
case msRecvonly:
case msSendRecv: break; // Do nothing here
} }
break; break;
@@ -230,8 +227,10 @@ bool AudioProvider::processSdpOffer(const resip::SdpContents::Session::Medium& m
for (int localIndex=0; localIndex<mCodecPriority.count(mTerminal.codeclist()); localIndex++) for (int localIndex=0; localIndex<mCodecPriority.count(mTerminal.codeclist()); localIndex++)
{ {
MT::Codec::Factory& factory = mCodecPriority.codecAt(mTerminal.codeclist(), localIndex); MT::Codec::Factory& factory = mCodecPriority.codecAt(mTerminal.codeclist(), localIndex);
#if defined(USE_RESIP_INTEGRATION)
if ((pt = factory.processSdp(media.codecs(), sdpDirection)) != -1) if ((pt = factory.processSdp(media.codecs(), sdpDirection)) != -1)
mAvailableCodecs.push_back(RemoteCodec(&factory, pt)); mAvailableCodecs.push_back(RemoteCodec(&factory, pt));
#endif
} }
if (!mAvailableCodecs.size()) if (!mAvailableCodecs.size())
@@ -248,13 +247,11 @@ bool AudioProvider::processSdpOffer(const resip::SdpContents::Session::Medium& m
{ {
const resip::Data& attr = *attrIter; const resip::Data& attr = *attrIter;
ByteBuffer tempkey; ByteBuffer tempkey;
int tag = 1; SrtpSuite suite = processCryptoAttribute(attr, tempkey);
SrtpSuite suite = processCryptoAttribute(attr, tempkey, &tag); if (suite > ss)
if (srtpSuiteStrength(suite) > srtpSuiteStrength(ss))
{ {
ss = suite; ss = suite;
mSrtpSuite = suite; mSrtpSuite = suite;
mSrtpTag = tag;
key = tempkey; key = tempkey;
} }
} }
@@ -299,30 +296,40 @@ MT::PStream AudioProvider::activeStream()
return mActiveStream; return mActiveStream;
} }
std::string AudioProvider::createCryptoAttribute(SrtpSuite suite, int tag) std::string AudioProvider::createCryptoAttribute(SrtpSuite suite)
{ {
if (!mActiveStream) if (!mActiveStream)
return ""; return "";
// Use tag 1 - it is ok, as we use only single crypto attribute
int srtpTag = 1;
// Print key to base64 string // Print key to base64 string
PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first; PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first;
if (!keyBuffer)
return "";
resip::Data d(keyBuffer->data(), keyBuffer->size()); resip::Data d(keyBuffer->data(), keyBuffer->size());
resip::Data keyText = d.base64encode(); resip::Data keyText = d.base64encode();
return std::format("{} {} inline:{}", tag, toString(suite), keyText.c_str()); // Create "crypto" attribute value
char buffer[512];
const char* suiteName = NULL;
switch (suite)
{
case SRTP_AES_128_AUTH_80: suiteName = SRTP_SUITE_NAME_1; break;
case SRTP_AES_256_AUTH_80: suiteName = SRTP_SUITE_NAME_2; break;
default: assert(0);
}
sprintf(buffer, "%d %s inline:%s", srtpTag, suiteName, keyText.c_str());
return buffer;
} }
SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key, int* tag) SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key)
{ {
int srtpTag = 0; int srtpTag = 0;
char suite[64], keyChunk[256]; char suite[64], keyChunk[256];
int components = sscanf(value.c_str(), "%d %63s inline: %255s", &srtpTag, suite, keyChunk); int components = sscanf(value.c_str(), "%d %63s inline: %255s", &srtpTag, suite, keyChunk);
if (components != 3) if (components != 3)
return SRTP_NONE; return SRTP_NONE;
if (tag)
*tag = srtpTag;
const char* delimiter = strchr(keyChunk, '|'); const char* delimiter = strchr(keyChunk, '|');
resip::Data keyText; resip::Data keyText;
@@ -334,7 +341,15 @@ SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBu
resip::Data rawkey = keyText.base64decode(); resip::Data rawkey = keyText.base64decode();
key = ByteBuffer(rawkey.c_str(), rawkey.size()); key = ByteBuffer(rawkey.c_str(), rawkey.size());
return toSrtpSuite(suite); // Open srtp
SrtpSuite result = SRTP_NONE;
if (strcmp(suite, SRTP_SUITE_NAME_1) == 0)
result = SRTP_AES_128_AUTH_80;
else
if (strcmp(suite, SRTP_SUITE_NAME_2) == 0)
result = SRTP_AES_256_AUTH_80;
return result;
} }
void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs) void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs)
@@ -372,4 +387,4 @@ void AudioProvider::setStateImpl(unsigned int state) {
if (mActiveStream) if (mActiveStream)
mActiveStream->setState(state); mActiveStream->setState(state);
} }
+7 -6
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -12,7 +12,9 @@
#include "../media/MT_Box.h" #include "../media/MT_Box.h"
#include "../media/MT_Stream.h" #include "../media/MT_Stream.h"
#include "../media/MT_Codec.h" #include "../media/MT_Codec.h"
#include "../audio/Audio_Interface.h"
#include "rutil/ThreadIf.hxx"
#include <vector> #include <vector>
#include <string> #include <string>
@@ -35,10 +37,10 @@ public:
void setDestinationAddress(const RtpPair<InternetAddress>& addr) override; void setDestinationAddress(const RtpPair<InternetAddress>& addr) override;
// Processes incoming data // Processes incoming data
void processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& source) override; void processData(PDatagramSocket s, const void* dataBuffer, int dataSize, InternetAddress& source) override;
// This method is called by user agent to send ICE packet from mediasocket // 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; void sendData(PDatagramSocket s, InternetAddress& destination, const void* dataBuffer, unsigned int datasize) override;
// Updates SDP offer // Updates SDP offer
void updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction) override; void updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction) override;
@@ -74,7 +76,6 @@ public:
void setupMirror(bool enable); void setupMirror(bool enable);
void configureMediaObserver(MT::Stream::MediaObserver* observer, void* userTag); void configureMediaObserver(MT::Stream::MediaObserver* observer, void* userTag);
static SrtpSuite processCryptoAttribute(const resip::Data& value, ByteBuffer& key, int* tag = nullptr);
protected: protected:
// SDP's stream name // SDP's stream name
@@ -93,7 +94,6 @@ protected:
unsigned mState; unsigned mState;
SrtpSuite mSrtpSuite; SrtpSuite mSrtpSuite;
int mSrtpTag = 1; // RFC 4568 tag of the negotiated crypto attribute
struct RemoteCodec struct RemoteCodec
{ {
RemoteCodec(MT::Codec::Factory* factory, int payloadType) RemoteCodec(MT::Codec::Factory* factory, int payloadType)
@@ -110,7 +110,8 @@ protected:
MT::Stream::MediaObserver* mMediaObserver = nullptr; MT::Stream::MediaObserver* mMediaObserver = nullptr;
void* mMediaObserverTag = nullptr; void* mMediaObserverTag = nullptr;
std::string createCryptoAttribute(SrtpSuite suite, int tag); std::string createCryptoAttribute(SrtpSuite suite);
SrtpSuite processCryptoAttribute(const resip::Data& value, ByteBuffer& key);
void findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs); void findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs);
// Implements setState() logic. This allows to be called from constructor (it is not virtual function) // Implements setState() logic. This allows to be called from constructor (it is not virtual function)
+45 -45
View File
@@ -8,67 +8,67 @@
bool DataProvider::isSupported(const char* name) bool DataProvider::isSupported(const char* name)
{ {
return !strcmp(name, "audio"); return !strcmp(name, "audio");
//return (!strcmp(name, "screen") || !strcmp(name, "data") || !strcmp(name, "audio") || !strcmp(name, "video")); //return (!strcmp(name, "screen") || !strcmp(name, "data") || !strcmp(name, "audio") || !strcmp(name, "video"));
} }
void DataProvider::pause() void DataProvider::pause()
{ {
/*if (state() & STATE_SIPRECV) /*if (state() & STATE_SIPRECV)
setState( state() & ~STATE_SIPRECV );*/ setState( state() & ~STATE_SIPRECV );*/
// Stop receive RTP stream // Stop receive RTP stream
if (state() & (int)StreamState::Receiving) if (state() & (int)StreamState::Receiving)
setState( state() & ~(int)StreamState::Receiving ); setState( state() & ~(int)StreamState::Receiving );
mActive = mfPaused; mActive = mfPaused;
} }
void DataProvider::resume() void DataProvider::resume()
{ {
// Tell remote peer about resumed receiving in SDP // Tell remote peer about resumed receiving in SDP
//setState( state() | STATE_SIPRECV ); //setState( state() | STATE_SIPRECV );
// Start receive RTP stream // Start receive RTP stream
setState( state() | (int)StreamState::Receiving ); setState( state() | (int)StreamState::Receiving );
mActive = mfActive; mActive = mfActive;
} }
bool DataProvider::processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection) bool DataProvider::processSdpOffer(const resip::SdpContents::Session::Medium& media, SdpDirection sdpDirection)
{ {
// Process paused and inactive calls // Process paused and inactive calls
if (media.exists("sendonly")) 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)
{ {
mRemoteState = msSendonly; case mfActive:
setState(state() & ~(int)StreamState::Sending); setState(state() | (int)StreamState::Sending | (int)StreamState::Receiving);
} break;
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: case mfPaused:
setState(state() | (int)StreamState::Sending ); setState(state() | (int)StreamState::Sending );
break; break;
} }
} }
return true; return true;
} }
+53 -52
View File
@@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "resip/stack/SdpContents.hxx" #include "resip/stack/SdpContents.hxx"
#include "rutil/SharedPtr.hxx"
#include "../helper/HL_InternetAddress.h" #include "../helper/HL_InternetAddress.h"
#include "../helper/HL_NetworkSocket.h" #include "../helper/HL_NetworkSocket.h"
@@ -19,70 +20,70 @@
class DataProvider class DataProvider
{ {
public: public:
enum MediaFlow enum MediaFlow
{ {
mfActive, mfActive,
mfPaused mfPaused
}; };
enum MediaState
{
msSendRecv,
msSendonly,
msRecvonly,
msInactive
};
enum MediaState static bool isSupported(const char* name);
{
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;
// Returns provider RTP name // Processes incoming data
virtual std::string streamName() = 0; virtual void processData(PDatagramSocket s, const void* dataBuffer, int dataSize, InternetAddress& address) = 0;
// Returns provider RTP profile name // This method is called by user agent to send ICE packet from mediasocket
virtual std::string streamProfile() = 0; virtual void sendData(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;
// Sets destination IP address // Called by user agent when session is deleted. Comes after sessionTerminated().
virtual void setDestinationAddress(const RtpPair<InternetAddress>& addr) = 0; virtual void sessionDeleted() = 0;
// Processes incoming data // Called by user agent when session is terminated.
virtual void processData(const PDatagramSocket& s, const void* dataBuffer, int dataSize, InternetAddress& address) = 0; virtual void sessionTerminated() = 0;
// This method is called by user agent to send ICE packet from mediasocket // Called by user agent when session is started.
virtual void sendData(const PDatagramSocket& s, InternetAddress& destination, const void* dataBuffer, unsigned int datasize) = 0; virtual void sessionEstablished(int conntype) = 0;
// Updates SDP offer // Called by user agent to save media socket for this provider
virtual void updateSdpOffer(resip::SdpContents::Session::Medium& sdp, SdpDirection direction) = 0; 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 when session is deleted. Comes after sessionTerminated(). // Called by user agent to process media stream description from remote peer.
virtual void sessionDeleted() = 0; // 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();
// Called by user agent when session is terminated. virtual MT::Statistics getStatistics() = 0;
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: protected:
MediaFlow mActive; MediaFlow mActive;
MediaState mRemoteState; MediaState mRemoteState;
}; };
typedef std::shared_ptr<DataProvider> PDataProvider; typedef std::shared_ptr<DataProvider> PDataProvider;
File diff suppressed because it is too large Load Diff
+304 -305
View File
@@ -47,7 +47,7 @@
#include "../ice/ICETime.h" #include "../ice/ICETime.h"
#include <sstream> #include <sstream>
#include <time.h> #include <time.h>
#include "../engine_config.h" #include "../config.h"
#include "EP_Session.h" #include "EP_Session.h"
#include "EP_Observer.h" #include "EP_Observer.h"
#include "EP_DataProvider.h" #include "EP_DataProvider.h"
@@ -70,52 +70,52 @@ enum
enum enum
{ {
CONFIG_IPV4 = 0, // Use IP4 CONFIG_IPV4 = 0, // Use IP4
CONFIG_IPV6, // Use IP6. CONFIG_IPV6, // Use IP6.
CONFIG_USERNAME, // Username. String value. CONFIG_USERNAME, // Username. String value.
CONFIG_DOMAIN, // Domain. String value. CONFIG_DOMAIN, // Domain. String value.
CONFIG_PASSWORD, // Password. String value. CONFIG_PASSWORD, // Password. String value.
CONFIG_RINSTANCE, // Determines if SIP rinstance field has to be used during registration. Boolean 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_INSTANCE_ID, // Instance id. It is alternative option to rinstance.
CONFIG_DISPLAYNAME, // Optional user display name. String value. CONFIG_DISPLAYNAME, // Optional user display name. String value.
CONFIG_DOMAINPORT, // Optional domain port number. Integer value. CONFIG_DOMAINPORT, // Optional domain port number. Integer value.
CONFIG_REGISTERDURATION, // Wanted duration for registration. Integer value. It is MANDATORY 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_RPORT, // Use SIP rport field. Recommended to set it to true. Boolean value.
CONFIG_KEEPALIVETIME, // Interval between UDP keep-alive messages. 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_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_ICETIMEOUT, // Optional timeout for ICE connectivity checks and candidate gathering. Integer value.
CONFIG_ICEUSERNAME, // Optional username for TURN server. String value. CONFIG_ICEUSERNAME, // Optional username for TURN server. String value.
CONFIG_ICEPASSWORD, // Optional password 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_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_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_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_STUNSERVER_PORT, // Port number of STUN/TURN server. Integer value.
CONFIG_USERAGENT, // Name of user agent in SIP headers. String 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_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_TRANSPORT, // 0 - all transports, 1 - UDP, 2 - TCP, 3 - TLS,
CONFIG_SUBSCRIPTION_TIME, // Subscription time (in seconds) CONFIG_SUBSCRIPTION_TIME, // Subscription time (in seconds)
CONFIG_SUBSCRIPTION_REFRESHTIME, // Refresh interval for subscriptions CONFIG_SUBSCRIPTION_REFRESHTIME, // Refresh interval for subscriptions
CONFIG_DNS_CACHE_TIME, // DNS cache time; default is 86400 seconds CONFIG_DNS_CACHE_TIME, // DNS cache time; default is 86400 seconds
CONFIG_PRESENCE_ID, // Tuple ID used in presence publishing; determines source device CONFIG_PRESENCE_ID, // Tuple ID used in presence publishing; determines source device
CONFIG_ROOTCERT, // Additional root cert in PEM format; string. 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_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_RTCP_ATTR, // Use "rtcp" attribute in sdp. Default value is true.
CONFIG_MULTIPLEXING, // Do rtp/rtcp multiplexing CONFIG_MULTIPLEXING, // Do rtp/rtcp multiplexing
CONFIG_DEFERRELAYED, // Defer relayed media path CONFIG_DEFERRELAYED, // Defer relayed media path
CONFIG_PROXY, // Proxy host name or IP address CONFIG_PROXY, // Proxy host name or IP address
CONFIG_PROXYPORT, // Proxy port number CONFIG_PROXYPORT, // Proxy port number
CONFIG_CODEC_PRIORITY, // Another VariantMap with codec priorities, CONFIG_CODEC_PRIORITY, // Another VariantMap with codec priorities,
CONFIG_ACCOUNT, // VariantMap with account configuration CONFIG_ACCOUNT, // VariantMap with account configuration
CONFIG_EXTERNALIP, // Use external/public IP in outgoing requests CONFIG_EXTERNALIP, // Use external/public IP in outgoing requests
CONFIG_OWN_DNS, // Use predefined DNS servers CONFIG_OWN_DNS, // Use predefined DNS servers
CONFIG_REGID // reg-id value from RFC5626, CONFIG_REGID // reg-id value from RFC5626
}; };
// Conntype parameter for OnSessionEstablished event // Conntype parameter for OnSessionEstablished event
enum enum
{ {
EV_SIP = 1, EV_SIP = 1,
EV_ICE = 2 EV_ICE = 2
}; };
class UserAgent; class UserAgent;
@@ -124,7 +124,7 @@ class UserAgent;
class SIPAction class SIPAction
{ {
public: public:
virtual void Run(UserAgent& ua) = 0; virtual void Run(UserAgent& ua) = 0;
}; };
typedef std::vector<SIPAction*> SIPActionVector; typedef std::vector<SIPAction*> SIPActionVector;
@@ -132,15 +132,15 @@ typedef std::vector<SIPAction*> SIPActionVector;
// Session termination reason // Session termination reason
enum enum
{ {
Error, Error,
Timeout, Timeout,
Replaced, Replaced,
LocalBye, LocalBye,
RemoteBye, RemoteBye,
LocalCancel, LocalCancel,
RemoteCancel, RemoteCancel,
Rejected, //Only as UAS, UAC has distinct onFailure callback Rejected, //Only as UAS, UAC has distinct onFailure callback
Referred Referred
}; };
class UserAgent: public resip::ClientRegistrationHandler, class UserAgent: public resip::ClientRegistrationHandler,
@@ -152,239 +152,238 @@ class UserAgent: public resip::ClientRegistrationHandler,
public resip::ServerSubscriptionHandler, public resip::ServerSubscriptionHandler,
public resip::ClientPagerMessageHandler, public resip::ClientPagerMessageHandler,
public resip::ServerPagerMessageHandler, public resip::ServerPagerMessageHandler,
public resip::ClientPublicationHandler public resip::ClientPublicationHandler,
//public resip::InternalTransport::TransportLogger public resip::InternalTransport::TransportLogger
{ {
friend class Account; friend class Account;
friend class Session; friend class Session;
friend class ResipSession; friend class ResipSession;
friend class NATDecorator; friend class NATDecorator;
friend class WatcherQueue; friend class WatcherQueue;
public: public:
/* Compares two sip addresses. Returns true if they represent the same entity - user and domain are the same. Otherwise returns false. */ /* 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 bool compareSipAddresses(std::string sip1, std::string sip2);
static std::string formatSipAddress(const std::string& sip); static std::string formatSipAddress(std::string sip);
static bool isSipAddressValid(const std::string& sip); static bool isSipAddressValid(std::string sip);
struct SipAddress struct SipAddress
{ {
bool mValid; bool mValid;
std::string mScheme; std::string mScheme;
std::string mUsername; std::string mUsername;
std::string mDomain; std::string mDomain;
std::string mDisplayname; std::string mDisplayname;
}; };
static SipAddress parseSipAddress(const std::string& sip); static SipAddress parseSipAddress(const std::string& sip);
UserAgent(); UserAgent();
virtual ~UserAgent(); virtual ~UserAgent();
/* Brings user agent online. Basically it creates a signalling socket(s). /* Brings user agent online. Basically it creates a signalling socket(s).
This is asynchronous method. */ This is asynchronous method. */
void start(); void start();
/* Shutdowns user agent. It closes all sessions, tries to unregister from server and disconnects from it. /* 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 */ This is asynchronous method. onStop() event will be called later */
void shutdown(); 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();
/* Emergency stop. Please always call shutdown() before this. Kills registration, sessions & presence - everything. onStop() is called in context of this method. */ /* Checks if user agent is active (started). */
void stop(); bool active();
/* Used to refresh existing registration(s), publication, subscriptions. */
void refresh();
/* Checks if user agent is active (started). */ /* Runs sip & ice stacks. Event handlers are called in its context. */
bool active(); void process();
/* Adds root cert in PEM format. Usable after start() call. */
void addRootCert(const ByteBuffer& data);
/* Used to refresh existing registration(s), publication, subscriptions. */ PAccount createAccount(PVariantMap config);
void refresh(); void deleteAccount(PAccount account);
/* Runs sip & ice stacks. Event handlers are called in its context. */ /* Creates session. Returns session ID. */
void process(); PSession createSession(PAccount account);
// Must be called when IP interface list is changed
void updateInterfaceList();
/* Adds root cert in PEM format. Usable after start() call. */ // Called on new incoming session; providers shoukld
void addRootCert(const ByteBuffer& data); virtual PDataProvider onProviderNeeded(const std::string& name) = 0;
PAccount createAccount(PVariantMap config); // Called on new session offer
void deleteAccount(PAccount account); 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;
/* Creates session. Returns session ID. */ // Called when client session gets
PSession createSession(PAccount account); virtual void onSessionProvisional(PSession s, int code) = 0;
// Must be called when IP interface list is changed // Called when user agent started
void updateInterfaceList(); virtual void onStart(int errorcode) = 0;
// Called when user agent stopped
virtual void onStop() = 0;
// Called on new incoming session; providers shoukld // Called when account registered
virtual PDataProvider onProviderNeeded(const std::string& name) = 0; virtual void onAccountStart(PAccount account) = 0;
// Called on new session offer // Called when account removed or failed (non zero error code)
virtual void onNewSession(PSession s) = 0; virtual void onAccountStop(PAccount account, int error) = 0;
// Called when session is terminated // Called when connectivity checks failed.
virtual void onSessionTerminated(PSession s, int responsecode, int reason) = 0; virtual void onConnectivityFailed(PSession s) = 0;
// Called when session is established ok i.e. after all ICE signalling is finished // Called when new candidate is gathered
// Conntype is type of establish event - EV_SIP or EV_ICE virtual void onCandidateGathered(PSession s, const char* address);
virtual void onSessionEstablished(PSession s, int conntype, const RtpPair<InternetAddress>& p) = 0;
// Called when network change detected
virtual void onNetworkChange(PSession s) = 0;
// Called when client session gets // Called when all candidates are gathered
virtual void onSessionProvisional(PSession s, int code) = 0; virtual void onGathered(PSession s);
// Called when user agent started // Called when new connectivity check is finished
virtual void onStart(int errorcode) = 0; 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;
// Called when user agent stopped // Subscribe/publish presence methods
virtual void onStop() = 0; 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);
// Called when account registered virtual void onPresenceUpdate(PClientObserver observer, const std::string& peer, bool online, const std::string& content);
virtual void onAccountStart(PAccount account) = 0; 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);
// Called when account removed or failed (non zero error code) virtual void onMessageSent(PAccount account, int id, const std::string& peer, void* tag);
virtual void onAccountStop(PAccount account, int error) = 0;
// Configuration methods
// Called when connectivity checks failed. VariantMap& config();
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: public:
// InviteSessionHandler implementation // InviteSessionHandler implementation
#pragma region InviteSessionHandler implementation #pragma region InviteSessionHandler implementation
/// called when an initial INVITE or the intial response to an outoing invite /// 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::ClientInviteSessionHandle, resip::InviteSession::OfferAnswerType oat, const resip::SipMessage& msg) override;
virtual void onNewSession(resip::ServerInviteSessionHandle, 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 /// Received a failure response from UAS
virtual void onFailure(resip::ClientInviteSessionHandle, const resip::SipMessage& msg) override; 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 an in-dialog provisional response is received that contains an SDP body /// called when dialog enters the Early state - typically after getting 18x
virtual void onEarlyMedia(resip::ClientInviteSessionHandle, const resip::SipMessage&, const resip::SdpContents&) override; virtual void onProvisional(resip::ClientInviteSessionHandle, const resip::SipMessage&) override;
/// called when dialog enters the Early state - typically after getting 18x /// called when a dialog initiated as a UAC enters the connected state
virtual void onProvisional(resip::ClientInviteSessionHandle, const resip::SipMessage&) override; virtual void onConnected(resip::ClientInviteSessionHandle, const resip::SipMessage& msg) override;
/// called when a dialog initiated as a UAC enters the connected state /// called when a dialog initiated as a UAS enters the connected state
virtual void onConnected(resip::ClientInviteSessionHandle, const resip::SipMessage& msg) override; virtual void onConnected(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
/// called when a dialog initiated as a UAS enters the connected state virtual void onTerminated(resip::InviteSessionHandle, resip::InviteSessionHandler::TerminatedReason reason, const resip::SipMessage* related=0) override;
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 fork that was created through a 1xx never receives a 2xx /// called when a 3xx with valid targets is encountered in an early dialog
/// because another fork answered and this fork was canceled by a proxy. /// This is different then getting a 3xx in onTerminated, as another
virtual void onForkDestroyed(resip::ClientInviteSessionHandle) override; /// 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 a 3xx with valid targets is encountered in an early dialog /// called when an SDP answer is received - has nothing to do with user
/// This is different then getting a 3xx in onTerminated, as another /// answering the call
/// request will be attempted, so the DialogSet will not be destroyed. virtual void onAnswer(resip::InviteSessionHandle, const resip::SipMessage& msg, const resip::SdpContents&) override;
/// 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 /// called when an SDP offer is received - must send an answer soon after this
/// answering the call virtual void onOffer(resip::InviteSessionHandle, const resip::SipMessage& msg, const resip::SdpContents&) override;
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 /// called when an Invite w/out SDP is sent, or any other context which
virtual void onOffer(resip::InviteSessionHandle, const resip::SipMessage& msg, const resip::SdpContents&) override; /// 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 an Invite w/out SDP is sent, or any other context which /// called when response to INFO message is received
/// requires an SDP offer from the user virtual void onInfoSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
virtual void onOfferRequired(resip::InviteSessionHandle, const resip::SipMessage& msg) override; virtual void onInfoFailure(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
/// called if an offer in a UPDATE or re-INVITE was rejected - not real /// called when MESSAGE message is received
/// useful. A SipMessage is provided if one is available virtual void onMessage(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
virtual void onOfferRejected(resip::InviteSessionHandle, const resip::SipMessage* msg) override;
/// called when INFO message is received /// called when response to MESSAGE message is received
virtual void onInfo(resip::InviteSessionHandle, const resip::SipMessage& msg) override; virtual void onMessageSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
virtual void onMessageFailure(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
/// called when response to INFO message is received /// called when an REFER message is received. The refer is accepted or
virtual void onInfoSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg) override; /// rejected using the server subscription. If the offer is accepted,
virtual void onInfoFailure(resip::InviteSessionHandle, const resip::SipMessage& msg) override; /// 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;
/// called when MESSAGE message is received virtual void onReferNoSub(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
virtual void onMessage(resip::InviteSessionHandle, const resip::SipMessage& msg) override;
/// called when response to MESSAGE message is received /// called when an REFER message receives a failure response
virtual void onMessageSuccess(resip::InviteSessionHandle, const resip::SipMessage& msg) override; virtual void onReferRejected(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 /// called when an REFER message receives an accepted response
/// rejected using the server subscription. If the offer is accepted, virtual void onReferAccepted(resip::InviteSessionHandle, resip::ClientSubscriptionHandle, const resip::SipMessage& msg) override;
/// 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 #pragma endregion
// ClientRegistrationHandler implementation // ClientRegistrationHandler implementation
#pragma region ClientRegistrationHandler implementation #pragma region ClientRegistrationHandler implementation
/// Called when registraion succeeds or each time it is sucessfully /// Called when registraion succeeds or each time it is sucessfully
/// refreshed. /// refreshed.
void onSuccess(resip::ClientRegistrationHandle, const resip::SipMessage& response) override; void onSuccess(resip::ClientRegistrationHandle, const resip::SipMessage& response) override;
// Called when all of my bindings have been removed // Called when all of my bindings have been removed
void onRemoved(resip::ClientRegistrationHandle, const resip::SipMessage& response) override; void onRemoved(resip::ClientRegistrationHandle, const resip::SipMessage& response) override;
/// call on Retry-After failure. /// call on Retry-After failure.
/// return values: -1 = fail, 0 = retry immediately, N = retry in N seconds /// return values: -1 = fail, 0 = retry immediately, N = retry in N seconds
int onRequestRetry(resip::ClientRegistrationHandle, int retrySeconds, const resip::SipMessage& response) override; int onRequestRetry(resip::ClientRegistrationHandle, int retrySeconds, const resip::SipMessage& response) override;
/// Called if registration fails, usage will be destroyed (unless a /// Called if registration fails, usage will be destroyed (unless a
/// Registration retry interval is enabled in the Profile) /// Registration retry interval is enabled in the Profile)
void onFailure(resip::ClientRegistrationHandle, const resip::SipMessage& response) override; void onFailure(resip::ClientRegistrationHandle, const resip::SipMessage& response) override;
#pragma endregion #pragma endregion
#pragma region ExternalLogger implementation #pragma region ExternalLogger implementation
/** return true to also do default logging, false to suppress default logging. */ /** return true to also do default logging, false to suppress default logging. */
virtual bool operator()(resip::Log::Level level, virtual bool operator()(resip::Log::Level level,
const resip::Subsystem& subsystem, const resip::Subsystem& subsystem,
const resip::Data& appName, const resip::Data& appName,
const char* file, const char* file,
int line, int line,
const resip::Data& message, const resip::Data& message,
const resip::Data& messageWithHeaders, const resip::Data& messageWithHeaders) override;
const resip::Data& instanceName) override;
#pragma endregion #pragma endregion
#pragma region DnsResultSink implementation #pragma region DnsResultSink implementation
@@ -398,96 +397,96 @@ public:
#pragma endregion #pragma endregion
#pragma region TransportLogger implementation #pragma region TransportLogger implementation
void onSipMessage(int flow, const char* msg, unsigned int length, const sockaddr* addr, unsigned int addrlen); void onSipMessage(int flow, const char* msg, unsigned int length, const sockaddr* addr, unsigned int addrlen) override;
#pragma endregion #pragma endregion
#pragma region ClientPublicationHandler #pragma region ClientPublicationHandler
void onSuccess(resip::ClientPublicationHandle, const resip::SipMessage& status) override; void onSuccess(resip::ClientPublicationHandle, const resip::SipMessage& status) override;
void onRemove(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; void onFailure(resip::ClientPublicationHandle, const resip::SipMessage& status) override;
int onRequestRetry(resip::ClientPublicationHandle, int retrySeconds, const resip::SipMessage& status) override; int onRequestRetry(resip::ClientPublicationHandle, int retrySeconds, const resip::SipMessage& status) override;
#pragma endregion #pragma endregion
#pragma region SubscriptionHandler #pragma region SubscriptionHandler
void onUpdate(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify); void onUpdate(resip::ClientSubscriptionHandle h, const resip::SipMessage& notify);
void onUpdatePending(resip::ClientSubscriptionHandle, const resip::SipMessage& notify, bool outOfOrder) override; void onUpdatePending(resip::ClientSubscriptionHandle, const resip::SipMessage& notify, bool outOfOrder) override;
void onUpdateActive(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;
//unknown Subscription-State value /// called to allow app to adorn a message.
void onUpdateExtension(resip::ClientSubscriptionHandle, const resip::SipMessage& notify, bool outOfOrder) override; void onReadyToSend(resip::ClientSubscriptionHandle, resip::SipMessage& msg) override;
int onRequestRetry(resip::ClientSubscriptionHandle, int retrySeconds, const resip::SipMessage& notify) override; void onNotifyNotReceived(resip::ClientSubscriptionHandle) override;
//subscription can be ended through a notify or a failure response. /// Called when a TCP or TLS flow to the server has terminated. This can be caused by socket
void onTerminated(resip::ClientSubscriptionHandle, const resip::SipMessage* msg) override; /// errors, or missing CRLF keep alives pong responses from the server.
//not sure if this has any value. // Called only if clientOutbound is enabled on the UserProfile and the first hop server
void onNewSubscription(resip::ClientSubscriptionHandle, const resip::SipMessage& notify) override; /// supports RFC5626 (outbound).
/// Default implementation is to re-form the subscription using a new flow
/// called to allow app to adorn a message. void onFlowTerminated(resip::ClientSubscriptionHandle) override;
void onReadyToSend(resip::ClientSubscriptionHandle, resip::SipMessage& msg) override; void onNewSubscription(resip::ServerSubscriptionHandle, const resip::SipMessage& sub) override;
void onNotifyNotReceived(resip::ClientSubscriptionHandle) override; void onTerminated(resip::ServerSubscriptionHandle) 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 endregion
#pragma region PagerHandler #pragma region PagerHandler
void onSuccess(resip::ClientPagerMessageHandle, const resip::SipMessage& status) override; 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 onFailure(resip::ClientPagerMessageHandle, const resip::SipMessage& status, std::unique_ptr<resip::Contents> contents) override;
void onMessageArrived(resip::ServerPagerMessageHandle, const resip::SipMessage& message) override; void onMessageArrived(resip::ServerPagerMessageHandle, const resip::SipMessage& message) override;
#pragma endregion #pragma endregion
void onDumCanBeDeleted() override; void onDumCanBeDeleted() override;
protected: protected:
// Mutex to protect this instance // Mutex to protect this instance
Mutex mGuard; Mutex mGuard;
// Smart pointer to resiprocate's master profile instance. The stack configuration holds here. // Smart pointer to resiprocate's master profile instance. The stack configuration holds here.
std::shared_ptr<resip::MasterProfile> mProfile; resip::SharedPtr<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;
// Resiprocate's SIP stack object pointer typedef std::map<int, PSession> SessionMap;
resip::SipStack* mStack;
// Session's map
SessionMap mSessionMap;
// Resiprocate's dialog usage manager object pointer // Used configuration
resip::DialogUsageManager* mDum; VariantMap mConfig;
// List of available transports. They are owned by SipStack - so there is no need to delete instances in UserAgent. // Action vector
std::vector<resip::InternalTransport*> mTransportList; SIPActionVector mActionVector;
typedef std::map<int, PClientObserver> ClientObserverMap;
ClientObserverMap mClientObserverMap;
typedef std::map<int, PSession> SessionMap; typedef std::map<int, PServerObserver> ServerObserverMap;
ServerObserverMap mServerObserverMap;
typedef std::set<PAccount> AccountSet;
AccountSet mAccountSet;
// Session's map // Constructs and sends INVITE to remote peer. Remote peer address is stored inside session object.
SessionMap mSessionMap; void sendOffer(Session* session);
void internalStopSession(Session& session);
void processWatchingList();
bool handleMultipartRelatedNotify(const resip::SipMessage& notify);
// Used configuration PSession getUserSession(int sessionId);
VariantMap mConfig; PAccount getAccount(const resip::NameAddr& myAddr);
PAccount getAccount(Account* account);
// Action vector PAccount getAccount(int sessionId);
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 #endif
+127 -124
View File
@@ -2,178 +2,181 @@
#include "EP_Engine.h" #include "EP_Engine.h"
WatcherQueue::WatcherQueue(UserAgent& ua) WatcherQueue::WatcherQueue(UserAgent& ua)
:mActiveId(0), mAgent(ua) :mActiveId(0), mAgent(ua)
{} {}
WatcherQueue::~WatcherQueue() WatcherQueue::~WatcherQueue()
{} {}
int WatcherQueue::add(const std::string& peer, const std::string& package, void* tag) int WatcherQueue::add(std::string peer, std::string package, void* tag)
{ {
ice::Lock l(mGuard); ice::Lock l(mGuard);
// Check if queue has similar item // Check if queue has similar item
for (unsigned i=0; i<mItemList.size(); i++) for (unsigned i=0; i<mItemList.size(); i++)
{ {
Item& item = mItemList[i]; Item& item = mItemList[i];
if (item.mTarget == peer && item.mPackage == package && item.mState != Item::State_Deleting) if (item.mTarget == peer && item.mPackage == package &&
return item.mId; item.mState != Item::State_Deleting)
} return item.mId;
}
Item item; Item item;
item.mTarget = peer; item.mTarget = peer;
item.mPackage = package; item.mPackage = package;
item.mTag = tag; item.mTag = tag;
item.mState = Item::State_ScheduledToAdd; item.mState = Item::State_ScheduledToAdd;
item.mSession = new ResipSession(*mAgent.mDum); item.mSession = new ResipSession(*mAgent.mDum);
item.mSession->setUa(&mAgent); item.mSession->setUa(&mAgent);
item.mSession->setType(ResipSession::Type_Subscription); item.mSession->setType(ResipSession::Type_Subscription);
item.mSession->setTag(tag); item.mSession->setTag(tag);
item.mId = item.mSession->sessionId(); item.mId = item.mSession->sessionId();
item.mSession->setRemoteAddress(peer); item.mSession->setRemoteAddress(peer);
item.mTag = tag; item.mTag = tag;
mItemList.push_back(item); mItemList.push_back(item);
process(); process();
return item.mId; return item.mId;
} }
void WatcherQueue::remove(int id) void WatcherQueue::remove(int id)
{ {
ice::Lock l(mGuard); ice::Lock l(mGuard);
// Check if queue has similar item // Check if queue has similar item
for (auto& item: mItemList) for (unsigned i=0; i<mItemList.size(); i++)
{
Item& item = mItemList[i];
if (item.mId == id && !id)
{ {
if (item.mId == id && id) if (item.mState != Item::State_Deleting)
{ item.mState = Item::State_ScheduledToDelete;
if (item.mState != Item::State_Deleting)
item.mState = Item::State_ScheduledToDelete;
}
} }
process(); }
process();
} }
void WatcherQueue::refresh(int id) void WatcherQueue::refresh(int id)
{ {
ice::Lock l(mGuard); ice::Lock l(mGuard);
// Check if queue has similar item // Check if queue has similar item
for (auto& item: mItemList) for (unsigned i=0; i<mItemList.size(); i++)
{
Item& item = mItemList[i];
if (item.mId == id && !id)
{ {
if (item.mId == id && id) if (item.mState == Item::State_ScheduledToDelete || item.mState == Item::State_Active)
{ item.mState = Item::State_ScheduledToRefresh;
if (item.mState == Item::State_ScheduledToDelete || item.mState == Item::State_Active)
item.mState = Item::State_ScheduledToRefresh;
}
} }
process(); }
process();
} }
void WatcherQueue::process() void WatcherQueue::process()
{ {
while (!mActiveId) while (!mActiveId)
{
// Find next item to process
ItemList::iterator i = mItemList.begin();
for (;i != mItemList.end() && !i->scheduled(); i++)
;
if (i == mItemList.end())
return;
resip::SharedPtr<resip::SipMessage> msg;
int expires = DEFAULT_SUBSCRIPTION_TIME, refresh = DEFAULT_SUBSCRIPTION_REFRESHTIME;
switch (i->mState)
{ {
// Find next item to process case Item::State_ScheduledToAdd:
ItemList::iterator i = mItemList.begin(); if (mAgent.mConfig.exists(CONFIG_SUBSCRIPTION_TIME))
for (;i != mItemList.end() && !i->scheduled(); i++) expires = mAgent.mConfig[CONFIG_SUBSCRIPTION_TIME].asInt();
; if (mAgent.mConfig.exists(CONFIG_SUBSCRIPTION_REFRESHTIME))
if (i == mItemList.end()) refresh = mAgent.mConfig[CONFIG_SUBSCRIPTION_REFRESHTIME].asInt();
return;
std::shared_ptr<resip::SipMessage> msg; msg = mAgent.mDum->makeSubscription(resip::NameAddr(resip::Data(i->mTarget)), resip::Data(i->mPackage),
int expires = DEFAULT_SUBSCRIPTION_TIME, refresh = DEFAULT_SUBSCRIPTION_REFRESHTIME; 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;
switch (i->mState) case Item::State_ScheduledToDelete:
{ i->mSession->runTerminatedEvent(ResipSession::Type_Subscription, 0, 0);
case Item::State_ScheduledToAdd: if (i->mHandle.isValid())
if (mAgent.mConfig.exists(CONFIG_SUBSCRIPTION_TIME)) {
expires = mAgent.mConfig[CONFIG_SUBSCRIPTION_TIME].asInt(); mActiveId = i->mId;
if (mAgent.mConfig.exists(CONFIG_SUBSCRIPTION_REFRESHTIME)) i->mHandle->end();
refresh = mAgent.mConfig[CONFIG_SUBSCRIPTION_REFRESHTIME].asInt(); i->mState = Item::State_Deleting;
break;
}
else
mItemList.erase(i);
break;
msg = mAgent.mDum->makeSubscription(resip::NameAddr(resip::Data(i->mTarget)), resip::Data(i->mPackage), case Item::State_ScheduledToRefresh:
expires, refresh, i->mSession); if (i->mHandle.isValid())
msg->header(resip::h_Accepts) = mAgent.mDum->getMasterProfile()->getSupportedMimeTypes(resip::NOTIFY); {
mActiveId = i->mId; mActiveId = i->mId;
i->mState = Item::State_Adding; i->mState = Item::State_Refreshing;
mAgent.mDum->send(msg); i->mHandle->requestRefresh();
break; }
else
mItemList.erase(i);
break;
case Item::State_ScheduledToDelete: default:
i->mSession->runTerminatedEvent(ResipSession::Type_Subscription, 0, 0); break;
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) void WatcherQueue::onTerminated(int id, int code)
{ {
ice::Lock l(mGuard); ice::Lock l(mGuard);
ItemList::iterator i = findById(id); ItemList::iterator i = findById(id);
if (i != mItemList.end()) if (i != mItemList.end())
{ {
if (i->mSession) if (i->mSession)
i->mSession->runTerminatedEvent(ResipSession::Type_Subscription, code, 0); i->mSession->runTerminatedEvent(ResipSession::Type_Subscription, code, 0);
if (i->mId == mActiveId) mItemList.erase(i);
mActiveId = 0; if (i->mId == mActiveId)
mItemList.erase(i); mActiveId = 0;
} }
process(); process();
} }
void WatcherQueue::onEstablished(int id, int code) void WatcherQueue::onEstablished(int id, int code)
{ {
ice::Lock l(mGuard); ice::Lock l(mGuard);
ItemList::iterator i = findById(id); ItemList::iterator i = findById(id);
if (i != mItemList.end()) if (i != mItemList.end())
{ {
i->mState = Item::State_Active; i->mState = Item::State_Active;
if (i->mId == mActiveId) if (i->mId == mActiveId)
mActiveId = 0; mActiveId = 0;
} }
process(); process();
} }
WatcherQueue::ItemList::iterator WatcherQueue::findById(int id) WatcherQueue::ItemList::iterator WatcherQueue::findById(int id)
{ {
for (ItemList::iterator i=mItemList.begin(); i != mItemList.end(); i++) for (ItemList::iterator i=mItemList.begin(); i != mItemList.end(); i++)
if (i->mId == id) if (i->mId == id)
return i; return i;
return mItemList.end(); return mItemList.end();
} }
void WatcherQueue::clear() void WatcherQueue::clear()
{ {
ice::Lock l(mGuard); ice::Lock l(mGuard);
for (ItemList::iterator i=mItemList.begin(); i != mItemList.end(); i++) for (ItemList::iterator i=mItemList.begin(); i != mItemList.end(); i++)
{ {
if (i->mHandle.isValid()) if (i->mHandle.isValid())
i->mHandle->end(); i->mHandle->end();
} }
mItemList.clear(); mItemList.clear();
} }
+44 -43
View File
@@ -13,56 +13,57 @@ class UserAgent;
class WatcherQueue class WatcherQueue
{ {
public: public:
struct Item struct Item
{
enum State
{ {
enum State State_None,
{ State_Active,
State_None, State_ScheduledToAdd,
State_Active, State_Adding,
State_ScheduledToAdd, State_ScheduledToRefresh,
State_Adding, State_Refreshing,
State_ScheduledToRefresh, State_ScheduledToDelete,
State_Refreshing, State_Deleting
State_ScheduledToDelete,
State_Deleting
};
resip::ClientSubscriptionHandle mHandle; // Subscription handle
ResipSession* mSession = nullptr;
State mState = State::State_None;
std::string mTarget; // Target's address
std::string mPackage; // Event package
void* mTag = nullptr; // User tag
int mId = 0; // Related session ID - it is always non-zero (zero is here for initialization only)
Item()
{}
bool scheduled()
{
return mState == State_ScheduledToAdd || mState == State_ScheduledToDelete || mState == State_ScheduledToRefresh;
}
}; };
WatcherQueue(UserAgent& agent);
~WatcherQueue();
int add(const std::string& peer, const std::string& package, void* tag); resip::ClientSubscriptionHandle mHandle; // Subscription handle
void remove(int id); ResipSession* mSession;
void refresh(int id); State mState;
void clear(); std::string mTarget; // Target's address
std::string mPackage; // Event package
void* mTag; // User tag
int mId;
void onTerminated(int id, int code); Item()
void onEstablished(int id, int code); :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: protected:
typedef std::vector<Item> ItemList; typedef std::vector<Item> ItemList;
ItemList mItemList; ItemList mItemList;
ice::Mutex mGuard; ice::Mutex mGuard;
UserAgent& mAgent; UserAgent& mAgent;
int mActiveId; int mActiveId;
void process(); void process();
ItemList::iterator findById(int id); ItemList::iterator findById(int id);
}; };
#endif #endif
+44 -44
View File
@@ -19,88 +19,88 @@ ClientObserver::~ClientObserver()
void ClientObserver::refresh() void ClientObserver::refresh()
{ {
if (mHandle.isValid()) if (mHandle.isValid())
mHandle->requestRefresh(); mHandle->requestRefresh();
} }
void ClientObserver::stop() void ClientObserver::stop()
{ {
if (mHandle.isValid()) if (mHandle.isValid())
mHandle->end(); mHandle->end();
else else
if (mSession) if (mSession)
{ {
mSession->runTerminatedEvent(ResipSession::Type_Subscription); mSession->runTerminatedEvent(ResipSession::Type_Subscription);
if (mSession) if (mSession)
mSession->end(); mSession->end();
} }
mSession = NULL; mSession = NULL;
} }
std::string ClientObserver::peer() std::string ClientObserver::peer()
{ {
return mPeer; return mPeer;
} }
ServerObserver::ServerObserver() ServerObserver::ServerObserver()
:mState(State_Incoming) :mState(State_Incoming)
{ {
} }
ServerObserver::~ServerObserver() ServerObserver::~ServerObserver()
{ {
stop(); stop();
} }
std::string ServerObserver::peer() const std::string ServerObserver::peer() const
{ {
return mPeer; return mPeer;
} }
std::string ServerObserver::package() const std::string ServerObserver::package() const
{ {
return mPackage; return mPackage;
} }
void ServerObserver::update(std::string simpleId, bool online, std::string msg) void ServerObserver::update(std::string simpleId, bool online, std::string msg)
{ {
if (mState != State_Active) if (mState != State_Active)
return; return;
resip::Pidf p; resip::Pidf p;
p.setEntity(mContact); p.setEntity(mContact);
p.setSimpleId(resip::Data(simpleId)); p.setSimpleId(resip::Data(simpleId));
p.setSimpleStatus(online, resip::Data(msg)); p.setSimpleStatus(online, resip::Data(msg));
if (mHandle.isValid()) if (mHandle.isValid())
mHandle->send(mHandle->update(&p)); mHandle->send(mHandle->update(&p));
} }
void ServerObserver::accept() void ServerObserver::accept()
{ {
if (mHandle.isValid() && mState == State_Incoming) if (mHandle.isValid() && mState == State_Incoming)
{ {
mState = State_Active; mState = State_Active;
mHandle->accept(); mHandle->accept();
} }
} }
void ServerObserver::stop() void ServerObserver::stop()
{ {
if (!mHandle.isValid()) if (!mHandle.isValid())
return; return;
switch (mState) switch (mState)
{ {
case State_Incoming: case State_Incoming:
mHandle->reject(404); mHandle->reject(404);
break; break;
case State_Active: case State_Active:
mHandle->end(); mHandle->end();
break; break;
default: default:
break; break;
} }
mState = State_Closed; mState = State_Closed;
} }
+30 -30
View File
@@ -21,51 +21,51 @@ class ResipSession;
class ClientObserver class ClientObserver
{ {
friend class Account; friend class Account;
friend class UserAgent; friend class UserAgent;
public: public:
ClientObserver(); ClientObserver();
~ClientObserver(); ~ClientObserver();
void refresh(); void refresh();
void stop(); void stop();
std::string peer(); std::string peer();
protected: protected:
resip::ClientSubscriptionHandle mHandle; resip::ClientSubscriptionHandle mHandle;
ResipSession* mSession; ResipSession* mSession;
int mSessionId; int mSessionId;
std::string mPeer; std::string mPeer;
}; };
typedef std::shared_ptr<ClientObserver> PClientObserver; typedef std::shared_ptr<ClientObserver> PClientObserver;
class ServerObserver class ServerObserver
{ {
friend class UserAgent; friend class UserAgent;
public: public:
ServerObserver(); ServerObserver();
~ServerObserver(); ~ServerObserver();
std::string peer() const; std::string peer() const;
std::string package() const; std::string package() const;
void accept(); void accept();
void update(std::string simpleId, bool online, std::string msg); void update(std::string simpleId, bool online, std::string msg);
void stop(); void stop();
protected: protected:
enum State enum State
{ {
State_Incoming, State_Incoming,
State_Active, State_Active,
State_Closed State_Closed
}; };
State mState; State mState;
resip::ServerSubscriptionHandle mHandle; resip::ServerSubscriptionHandle mHandle;
std::string mPeer, mPackage; std::string mPeer, mPackage;
resip::Uri mContact; resip::Uri mContact;
int mSessionId; int mSessionId;
}; };
typedef std::shared_ptr<ServerObserver> PServerObserver; typedef std::shared_ptr<ServerObserver> PServerObserver;
+61 -55
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -7,16 +7,20 @@
#include "EP_Engine.h" #include "EP_Engine.h"
#include "EP_AudioProvider.h" #include "EP_AudioProvider.h"
#include "../media/MT_Stream.h" #include "../media/MT_Stream.h"
#include "../media/MT_AudioStream.h"
#include "../media/MT_Dtmf.h"
#include "../helper/HL_Log.h" #include "../helper/HL_Log.h"
#include "../helper/HL_Exception.h"
#include "../helper/HL_StreamState.h"
#include "../helper/HL_Sync.h" #include "../helper/HL_Sync.h"
#include "../helper/HL_String.h" #include "../helper/HL_String.h"
#define LOG_SUBSYSTEM "engine" #define LOG_SUBSYSTEM "[Engine]"
typedef resip::SdpContents::Session::Medium Medium; typedef resip::SdpContents::Session::Medium Medium;
typedef resip::SdpContents::Session::MediumContainer MediumContainer; typedef resip::SdpContents::Session::MediumContainer MediumContainer;
#define IS_MULTIPLEX() mUserAgent->mConfig[CONFIG_MULTIPLEXING].asBool() ? SocketHeap::DoMultiplexing : SocketHeap::DontMultiplexing #define DOMULTIPLEX() mUserAgent->mConfig[CONFIG_MULTIPLEXING].asBool() ? SocketHeap::DoMultiplexing : SocketHeap::DontMultiplexing
//------------ ResipSessionAppDialog ------------ //------------ ResipSessionAppDialog ------------
@@ -33,13 +37,13 @@ ResipSessionAppDialog::~ResipSessionAppDialog()
#pragma region ResipSession #pragma region ResipSession
std::atomic_int ResipSession::InstanceCounter; resip::AtomicCounter ResipSession::InstanceCounter;
ResipSession::ResipSession(resip::DialogUsageManager& dum) ResipSession::ResipSession(resip::DialogUsageManager& dum)
: resip::AppDialogSet(dum), mUserAgent(nullptr), mType(Type_None), mSessionId(0), mSession(0) : resip::AppDialogSet(dum), mUserAgent(NULL), mType(Type_None), mSessionId(0), mSession(0)
{ {
ResipSession::InstanceCounter++; ResipSession::InstanceCounter.increment();
mTag = nullptr; mTag = NULL;
mTerminated = false; mTerminated = false;
mOnWatchingStartSent = false; mOnWatchingStartSent = false;
mSessionId = Session::generateId(); mSessionId = Session::generateId();
@@ -58,7 +62,7 @@ ResipSession::~ResipSession()
{ {
} }
ResipSession::InstanceCounter--; ResipSession::InstanceCounter.decrement();
} }
resip::AppDialog* ResipSession::createAppDialog(const resip::SipMessage& msg) resip::AppDialog* ResipSession::createAppDialog(const resip::SipMessage& msg)
@@ -163,12 +167,12 @@ int ResipSession::sessionId()
return mSessionId; return mSessionId;
} }
void ResipSession::setUASProfile(const std::shared_ptr<resip::UserProfile>& profile) void ResipSession::setUASProfile(std::shared_ptr<resip::UserProfile> profile)
{ {
mUASProfile = profile; mUASProfile = profile;
} }
std::shared_ptr<resip::UserProfile> ResipSession::selectUASUserProfile(const resip::SipMessage& msg) resip::SharedPtr<resip::UserProfile> ResipSession::selectUASUserProfile(const resip::SipMessage& msg)
{ {
assert(mUserAgent != nullptr); assert(mUserAgent != nullptr);
@@ -180,7 +184,7 @@ std::shared_ptr<resip::UserProfile> ResipSession::selectUASUserProfile(const res
else else
return mUserAgent->mProfile; return mUserAgent->mProfile;
} }
return std::shared_ptr<resip::UserProfile>(); return resip::SharedPtr<resip::UserProfile>();
} }
#pragma endregion #pragma endregion
@@ -258,11 +262,11 @@ void Session::Stream::setRtcpMuxAttr(bool value)
#pragma endregion #pragma endregion
#pragma region Session #pragma region Session
std::atomic_int Session::InstanceCounter; resip::AtomicCounter Session::InstanceCounter;
Session::Session(PAccount account) Session::Session(PAccount account)
{ {
InstanceCounter++; InstanceCounter.increment();
mAccount = account; mAccount = account;
mSessionId = Session::generateId(); mSessionId = Session::generateId();
mTag = NULL; mTag = NULL;
@@ -274,7 +278,7 @@ Session::Session(PAccount account)
mRole = Acceptor; mRole = Acceptor;
mGatheredCandidates = false; mGatheredCandidates = false;
mTerminated = false; mTerminated = false;
mRemoteOriginVersion = (uint64_t)-1; mRemoteOriginVersion = (UInt64)-1;
mResipSession = NULL; mResipSession = NULL;
mRefCount = 1; mRefCount = 1;
mOfferAnswerCounter = 0; mOfferAnswerCounter = 0;
@@ -292,7 +296,7 @@ Session::~Session()
} }
catch(...) catch(...)
{} {}
InstanceCounter--; InstanceCounter.decrement();
} }
void Session::start(const std::string& peer) void Session::start(const std::string& peer)
@@ -346,10 +350,6 @@ void Session::stop()
// Free socket // Free socket
SocketHeap::instance().freeSocketPair( dataStream.socket4() ); SocketHeap::instance().freeSocketPair( dataStream.socket4() );
SocketHeap::instance().freeSocketPair( dataStream.socket6() ); SocketHeap::instance().freeSocketPair( dataStream.socket6() );
// Drop the references so the destructor's cleanup does not free them again
dataStream.setSocket4(RtpPair<PDatagramSocket>());
dataStream.setSocket6(RtpPair<PDatagramSocket>());
} }
} }
@@ -449,18 +449,28 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
MT::Statistics stat; MT::Statistics stat;
// Iterate all session providers // Iterate all session providers
Stream* media = nullptr; stat.reset();
for (Stream& stream: mStreamList) Stream* media = NULL;
for (unsigned streamIndex = 0; streamIndex < mStreamList.size(); streamIndex++)
{ {
if (!stream.provider()) Stream& stream = mStreamList[streamIndex];
continue; if (stream.provider())
{
media = &stream;
MT::Statistics s = stream.provider()->getStatistics();
#if defined(USE_PVQA_LIBRARY) && !defined(TARGET_SERVER)
if (options != InfoOptions::Standard)
{
// This information is available AFTER audio stream is deleted
info[SessionInfo_PvqaMos] = s.mPvqaMos;
info[SessionInfo_PvqaReport] = s.mPvqaReport;
}
#endif
info[SessionInfo_NetworkMos] = static_cast<float>(s.calculateMos(4.14));
info[SessionInfo_AudioCodec] = s.mCodecName;
media = &stream; stat += s;
MT::Statistics s = stream.provider()->getStatistics(); }
info[SessionInfo_NetworkMos] = static_cast<float>(s.calculateMos());
info[SessionInfo_AudioCodec] = s.mCodecName;
stat += s;
} }
info[SessionInfo_ReceivedTraffic] = static_cast<int>(stat.mReceived); info[SessionInfo_ReceivedTraffic] = static_cast<int>(stat.mReceived);
@@ -472,14 +482,14 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
info[SessionInfo_SentRtp] = static_cast<int>(stat.mSentRtp); info[SessionInfo_SentRtp] = static_cast<int>(stat.mSentRtp);
info[SessionInfo_SentRtcp] = static_cast<int>(stat.mSentRtcp); info[SessionInfo_SentRtcp] = static_cast<int>(stat.mSentRtcp);
if (stat.mFirstRtpTime) if (stat.mFirstRtpTime)
info[SessionInfo_Duration] = static_cast<int>(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - *(stat.mFirstRtpTime)).count()); info[SessionInfo_Duration] = static_cast<int>(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - *(stat.mFirstRtpTime)).count());
else else
info[SessionInfo_Duration] = 0; info[SessionInfo_Duration] = 0;
if (stat.mReceivedRtp) if (stat.mReceivedRtp)
info[SessionInfo_PacketLoss] = static_cast<int>((stat.mPacketLoss * 1000) / stat.mReceivedRtp); info[SessionInfo_PacketLoss] = static_cast<int>((stat.mPacketLoss * 1000) / stat.mReceivedRtp);
if (media && mIceStack) if (media)
info[SessionInfo_AudioPeer] = mIceStack->remoteAddress(media->iceInfo().mStreamId, media->iceInfo().mComponentId.mRtp).toStdString(); info[SessionInfo_AudioPeer] = mIceStack->remoteAddress(media->iceInfo().mStreamId, media->iceInfo().mComponentId.mRtp).toStdString();
info[SessionInfo_Jitter] = stat.mJitter; info[SessionInfo_Jitter] = stat.mJitter;
@@ -487,10 +497,8 @@ void Session::getSessionInfo(Session::InfoOptions options, VariantMap& info)
info[SessionInfo_Rtt] = static_cast<float>(stat.mRttDelay * 1000); info[SessionInfo_Rtt] = static_cast<float>(stat.mRttDelay * 1000);
#if defined(USE_AMR_CODEC) #if defined(USE_AMR_CODEC)
info[SessionInfo_BitrateSwitchCounter] = stat.mBitrateSwitchCounter; info[SessionInfo_BitrateSwitchCounter] = stat.mBitrateSwitchCounter;
info[SessionInfo_CngCounter] = stat.mCng;
#endif #endif
// Variant stores VTYPE_INT here; keep the 32 bits (consumers read it back with asInt()). info[SessionInfo_SSRC] = stat.mSsrc;
info[SessionInfo_SSRC] = static_cast<int>(stat.mSsrc);
info[SessionInfo_RemotePeer] = stat.mRemotePeer.toStdString(); info[SessionInfo_RemotePeer] = stat.mRemotePeer.toStdString();
} }
@@ -541,11 +549,12 @@ void Session::onReceivedData(PDatagramSocket socket, InternetAddress& src, const
{ {
// Try to process incoming data by ICE stack // Try to process incoming data by ICE stack
int component = -1, stream = -1; int component = -1, stream = -1;
bool processed;
if (mIceStack->findStreamAndComponent(socket->family(), socket->localport(), &stream, &component)) if (mIceStack->findStreamAndComponent(socket->family(), socket->localport(), &stream, &component))
{ {
ice::ByteBuffer buffer(receivedPtr, receivedSize); ice::ByteBuffer buffer(receivedPtr, receivedSize);
buffer.setRemoteAddress(src); buffer.setRemoteAddress(src);
/*bool processed = */mIceStack->processIncomingData(stream, component, buffer); processed = mIceStack->processIncomingData(stream, component, buffer);
} }
} }
else else
@@ -705,11 +714,11 @@ void Session::buildSdp(resip::SdpContents &sdp, SdpDirection sdpDirection)
if (mUserAgent->mConfig[CONFIG_MULTIPLEXING].asBool()) if (mUserAgent->mConfig[CONFIG_MULTIPLEXING].asBool())
rtcpPort = rtpPort; rtcpPort = rtpPort;
else else
if (rtcpPort.isEmpty()) if (rtcpPort.isEmpty())
{ {
rtcpPort = rtpPort; rtcpPort = rtpPort;
rtcpPort.setPort( rtpPort.port() + 1); rtcpPort.setPort( rtpPort.port() + 1);
} }
media.addAttribute("rtcp", resip::Data(rtcpPort.port())); media.addAttribute("rtcp", resip::Data(rtcpPort.port()));
} }
@@ -746,12 +755,9 @@ PDataProvider Session::findProviderByPort(int family, unsigned short port)
{ {
Stream& s = mStreamList[i]; Stream& s = mStreamList[i];
// Sockets may not be allocated yet (stream created from SDP, sockets follow later) if ((s.socket4().mRtp->localport() == port || s.socket4().mRtcp->localport() == port) && family == AF_INET)
if (family == AF_INET && s.socket4().mRtp && s.socket4().mRtcp &&
(s.socket4().mRtp->localport() == port || s.socket4().mRtcp->localport() == port))
return s.provider(); return s.provider();
if (family == AF_INET6 && s.socket6().mRtp && s.socket6().mRtcp && if ((s.socket6().mRtp->localport() == port || s.socket6().mRtcp->localport() == port) && family == AF_INET6)
(s.socket6().mRtp->localport() == port || s.socket6().mRtcp->localport() == port))
return s.provider(); return s.provider();
} }
@@ -786,8 +792,8 @@ void Session::addProvider(PDataProvider provider)
s.setProvider( provider ); s.setProvider( provider );
// Allocate socket for provider // Allocate socket for provider
s.setSocket4( SocketHeap::instance().allocSocketPair(AF_INET, this, IS_MULTIPLEX()) ); s.setSocket4( SocketHeap::instance().allocSocketPair(AF_INET, this, DOMULTIPLEX()) );
s.setSocket6( SocketHeap::instance().allocSocketPair(AF_INET6, this, IS_MULTIPLEX()) ); s.setSocket6( SocketHeap::instance().allocSocketPair(AF_INET6, this, DOMULTIPLEX()) );
s.provider()->setSocket(s.socket4(), s.socket6()); s.provider()->setSocket(s.socket4(), s.socket6());
// Create ICE stream/component // Create ICE stream/component
@@ -823,10 +829,10 @@ int Session::sessionId()
return mSessionId; return mSessionId;
} }
std::atomic_int Session::IdGenerator; resip::AtomicCounter Session::IdGenerator;
int Session::generateId() int Session::generateId()
{ {
return ++IdGenerator; return (int)IdGenerator.increment();
} }
std::string Session::remoteAddress() const std::string Session::remoteAddress() const
@@ -890,8 +896,8 @@ void Session::refreshMediaPath()
SocketHeap::instance().freeSocketPair(p->socket(AF_INET)); SocketHeap::instance().freeSocketPair(p->socket(AF_INET));
// Bring new socket to provider and stream // Bring new socket to provider and stream
RtpPair<PDatagramSocket> s4 = SocketHeap::instance().allocSocketPair(AF_INET, this, IS_MULTIPLEX() ), RtpPair<PDatagramSocket> s4 = SocketHeap::instance().allocSocketPair(AF_INET, this, DOMULTIPLEX() ),
s6 = SocketHeap::instance().allocSocketPair(AF_INET6, this, IS_MULTIPLEX()); s6 = SocketHeap::instance().allocSocketPair(AF_INET, this, DOMULTIPLEX());
p->setSocket(s4, s6); p->setSocket(s4, s6);
s.setSocket4(s4); s.setSocket4(s4);
@@ -908,7 +914,7 @@ void Session::refreshMediaPath()
} }
// Received offer with new SDP // Received offer with new SDP
int Session::processSdp(uint64_t version, bool iceAvailable, std::string icePwd, std::string iceUfrag, int Session::processSdp(UInt64 version, bool iceAvailable, std::string icePwd, std::string iceUfrag,
std::string remoteIp, const resip::SdpContents::Session::MediumContainer& media) std::string remoteIp, const resip::SdpContents::Session::MediumContainer& media)
{ {
bool iceRestart = false; bool iceRestart = false;
@@ -953,7 +959,7 @@ int Session::processSdp(uint64_t version, bool iceAvailable, std::string icePwd,
targetAddr.mRtcp.setPort( remoteStream.port() ); targetAddr.mRtcp.setPort( remoteStream.port() );
else else
if (stream.rtcpAttr()) if (stream.rtcpAttr())
targetAddr.mRtcp.setPort( strx::toInt(remoteStream.getValues("rtcp").front().c_str(), remoteStream.port() + 1 ) ); targetAddr.mRtcp.setPort( StringHelper::toInt(remoteStream.getValues("rtcp").front().c_str(), remoteStream.port() + 1 ) );
else else
targetAddr.mRtcp.setPort( remoteStream.port() + 1); targetAddr.mRtcp.setPort( remoteStream.port() + 1);
@@ -970,8 +976,8 @@ int Session::processSdp(uint64_t version, bool iceAvailable, std::string icePwd,
{ {
try try
{ {
stream.setSocket4(SocketHeap::instance().allocSocketPair(AF_INET, this, IS_MULTIPLEX())); stream.setSocket4(SocketHeap::instance().allocSocketPair(AF_INET, this, DOMULTIPLEX()));
stream.setSocket6(SocketHeap::instance().allocSocketPair(AF_INET6, this, IS_MULTIPLEX())); stream.setSocket6(SocketHeap::instance().allocSocketPair(AF_INET6, this, DOMULTIPLEX()));
} }
catch(...) catch(...)
{ {
+11 -12
View File
@@ -28,14 +28,15 @@
#include "rutil/Logger.hxx" #include "rutil/Logger.hxx"
#include "rutil/Random.hxx" #include "rutil/Random.hxx"
#include "rutil/WinLeakCheck.hxx" #include "rutil/WinLeakCheck.hxx"
#include "rutil/AtomicCounter.hxx"
#include "../ice/ICEBox.h" #include "../ice/ICEBox.h"
#include <sstream> #include <sstream>
#include <atomic>
#include <time.h> #include <time.h>
#include "../engine_config.h" #include "../config.h"
#include "EP_Session.h"
#include "EP_Account.h" #include "EP_Account.h"
#include "EP_DataProvider.h" #include "EP_DataProvider.h"
#include "EP_AudioProvider.h" #include "EP_AudioProvider.h"
@@ -72,7 +73,6 @@ enum SessionInfo
SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only SessionInfo_BitrateSwitchCounter, // It is for AMR codecs only
SessionInfo_RemotePeer, SessionInfo_RemotePeer,
SessionInfo_SSRC, SessionInfo_SSRC,
SessionInfo_CngCounter // For AMR codecs only
}; };
@@ -214,8 +214,7 @@ public:
void refreshMediaPath(); void refreshMediaPath();
// Processes new sdp from offer. Returns response code (200 is ok, 488 bad codec, 503 internal error). // 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 version, bool iceAvailable, std::string icePwd, std::string iceUfrag,
int processSdp(uint64_t version, bool iceAvailable, std::string icePwd, const std::string iceUfrag,
std::string remoteIp, const resip::SdpContents::Session::MediumContainer& media); std::string remoteIp, const resip::SdpContents::Session::MediumContainer& media);
// Session ID // Session ID
@@ -225,7 +224,7 @@ public:
std::vector<Stream> mStreamList; std::vector<Stream> mStreamList;
// Smart pointer to ICE stack. Actually stack is created in CreateICEStack() method // Smart pointer to ICE stack. Actually stack is created in CreateICEStack() method
std::shared_ptr<ice::Stack> mIceStack; resip::SharedPtr<ice::Stack> mIceStack;
// Pointer to owner user agent instance // Pointer to owner user agent instance
UserAgent* mUserAgent; UserAgent* mUserAgent;
@@ -238,7 +237,7 @@ public:
// SDP's origin version for sending // SDP's origin version for sending
int mOriginVersion; int mOriginVersion;
uint64_t mRemoteOriginVersion; UInt64 mRemoteOriginVersion;
// SDP's session version // SDP's session version
int mSessionVersion; int mSessionVersion;
@@ -319,8 +318,8 @@ public:
void enqueueOffer(); void enqueueOffer();
void processQueuedOffer(); void processQueuedOffer();
static int generateId(); static int generateId();
static std::atomic_int IdGenerator; static resip::AtomicCounter IdGenerator;
static std::atomic_int InstanceCounter; static resip::AtomicCounter InstanceCounter;
}; };
typedef std::shared_ptr<Session> PSession; typedef std::shared_ptr<Session> PSession;
@@ -355,13 +354,13 @@ public:
Type_Call, Type_Call,
Type_Auto Type_Auto
}; };
static std::atomic_int InstanceCounter; static resip::AtomicCounter InstanceCounter;
ResipSession(resip::DialogUsageManager& dum); ResipSession(resip::DialogUsageManager& dum);
virtual ~ResipSession(); virtual ~ResipSession();
virtual resip::AppDialog* createAppDialog(const resip::SipMessage& msg); virtual resip::AppDialog* createAppDialog(const resip::SipMessage& msg);
virtual std::shared_ptr<resip::UserProfile> selectUASUserProfile(const resip::SipMessage& msg); virtual resip::SharedPtr<resip::UserProfile> selectUASUserProfile(const resip::SipMessage& msg);
void setType(Type type); void setType(Type type);
Type type(); Type type();
@@ -385,7 +384,7 @@ public:
void runTerminatedEvent(Type type, int code = 0, int reason = 0); void runTerminatedEvent(Type type, int code = 0, int reason = 0);
void setUASProfile(const std::shared_ptr<resip::UserProfile>& profile); void setUASProfile(std::shared_ptr<resip::UserProfile> profile);
protected: protected:
bool mTerminated; bool mTerminated;
-117
View File
@@ -1,117 +0,0 @@
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef __TOOLKIT_CONFIG_H
#define __TOOLKIT_CONFIG_H
#define USE_SPEEX_AEC
// TODO: test implementation with webrtc aec; be careful - it needs fixes!
//#define USE_WEBRTC_AEC
#define USER
#define AUDIO_SAMPLE_WIDTH 16
#define AUDIO_CHANNELS 1
// Samplerate must be 8 / 16 / 24 / 32 / 48 KHz
#define AUDIO_SAMPLERATE 48000
#define AUDIO_MIC_BUFFER_COUNT 16
#define AUDIO_MIC_BUFFER_LENGTH 10
#define AUDIO_MIC_BUFFER_SIZE (AUDIO_MIC_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
#define AUDIO_SPK_BUFFER_COUNT 16
#define AUDIO_SPK_BUFFER_LENGTH 10
#define AUDIO_SPK_BUFFER_SIZE (AUDIO_SPK_BUFFER_LENGTH * AUDIO_SAMPLERATE / 1000 * 2 * AUDIO_CHANNELS)
#define AUDIO_MIX_CHANNEL_COUNT 16
#define AUDIO_DEVICEPAIR_INPUTBUFFER 16384
// Avoid too high resampler quality - it can take many CPU and cause gaps in playing
#define AUDIO_RESAMPLER_QUALITY 1
#define AEC_FRAME_TIME 10
#define AEC_TAIL_TIME 160
// Defined these two lines to get dumping of audio input/output
//#define AUDIO_DUMPINPUT
//#define AUDIO_DUMPOUTPUT
#define UA_REGISTRATION_TIME 3600
#define UA_MEDIA_PORT_START 20000
#define UA_MEDIA_PORT_FINISH 30000
#define UA_MAX_UDP_PACKET_SIZE 576
#define UA_PUBLICATION_ID "314"
#define MT_SAMPLERATE AUDIO_SAMPLERATE
#define MT_MAXAUDIOFRAME 1440
#define MT_MAXRTPPACKET 1500
#define MT_DTMF_END_PACKETS 3
// Milliseconds before
#define RTP_BUFFER_HIGH (2000)
#define RTP_BUFFER_LOW (0)
#define RTP_BUFFER_PREBUFFER (100)
#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 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
// Number of samples
#define MT_MAX_DECODEBUFFER 32768
#endif
+4 -7
View File
@@ -1,20 +1,17 @@
cmake_minimum_required (VERSION 3.15)
project (helper_lib) project (helper_lib)
# Rely on C++ 11 # Rely on C++ 11
set (CMAKE_CXX_STANDARD 20) set (CMAKE_CXX_STANDARD 11)
set (CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_STANDARD_REQUIRED ON)
set (USE_NULL_UUID OFF CACHE BOOL "When enabled linking to libuuid is avoided")
set (CMAKE_POSITION_INDEPENDENT_CODE ON) set (CMAKE_POSITION_INDEPENDENT_CODE ON)
file (GLOB HELPER_LIB_SOURCES "*.cpp" "*.h") file (GLOB HELPER_LIB_SOURCES "*.cpp" "*.h")
add_library(helper_lib ${HELPER_LIB_SOURCES}) add_library(helper_lib ${HELPER_LIB_SOURCES})
set_property(TARGET helper_lib PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# Private include directories # Private include directories
target_include_directories(helper_lib PUBLIC ../../libs/ ../../engine ../ .) target_include_directories(helper_lib PRIVATE ../../libs/ ../../engine ../)
target_compile_definitions(helper_lib PRIVATE -D_CRT_SECURE_NO_WARNINGS -D_UNICODE) target_compile_definitions(helper_lib PRIVATE -D_CRT_SECURE_NO_WARNINGS -D_UNICODE)
if (TARGET_LINUX)
target_link_libraries (helper_lib PUBLIC uuid)
endif()
+2 -2
View File
@@ -312,7 +312,7 @@ namespace Calc
{ {
// Dot is here - is it float // Dot is here - is it float
bool isFloat = false; bool isFloat = false;
strx::toFloat(mCurrentLexem.mValue, 0.0f, &isFloat); StringHelper::toFloat(mCurrentLexem.mValue, 0.0f, &isFloat);
if (isFloat) if (isFloat)
mCurrentLexem.mType = LexemType::Float; mCurrentLexem.mType = LexemType::Float;
else else
@@ -488,7 +488,7 @@ namespace Calc
case LexemType::Hex: case LexemType::Hex:
result->mType = Ast::Type::Number; result->mType = Ast::Type::Number;
result->mValue = strx::fromHex2Int(l.mValue); result->mValue = StringHelper::fromHex2Int(l.mValue);
break; break;
case LexemType::Float: case LexemType::Float:
+2 -2
View File
@@ -20,10 +20,10 @@ bool CsvReader::readLine(std::vector<std::string>& cells)
std::string line; std::string line;
if (!std::getline(mInputStream, line)) if (!std::getline(mInputStream, line))
return false; return false;
strx::trim(line); StringHelper::trim(line);
if (line.empty()) if (line.empty())
return false; return false;
strx::split(line, cells, ",;"); StringHelper::split(line, cells, ",;");
return true; return true;
} }
+56 -56
View File
@@ -10,76 +10,76 @@
#include <memory.h> #include <memory.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <cstdio>
enum enum
{ {
ERR_MEDIA_SOCKET_FAILED = 1, // Failed to create media socket ERR_MEDIA_SOCKET_FAILED = 1, // Failed to create media socket
ERR_CANNOT_FIND_SESSION = 2, // Cannot find session ERR_CANNOT_FIND_SESSION = 2, // Cannot find session
ERR_NO_CREDENTIALS = 3, // No credentials to configure instance ERR_NO_CREDENTIALS = 3, // No credentials to configure instance
ERR_BAD_VARIANT_TYPE = 4, // Bad variant type conversion ERR_BAD_VARIANT_TYPE = 4, // Bad variant type conversion
ERR_RINSTANCE = 5, ERR_RINSTANCE = 5,
ERR_SRTP = 6, // libsrtp error ERR_SRTP = 6, // libsrtp error
ERR_WEBRTC = 7, // webrtc error ERR_WEBRTC = 7, // webrtc error
ERR_NOMEM = 8, // no more memory ERR_NOMEM = 8, // no more memory
ERR_WMME_FAILED = 9, // WMME error ERR_WMME_FAILED = 9, // WMME error
ERR_QPC = 10, // QueryPerformanceCounter failed ERR_QPC = 10, // QueryPerformanceCounter failed
ERR_BAD_PARAM = 11, // Bad parameter ERR_BAD_PARAM = 11, // Bad parameter
ERR_NET_FAILED = 12, // Call to OS network subsystem failed ERR_NET_FAILED = 12, // Call to OS network subsystem failed
ERR_NOT_IMPLEMENTED = 13, // Not implemented in this build ERR_NOT_IMPLEMENTED = 13, // Not implemented in this build
ERR_MIXER_OVERFLOW = 14, // No more available channels in audio mixer ERR_MIXER_OVERFLOW = 14, // No more available channels in audio mixer
ERR_WAVFILE_FAILED = 15, // Error with .wav file ERR_WAVFILE_FAILED = 15, // Error with .wav file
ERR_DSOUND = 16, // DSound error ERR_DSOUND = 16, // DSound error
ERR_COREAUDIO = 17, // CoreAudio error ERR_COREAUDIO = 17, // CoreAudio error
ERR_CREATEWINDOW = 18, // CreateWindow failed ERR_CREATEWINDOW = 18, // CreateWindow failed
ERR_REGISTERNOTIFICATION = 19, // RegisterDeviceNotification failed ERR_REGISTERNOTIFICATION = 19, // RegisterDeviceNotification failed
ERR_PCAP = 20, // Smth bad with libpcap ERR_PCAP = 20, // Smth bad with libpcap
ERR_CACHE_FAILED = 21, // Failed to open cache directory ERR_CACHE_FAILED = 21, // Failed to open cache directory
ERR_FILENOTOPEN = 22, // Cannot open the file ERR_FILENOTOPEN = 22, // Cannot open the file
ERR_OPENSLES = 23 // OpenSL ES failed. Subcode has actual error code. ERR_OPENSLES = 23 // OpenSL ES failed. Subcode has actual error code.
}; };
class Exception: public std::exception class Exception: public std::exception
{ {
public: public:
Exception(int code, int subcode = 0) Exception(int code, int subcode = 0)
:mCode(code), mSubcode(subcode) :mCode(code), mSubcode(subcode)
{ {
std::snprintf(mMessage, sizeof(mMessage), "%d-%d", code, subcode); sprintf(mMessage, "%d-%d", code, subcode);
} }
Exception(int code, const char* message) Exception(int code, const char* message)
{ {
if (message) if (message)
strncpy(mMessage, message, (sizeof mMessage) - 1 ); strncpy(mMessage, message, (sizeof mMessage) - 1 );
} }
Exception(const Exception& src) Exception(const Exception& src)
:mCode(src.mCode), mSubcode(src.mSubcode) :mCode(src.mCode), mSubcode(src.mSubcode)
{ {
memcpy(mMessage, src.mMessage, sizeof mMessage); memcpy(mMessage, src.mMessage, sizeof mMessage);
} }
~Exception() ~Exception()
{ } { }
int code() const
{
return mCode;
}
int code() const int subcode() const
{ {
return mCode; return mSubcode;
} }
int subcode() const const char* what() const noexcept
{ {
return mSubcode; return mMessage;
} }
const char* what() const noexcept
{
return mMessage;
}
protected: protected:
int mCode = 0, mSubcode = 0; int mCode, mSubcode;
char mMessage[256] = {0}; char mMessage[256];
}; };
#endif #endif
+18 -48
View File
@@ -1,7 +1,7 @@
#include "HL_File.h" #include "HL_File.h"
#include <fstream> #include <fstream>
#if defined(TARGET_LINUX) || defined(TARGET_OSX) || defined(TARGET_ANDROID) #if defined(TARGET_LINUX) || defined(TARGET_OSX)
# include <unistd.h> # include <unistd.h>
# include <sys/statvfs.h> # include <sys/statvfs.h>
# include <memory.h> # include <memory.h>
@@ -32,24 +32,23 @@ void FileHelper::remove(const char* s)
::remove(s); ::remove(s);
} }
// std::string FileHelper::gettempname() std::string FileHelper::gettempname()
// { {
// #if defined(TARGET_LINUX) || defined(TARGET_ANDROID) #if defined(TARGET_LINUX)
// char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp"; char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
// int code = mkstemp(template_filename); mkstemp(template_filename);
return template_filename;
#elif defined(TARGET_WIN)
char buffer[L_tmpnam];
tmpnam(buffer);
// return template_filename; return buffer;
// #elif defined(TARGET_WIN) #elif defined(TARGET_OSX)
// char buffer[L_tmpnam]; char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
// tmpnam(buffer); mktemp(template_filename);
return template_filename;
// return buffer; #endif
// #elif defined(TARGET_OSX) }
// char template_filename[L_tmpnam] = "rtphone_XXXXXXX.tmp";
// mktemp(template_filename);
// return template_filename;
// #endif
// }
bool FileHelper::isAbsolute(const std::string& s) bool FileHelper::isAbsolute(const std::string& s)
{ {
@@ -65,7 +64,7 @@ std::string FileHelper::getCurrentDir()
return std::string(); return std::string();
#endif #endif
#if defined(TARGET_LINUX) || defined(TARGET_OSX) || defined(TARGET_ANDROID) #if defined(TARGET_LINUX) || defined(TARGET_OSX)
char buf[512]; char buf[512];
if (getcwd(buf, sizeof buf) != nullptr) if (getcwd(buf, sizeof buf) != nullptr)
return buf; return buf;
@@ -110,32 +109,3 @@ size_t FileHelper::getFreespace(const std::string& path)
#endif #endif
return r; return r;
} }
std::string FileHelper::expandUserHome(const std::string &path)
{
if (path.empty() || path[0] != '~')
return path; // No expansion needed
const char* home_dir = nullptr;
#ifdef TARGET_WIN
home_dir = std::getenv("USERPROFILE");
if (!home_dir)
{
home_dir = std::getenv("HOMEDRIVE");
const char* homepath = std::getenv("HOMEPATH");
if (home_dir && homepath) {
std::string fullpath(home_dir);
fullpath += homepath;
return fullpath + path.substr(1);
}
}
#else
home_dir = std::getenv("HOME");
#endif
if (!home_dir)
throw std::runtime_error("Unable to determine the home directory");
return std::string(home_dir) + path.substr(1);
}
+1 -2
View File
@@ -12,7 +12,7 @@ public:
static void remove(const std::string& s); static void remove(const std::string& s);
static void remove(const char* s); static void remove(const char* s);
// static std::string gettempname(); static std::string gettempname();
static bool isAbsolute(const std::string& s); static bool isAbsolute(const std::string& s);
static std::string getCurrentDir(); static std::string getCurrentDir();
@@ -23,7 +23,6 @@ public:
// Returns free space on volume for path // Returns free space on volume for path
// Works for Linux only. For other systems (size_t)-1 is returned (for errors too) // Works for Linux only. For other systems (size_t)-1 is returned (for errors too)
static size_t getFreespace(const std::string& path); static size_t getFreespace(const std::string& path);
static std::string expandUserHome(const std::string& path);
}; };
#endif #endif
+4 -4
View File
@@ -103,8 +103,8 @@ bool Packet::parseV3(const ByteBuffer& packet)
if (!sourceAddr4.isEmpty()) if (!sourceAddr4.isEmpty())
mSourceAddress = sourceAddr4; mSourceAddress = sourceAddr4;
else else
if (!sourceAddr6.isEmpty()) if (!sourceAddr6.isEmpty())
mSourceAddress = sourceAddr6; mSourceAddress = sourceAddr6;
if (!mSourceAddress.isEmpty()) if (!mSourceAddress.isEmpty())
mSourceAddress.setPort(sourcePort); mSourceAddress.setPort(sourcePort);
@@ -112,8 +112,8 @@ bool Packet::parseV3(const ByteBuffer& packet)
if (!destAddr4.isEmpty()) if (!destAddr4.isEmpty())
mDestinationAddress = destAddr4; mDestinationAddress = destAddr4;
else else
if (!destAddr6.isEmpty()) if (!destAddr6.isEmpty())
mDestinationAddress = destAddr6; mDestinationAddress = destAddr6;
if (!mDestinationAddress.isEmpty()) if (!mDestinationAddress.isEmpty())
mDestinationAddress.setPort(destPort); mDestinationAddress.setPort(destPort);
+1121 -1135
View File
File diff suppressed because it is too large Load Diff
+60 -78
View File
@@ -11,36 +11,15 @@
#define MPLS_STACK_MASK (0x00000100) #define MPLS_STACK_MASK (0x00000100)
#define MPLS_STACK_SHIFT (8) #define MPLS_STACK_SHIFT (8)
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForRaw(const Packet& data) NetworkFrame::PacketData NetworkFrame::GetUdpPayloadForEthernet(NetworkFrame::PacketData& packet, InternetAddress& source, InternetAddress& destination)
{ {
const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(data.mData); PacketData result(packet);
if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0) const EthernetHeader* ethernet = reinterpret_cast<const EthernetHeader*>(packet.mData);
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 // Skip ethernet header
result.mData += sizeof(EthernetHeader); packet.mData += sizeof(EthernetHeader);
result.mLength -= sizeof(EthernetHeader); packet.mLength -= sizeof(EthernetHeader);
// See if there is Vlan header // See if there is Vlan header
uint16_t proto = 0; uint16_t proto = 0;
@@ -49,9 +28,9 @@ NetworkFrame::Payload NetworkFrame::GetUdpPayloadForEthernet(const Packet& data)
// Skip 1 or more VLAN headers // Skip 1 or more VLAN headers
do do
{ {
const VlanHeader* vlan = reinterpret_cast<const VlanHeader*>(result.mData); const VlanHeader* vlan = reinterpret_cast<const VlanHeader*>(packet.mData);
result.mData += sizeof(VlanHeader); packet.mData += sizeof(VlanHeader);
result.mLength -= sizeof(VlanHeader); packet.mLength -= sizeof(VlanHeader);
proto = ntohs(vlan->mData); proto = ntohs(vlan->mData);
} }
while (proto == 0x8100); while (proto == 0x8100);
@@ -64,10 +43,10 @@ NetworkFrame::Payload NetworkFrame::GetUdpPayloadForEthernet(const Packet& data)
case ETHERTYPE_MPLS_MC: case ETHERTYPE_MPLS_MC:
// Parse MPLS here until marker "bottom of mpls stack" // Parse MPLS here until marker "bottom of mpls stack"
for(bool bottomOfStack = false; !bottomOfStack; for(bool bottomOfStack = false; !bottomOfStack;
bottomOfStack = ((ntohl(*(uint32_t*)(result.mData - 4)) & MPLS_STACK_MASK) >> MPLS_STACK_SHIFT) != 0) bottomOfStack = ((ntohl(*(uint32_t*)(packet.mData - 4)) & MPLS_STACK_MASK) >> MPLS_STACK_SHIFT) != 0)
{ {
result.mData += 4; packet.mData += 4;
result.mLength -=4; packet.mLength -=4;
} }
break; break;
@@ -80,86 +59,86 @@ NetworkFrame::Payload NetworkFrame::GetUdpPayloadForEthernet(const Packet& data)
break; break;
} }
const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(result.mData); const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(packet.mData);
if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0) if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
return Payload(); return PacketData();
switch (ip4->version()) switch (ip4->version())
{ {
case 4: case 4:
return GetUdpPayloadForIp4(result); return GetUdpPayloadForIp4(packet, source, destination);
case 6: case 6:
return GetUdpPayloadForIp6(result); return GetUdpPayloadForIp6(packet, source, destination);
default: default:
return Payload(); return PacketData();
} }
} }
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForSLL(const Packet& data) NetworkFrame::PacketData NetworkFrame::GetUdpPayloadForSLL(NetworkFrame::PacketData& packet, InternetAddress& source, InternetAddress& destination)
{ {
Packet result(data); PacketData result(packet);
if (result.mLength < 16) if (packet.mLength < 16)
return Payload(); return PacketData();
const LinuxSllHeader* sll = reinterpret_cast<const LinuxSllHeader*>(result.mData); const LinuxSllHeader* sll = reinterpret_cast<const LinuxSllHeader*>(packet.mData);
result.mData += sizeof(LinuxSllHeader); packet.mData += sizeof(LinuxSllHeader);
result.mLength -= sizeof(LinuxSllHeader); packet.mLength -= sizeof(LinuxSllHeader);
switch (ntohs(sll->mProtocolType)) switch (ntohs(sll->mProtocolType))
{ {
case 0x0800: case 0x0800:
return GetUdpPayloadForIp4(result); return GetUdpPayloadForIp4(packet, source, destination);
case 0x86DD: case 0x86DD:
return GetUdpPayloadForIp6(result); return GetUdpPayloadForIp6(packet, source, destination);
default: default:
return Payload(); return PacketData();
} }
} }
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForLoopback(const Packet& data) NetworkFrame::PacketData NetworkFrame::GetUdpPayloadForLoopback(NetworkFrame::PacketData& packet, InternetAddress& source, InternetAddress& destination)
{ {
Packet result(data); PacketData result(packet);
if (result.mLength < 16) if (packet.mLength < 16)
return Payload(); return PacketData();
struct LoopbackHeader struct LoopbackHeader
{ {
uint32_t mProtocolType; uint32_t mProtocolType;
}; };
const LoopbackHeader* lh = reinterpret_cast<const LoopbackHeader*>(result.mData); const LoopbackHeader* lh = reinterpret_cast<const LoopbackHeader*>(packet.mData);
result.mData += sizeof(LoopbackHeader); packet.mData += sizeof(LoopbackHeader);
result.mLength -= sizeof(LoopbackHeader); packet.mLength -= sizeof(LoopbackHeader);
switch (lh->mProtocolType) switch (lh->mProtocolType)
{ {
case AF_INET: case AF_INET:
return GetUdpPayloadForIp4(result); return GetUdpPayloadForIp4(packet, source, destination);
case AF_INET6: case AF_INET6:
return GetUdpPayloadForIp6(result); return GetUdpPayloadForIp6(packet, source, destination);
default: default:
return Payload(); return PacketData();
} }
} }
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForIp4(const Packet& data) NetworkFrame::PacketData NetworkFrame::GetUdpPayloadForIp4(NetworkFrame::PacketData& packet, InternetAddress& source, InternetAddress& destination)
{ {
Packet result(data); PacketData result(packet);
const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(data.mData); const Ip4Header* ip4 = reinterpret_cast<const Ip4Header*>(packet.mData);
if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0) if (ip4->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
return Payload(); return PacketData(nullptr, 0);
result.mData += ip4->headerLength(); result.mData += ip4->headerLength();
result.mLength -= ip4->headerLength(); result.mLength -= ip4->headerLength();
@@ -173,15 +152,13 @@ NetworkFrame::Payload NetworkFrame::GetUdpPayloadForIp4(const Packet& data)
if (length - sizeof(UdpHeader) < (size_t)result.mLength) if (length - sizeof(UdpHeader) < (size_t)result.mLength)
result.mLength = length - sizeof(UdpHeader); result.mLength = length - sizeof(UdpHeader);
InternetAddress addr_source; source.setIp(ip4->mSource);
addr_source.setIp(ip4->mSource); source.setPort(ntohs(udp->mSourcePort));
addr_source.setPort(ntohs(udp->mSourcePort));
InternetAddress addr_dest; destination.setIp(ip4->mDestination);
addr_dest.setIp(ip4->mDestination); destination.setPort(ntohs(udp->mDestinationPort));
addr_dest.setPort(ntohs(udp->mDestinationPort));
return {.data = result, .source = addr_source, .dest = addr_dest}; return result;
} }
struct Ip6Header struct Ip6Header
@@ -211,10 +188,10 @@ struct Ip6Header
struct in6_addr dst_ip; struct in6_addr dst_ip;
}; };
NetworkFrame::Payload NetworkFrame::GetUdpPayloadForIp6(const Packet& data) NetworkFrame::PacketData NetworkFrame::GetUdpPayloadForIp6(NetworkFrame::PacketData& packet, InternetAddress& source, InternetAddress& destination)
{ {
Packet result(data); PacketData result(packet);
const Ip6Header* ip6 = reinterpret_cast<const Ip6Header*>(result.mData); const Ip6Header* ip6 = reinterpret_cast<const Ip6Header*>(packet.mData);
/*if (ip6->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0) /*if (ip6->mProtocol != IPPROTO_UDP && ip4->mProtocol != 0)
return PacketData(nullptr, 0); return PacketData(nullptr, 0);
*/ */
@@ -226,13 +203,18 @@ NetworkFrame::Payload NetworkFrame::GetUdpPayloadForIp6(const Packet& data)
result.mData += sizeof(UdpHeader); result.mData += sizeof(UdpHeader);
result.mLength -= sizeof(UdpHeader); result.mLength -= sizeof(UdpHeader);
InternetAddress addr_source; /*
addr_source.setIp(ip6->src_ip); if (result.mLength != ntohs(udp->mDatagramLength))
addr_source.setPort(ntohs(udp->mSourcePort)); return PacketData(nullptr, 0);
*/
InternetAddress addr_dest; source.setIp(ip6->src_ip);
addr_dest.setIp(ip6->dst_ip); source.setPort(ntohs(udp->mSourcePort));
addr_dest.setPort(ntohs(udp->mDestinationPort)); //std::cout << source.toStdString() << " - ";
return {.data = result, .source = addr_source, .dest = addr_dest}; destination.setIp(ip6->dst_ip);
destination.setPort(ntohs(udp->mDestinationPort));
//std::cout << destination.toStdString() << std::endl;
return result;
} }
+8 -21
View File
@@ -7,38 +7,25 @@
class NetworkFrame class NetworkFrame
{ {
public: public:
struct Packet struct PacketData
{ {
const uint8_t* mData; const uint8_t* mData;
size_t mLength; size_t mLength;
Packet(const uint8_t* data, size_t length) PacketData(const uint8_t* data, size_t length)
:mData(data), mLength(length) :mData(data), mLength(length)
{} {}
Packet() PacketData()
:mData(nullptr), mLength(0) :mData(nullptr), mLength(0)
{} {}
bool is_empty() const
{
return mData == nullptr || mLength == 0;
}
}; };
struct Payload static PacketData GetUdpPayloadForEthernet(PacketData& packet, InternetAddress& source, InternetAddress& destination);
{ static PacketData GetUdpPayloadForIp4(PacketData& packet, InternetAddress& source, InternetAddress& destination);
Packet data; static PacketData GetUdpPayloadForIp6(PacketData& packet, InternetAddress& source, InternetAddress& destination);
InternetAddress source; static PacketData GetUdpPayloadForSLL(PacketData& packet, InternetAddress& source, InternetAddress& destination);
InternetAddress dest; static PacketData GetUdpPayloadForLoopback(PacketData& packet, InternetAddress& source, InternetAddress& destination);
};
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 struct EthernetHeader
{ {
+77 -96
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -7,9 +7,8 @@
# include <asm/ioctls.h> # include <asm/ioctls.h>
#endif #endif
#include "../engine_config.h" #include "../config.h"
#include "HL_NetworkSocket.h" #include "HL_NetworkSocket.h"
#include "HL_Log.h"
#if defined(TARGET_OSX) || defined(TARGET_LINUX) #if defined(TARGET_OSX) || defined(TARGET_LINUX)
# include <fcntl.h> # include <fcntl.h>
@@ -20,136 +19,124 @@
#endif #endif
#include <assert.h> #include <assert.h>
#define LOG_SUBSYSTEM "network"
DatagramSocket::DatagramSocket() DatagramSocket::DatagramSocket()
:mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0) :mFamily(AF_INET), mHandle(INVALID_SOCKET), mLocalPort(0)
{} {
}
DatagramSocket::~DatagramSocket() DatagramSocket::~DatagramSocket()
{ {
internalClose(); closeSocket();
} }
void DatagramSocket::open(int family) void DatagramSocket::open(int family)
{ {
if (mHandle != INVALID_SOCKET || mFamily != family) if (mHandle != INVALID_SOCKET || mFamily != family)
closeSocket(); closeSocket();
assert(family == AF_INET || family == AF_INET6); assert(family == AF_INET || family == AF_INET6);
mFamily = family; mFamily = family;
mHandle = ::socket(mFamily, SOCK_DGRAM, IPPROTO_UDP); mHandle = ::socket(mFamily, SOCK_DGRAM, IPPROTO_UDP);
if (mHandle != INVALID_SOCKET) 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)
{ {
sockaddr_in addr4; sockaddr_in6 addr6; mLocalPort = ntohs(mFamily == AF_INET ? addr4.sin_port : addr6.sin6_port);
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() int DatagramSocket::localport()
{ {
return mLocalPort; return mLocalPort;
} }
void DatagramSocket::sendDatagram(InternetAddress &dest, const void *packetData, unsigned int packetSize) void DatagramSocket::sendDatagram(InternetAddress &dest, const void *packetData, unsigned int packetSize)
{ {
if (mHandle == INVALID_SOCKET) if (mHandle == INVALID_SOCKET)
return; return;
/*int sent = */::sendto(mHandle, (const char*)packetData, packetSize, 0, dest.genericsockaddr(), dest.sockaddrLen()); int sent = ::sendto(mHandle, (const char*)packetData, packetSize, 0, dest.genericsockaddr(), dest.sockaddrLen());
} }
unsigned DatagramSocket::recvDatagram(InternetAddress &src, void *packetBuffer, unsigned packetCapacity) unsigned DatagramSocket::recvDatagram(InternetAddress &src, void *packetBuffer, unsigned packetCapacity)
{ {
if (mHandle == INVALID_SOCKET) if (mHandle == INVALID_SOCKET)
return 0;
sockaddr* addr = nullptr;
socklen_t addrLen = 0;
sockaddr_in addr_4 = {AF_INET, 0, {0}, {0}};
sockaddr_in6 addr_6 = {AF_INET6, 0, 0, {0}, 0};
switch (mFamily)
{
case AF_INET: addr = (sockaddr*)&addr_4; addrLen = sizeof(addr_4); break;
case AF_INET6: addr = (sockaddr*)&addr_6; addrLen = sizeof(addr_6); break;
default:
assert(0);
}
if (!addr || !addrLen)
return 0;
int received = ::recvfrom(mHandle, (char*)packetBuffer, packetCapacity, 0, addr, &addrLen);
if (received > 0)
{
src = InternetAddress((sockaddr&)*addr, addrLen);
return received;
}
return 0; return 0;
}
void DatagramSocket::internalClose() sockaddr_in sourceaddr;
{
if (mHandle != INVALID_SOCKET)
{
#ifdef WIN32 #ifdef WIN32
::closesocket(mHandle); int addrlen = sizeof(sourceaddr);
#else #else
close(mHandle); socklen_t addrlen = sizeof(sourceaddr);
#endif #endif
mHandle = INVALID_SOCKET; 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::closeSocket() void DatagramSocket::closeSocket()
{ {
internalClose(); if (mHandle != INVALID_SOCKET)
{
#ifdef WIN32
::closesocket(mHandle);
#else
close(mHandle);
#endif
mHandle = INVALID_SOCKET;
}
} }
bool DatagramSocket::isValid() const bool DatagramSocket::isValid() const
{ {
return mHandle != INVALID_SOCKET; return mHandle != INVALID_SOCKET;
} }
int DatagramSocket::family() const int DatagramSocket::family() const
{ {
return mFamily; return mFamily;
} }
bool DatagramSocket::setBlocking(bool blocking) bool DatagramSocket::setBlocking(bool blocking)
{ {
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
unsigned long mode = blocking ? 0 : 1; unsigned long mode = blocking ? 0 : 1;
return (ioctlsocket(mHandle, FIONBIO, &mode) == 0) ? true : false; return (ioctlsocket(mHandle, FIONBIO, &mode) == 0) ? true : false;
#endif #endif
#if defined(TARGET_OSX) || defined(TARGET_LINUX) #if defined(TARGET_OSX) || defined(TARGET_LINUX)
int flags = fcntl(mHandle, F_GETFL, 0); int flags = fcntl(mHandle, F_GETFL, 0);
if (flags < 0) if (flags < 0)
return false; return false;
flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK); flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK);
return (fcntl(mHandle, F_SETFL, flags) == 0) ? true : false; return (fcntl(mHandle, F_SETFL, flags) == 0) ? true : false;
#endif #endif
#if defined(TARGET_ANDROID) #if defined(TARGET_ANDROID)
unsigned long mode = blocking ? 0 : 1; unsigned long mode = blocking ? 0 : 1;
return (ioctl(mHandle, FIONBIO, &mode) == 0) ? true : false; return (ioctl(mHandle, FIONBIO, &mode) == 0) ? true : false;
#endif #endif
return false; return false;
} }
SOCKET DatagramSocket::socket() const SOCKET DatagramSocket::socket() const
{ {
return mHandle; return mHandle;
} }
DatagramAgreggator::DatagramAgreggator() DatagramAgreggator::DatagramAgreggator()
{ {
FD_ZERO(&mReadSet); FD_ZERO(&mReadSet);
mMaxHandle = 0; mMaxHandle = 0;
} }
DatagramAgreggator::~DatagramAgreggator() DatagramAgreggator::~DatagramAgreggator()
@@ -158,44 +145,38 @@ DatagramAgreggator::~DatagramAgreggator()
void DatagramAgreggator::addSocket(PDatagramSocket socket) void DatagramAgreggator::addSocket(PDatagramSocket socket)
{ {
if (socket->mHandle == INVALID_SOCKET) if (socket->mHandle == INVALID_SOCKET)
return; return;
if (mSocketVector.size() >= 62) FD_SET(socket->mHandle, &mReadSet);
{ if (socket->mHandle > mMaxHandle)
ICELogError(<< "fd_set overflow; too much sockets"); mMaxHandle = socket->mHandle;
return;
}
FD_SET(socket->mHandle, &mReadSet); mSocketVector.push_back(socket);
if (socket->mHandle > mMaxHandle)
mMaxHandle = socket->mHandle;
mSocketVector.push_back(socket);
} }
unsigned DatagramAgreggator::count() unsigned DatagramAgreggator::count()
{ {
return mSocketVector.size(); return mSocketVector.size();
} }
bool DatagramAgreggator::hasDataAtIndex(unsigned index) bool DatagramAgreggator::hasDataAtIndex(unsigned index)
{ {
PDatagramSocket socket = mSocketVector[index]; PDatagramSocket socket = mSocketVector[index];
return FD_ISSET(socket->mHandle, &mReadSet); return (FD_ISSET(socket->mHandle, &mReadSet) != 0);
} }
PDatagramSocket DatagramAgreggator::socketAt(unsigned index) PDatagramSocket DatagramAgreggator::socketAt(unsigned index)
{ {
return mSocketVector[index]; return mSocketVector[index];
} }
bool DatagramAgreggator::waitForData(std::chrono::milliseconds timeout) bool DatagramAgreggator::waitForData(unsigned milliseconds)
{ {
timeval tv; timeval tv;
tv.tv_sec = timeout.count() / 1000; tv.tv_sec = milliseconds / 1000;
tv.tv_usec = (timeout.count() % 1000) * 1000; tv.tv_usec = (milliseconds % 1000) * 1000;
int rescode = ::select(mMaxHandle+1, &mReadSet, nullptr, nullptr, &tv); int rescode = ::select(mMaxHandle, &mReadSet, NULL, NULL, &tv);
return rescode > 0; return rescode > 0;
} }
+31 -34
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2026 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -9,61 +9,58 @@
#include "HL_InternetAddress.h" #include "HL_InternetAddress.h"
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <chrono>
class NetworkSocket class NetworkSocket
{ {
public: public:
virtual int localport() = 0; virtual int localport() = 0;
}; };
class DatagramSocket class DatagramSocket
{ {
friend class SocketHeap; friend class SocketHeap;
friend class DatagramAgreggator; friend class DatagramAgreggator;
public: public:
DatagramSocket(); DatagramSocket();
virtual ~DatagramSocket(); virtual ~DatagramSocket();
virtual int localport(); virtual int localport();
virtual void sendDatagram(InternetAddress& dest, const void* packetData, unsigned packetSize); virtual void sendDatagram(InternetAddress& dest, const void* packetData, unsigned packetSize);
virtual unsigned recvDatagram(InternetAddress& src, void* packetBuffer, unsigned packetCapacity); virtual unsigned recvDatagram(InternetAddress& src, void* packetBuffer, unsigned packetCapacity);
virtual void closeSocket(); virtual void closeSocket();
virtual bool isValid() const; virtual bool isValid() const;
virtual int family() const; virtual int family() const;
virtual bool setBlocking(bool blocking); virtual bool setBlocking(bool blocking);
virtual SOCKET socket() const; virtual SOCKET socket() const;
virtual void open(int family);
virtual void open(int family);
protected: protected:
int mFamily; int mFamily;
SOCKET mHandle; SOCKET mHandle;
int mLocalPort; int mLocalPort;
void internalClose();
}; };
typedef std::shared_ptr<DatagramSocket> PDatagramSocket; typedef std::shared_ptr<DatagramSocket> PDatagramSocket;
class DatagramAgreggator class DatagramAgreggator
{ {
public: public:
DatagramAgreggator(); DatagramAgreggator();
~DatagramAgreggator(); ~DatagramAgreggator();
void addSocket(PDatagramSocket socket); void addSocket(PDatagramSocket socket);
unsigned count(); unsigned count();
bool hasDataAtIndex(unsigned index); bool hasDataAtIndex(unsigned index);
PDatagramSocket socketAt(unsigned index); PDatagramSocket socketAt(unsigned index);
bool waitForData(std::chrono::milliseconds timeout); bool waitForData(unsigned milliseconds);
protected: protected:
typedef std::vector<PDatagramSocket> SocketList; typedef std::vector<PDatagramSocket> SocketList;
SocketList mSocketVector; SocketList mSocketVector;
fd_set mReadSet; fd_set mReadSet;
SOCKET mMaxHandle; SOCKET mMaxHandle;
}; };
#endif #endif
-179
View File
@@ -1,179 +0,0 @@
#pragma once
/// @file
/// @brief A thread-local fixed-block memory pool plus a std-conforming Allocator wrapper, used to
/// pool the small, short-lived, per-RTP-packet shared_ptr nodes produced by std::allocate_shared
/// (e.g. allocate_shared<jrtplib::RTPPacket> on the capture path and allocate_shared<RtpBuffer::
/// Packet> in the jitter buffer). Those objects are fixed-size and churn at the packet rate, so a
/// pool removes them from the general allocator's hot path.
///
/// Pooling is active by default. Define HL_RTP_POOL=0 at compile time to make hl::PoolAllocator a
/// transparent passthrough to the global allocator (i.e. allocate_shared behaves like make_shared)
/// for A/B benchmarking without touching the call sites.
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <new>
#include <vector>
#ifndef HL_RTP_POOL
# define HL_RTP_POOL 1
#endif
namespace hl
{
#if HL_RTP_POOL
/// @class FixedBlockPool
/// A process-wide, fixed-block pool with a lock-free thread-local fast path. Identical in
/// design to the pcpp Layer pool: uniform 256-byte blocks carved from 64 KB chunks, an
/// intrusive thread-local free list, and a per-block header tag so deallocate() is O(1) and
/// lock-free for any block (and can tell pooled blocks from the global-allocator fallback used
/// for oversized requests) regardless of the freeing thread. Uniform block size makes a block
/// allocated on one thread safe to free on another (it joins the freeing thread's free list).
class FixedBlockPool
{
public:
/// Usable bytes handed back to the caller from a pooled block. Comfortably covers the
/// shared_ptr nodes we pool (control block + RTPPacket / RtpBuffer::Packet, ~90-130 bytes).
static constexpr std::size_t PayloadSize = 240;
static constexpr std::size_t BlocksPerChunk = 256;
static void* allocate(std::size_t size)
{
if (size > PayloadSize)
{
uint8_t* raw = static_cast<uint8_t*>(::operator new(size + HeaderSize));
tagOf(raw) = TagGlobal;
return raw + HeaderSize;
}
void*& head = freeListHead();
if (head == nullptr)
head = registry().refill();
uint8_t* block = static_cast<uint8_t*>(head);
head = nextOf(block);
return block + HeaderSize;
}
static void deallocate(void* ptr) noexcept
{
if (ptr == nullptr)
return;
uint8_t* block = static_cast<uint8_t*>(ptr) - HeaderSize;
if (tagOf(block) == TagPool)
{
void*& head = freeListHead();
nextOf(block) = head;
head = block;
}
else
{
::operator delete(static_cast<void*>(block));
}
}
private:
static constexpr std::size_t HeaderSize =
alignof(std::max_align_t) >= sizeof(uint64_t) ? alignof(std::max_align_t) : sizeof(uint64_t);
static constexpr std::size_t BlockSize = HeaderSize + PayloadSize;
static constexpr uint64_t TagPool = 0x504F4F4C52545008ULL; // "POOLRTP\b"
static constexpr uint64_t TagGlobal = 0x474C4F42524C0808ULL; // "GLOBRL\b\b"
static uint64_t& tagOf(void* block) noexcept
{
return *reinterpret_cast<uint64_t*>(block);
}
static void*& nextOf(void* block) noexcept
{
return *reinterpret_cast<void**>(static_cast<uint8_t*>(block) + HeaderSize);
}
static void*& freeListHead() noexcept
{
static thread_local void* head = nullptr;
return head;
}
class ChunkRegistry
{
public:
~ChunkRegistry()
{
std::lock_guard<std::mutex> lock(m_Mutex);
for (uint8_t* chunk : m_Chunks)
::operator delete(chunk);
m_Chunks.clear();
}
void* refill()
{
const std::size_t chunkBytes = BlockSize * BlocksPerChunk;
uint8_t* chunk = static_cast<uint8_t*>(::operator new(chunkBytes));
{
std::lock_guard<std::mutex> lock(m_Mutex);
m_Chunks.push_back(chunk);
}
void* list = nullptr;
for (std::size_t i = 0; i < BlocksPerChunk; ++i)
{
uint8_t* block = chunk + i * BlockSize;
tagOf(block) = TagPool;
nextOf(block) = list;
list = block;
}
return list;
}
private:
std::mutex m_Mutex;
std::vector<uint8_t*> m_Chunks;
};
static ChunkRegistry& registry()
{
static ChunkRegistry instance;
return instance;
}
};
#endif // HL_RTP_POOL
/// @class PoolAllocator
/// A stateless, std-conforming Allocator suitable for std::allocate_shared. When HL_RTP_POOL is
/// enabled it serves single-node allocations from FixedBlockPool; otherwise (and for any request
/// that does not fit a pooled block) it delegates to the global allocator, matching make_shared.
template <class T> struct PoolAllocator
{
using value_type = T;
PoolAllocator() noexcept = default;
template <class U> PoolAllocator(const PoolAllocator<U>&) noexcept {}
T* allocate(std::size_t n)
{
#if HL_RTP_POOL
return static_cast<T*>(FixedBlockPool::allocate(n * sizeof(T)));
#else
return static_cast<T*>(::operator new(n * sizeof(T)));
#endif
}
void deallocate(T* p, std::size_t /*n*/) noexcept
{
#if HL_RTP_POOL
FixedBlockPool::deallocate(p);
#else
::operator delete(static_cast<void*>(p));
#endif
}
template <class U> bool operator==(const PoolAllocator<U>&) const noexcept { return true; }
template <class U> bool operator!=(const PoolAllocator<U>&) const noexcept { return false; }
};
} // namespace hl
+10 -13
View File
@@ -1,7 +1,6 @@
#include "HL_Process.h" #include "HL_Process.h"
#include <thread> #include <thread>
#include <memory> #include <memory>
#include <algorithm>
#ifdef TARGET_WIN #ifdef TARGET_WIN
# define popen _popen # define popen _popen
@@ -10,6 +9,7 @@
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
#include <Windows.h> #include <Windows.h>
#include <iostream>
#include "helper/HL_String.h" #include "helper/HL_String.h"
int OsProcess::execSystem(const std::string& cmd) int OsProcess::execSystem(const std::string& cmd)
@@ -39,7 +39,7 @@ std::string OsProcess::execCommand(const std::string& cmd)
PROCESS_INFORMATION pi = { 0 }; PROCESS_INFORMATION pi = { 0 };
char* cmdline = (char*)_alloca(cmd.size()+1); char* cmdline = (char*)_alloca(cmd.size()+1);
strcpy(cmdline, strx::replace(cmd, "/", "\\").c_str()); strcpy(cmdline, StringHelper::replace(cmd, "/", "\\").c_str());
BOOL fSuccess = CreateProcessA( nullptr, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); BOOL fSuccess = CreateProcessA( nullptr, cmdline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (! fSuccess) if (! fSuccess)
@@ -68,7 +68,7 @@ std::string OsProcess::execCommand(const std::string& cmd)
if (!dwAvail) // no data available, return if (!dwAvail) // no data available, return
break; break;
if (!::ReadFile(hPipeRead, buf, std::min(sizeof(buf) - 1, (size_t)dwAvail), &dwRead, NULL) || !dwRead) if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
// error, the child process might ended // error, the child process might ended
break; break;
@@ -114,7 +114,7 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
memset(&pi, 0, sizeof pi); memset(&pi, 0, sizeof pi);
char* cmdbuffer = (char*)_alloca(cmdline.size()+1); char* cmdbuffer = (char*)_alloca(cmdline.size()+1);
strcpy(cmdbuffer, strx::replace(cmdline, "/", "\\").c_str()); strcpy(cmdbuffer, StringHelper::replace(cmdline, "/", "\\").c_str());
BOOL fSuccess = CreateProcessA( nullptr, cmdbuffer, nullptr, nullptr, TRUE, BOOL fSuccess = CreateProcessA( nullptr, cmdbuffer, nullptr, nullptr, TRUE,
@@ -148,7 +148,7 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
break; break;
int filled = strlen(buf); int filled = strlen(buf);
if (!::ReadFile(hPipeRead, buf + filled, std::min(sizeof(buf) - 1 - filled, (size_t)dwAvail), &dwRead, nullptr) || !dwRead) if (!::ReadFile(hPipeRead, buf + filled, min(sizeof(buf) - 1 - filled, dwAvail), &dwRead, nullptr) || !dwRead)
// error, the child process might ended // error, the child process might ended
break; break;
@@ -160,14 +160,14 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
{ {
std::string line(buf, cr - buf -1); std::string line(buf, cr - buf -1);
if (callback) if (callback)
callback(strx::trim(line)); callback(StringHelper::trim(line));
memmove(buf, cr + 1, strlen(cr+1) + 1); memmove(buf, cr + 1, strlen(cr+1) + 1);
} }
} }
} //for } //for
if (buf[0]) if (buf[0])
callback(strx::trim(std::string(buf))); callback(StringHelper::trim(std::string(buf)));
char ctrlc = 3; char ctrlc = 3;
//if (finish_flag) //if (finish_flag)
@@ -278,16 +278,16 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
} }
while (r == sizeof(buffer) - 1); while (r == sizeof(buffer) - 1);
if (lines.find('\n') != std::string::npos && line_callback) if (lines.find("\n") != std::string::npos && line_callback)
{ {
std::string::size_type p = 0; std::string::size_type p = 0;
while (p < lines.size()) while (p < lines.size())
{ {
std::string::size_type d = lines.find('\n', p); std::string::size_type d = lines.find("\n", p);
if (d != std::string::npos) if (d != std::string::npos)
{ {
if (line_callback) if (line_callback)
line_callback(strx::trim(lines.substr(p, d-p))); line_callback(StringHelper::trim(lines.substr(p, d-p)));
p = d + 1; p = d + 1;
} }
} }
@@ -311,8 +311,6 @@ std::shared_ptr<std::thread> OsProcess::asyncExecCommand(const std::string& cmdl
return t; return t;
} }
#if defined(TARGET_OSX) || defined(TARGET_LINUX)
pid_t OsProcess::findPid(const std::string& cmdline) pid_t OsProcess::findPid(const std::string& cmdline)
{ {
try try
@@ -334,6 +332,5 @@ void OsProcess::killByPid(pid_t pid)
return; return;
execSystem("kill -9 " + std::to_string(pid) + " &"); execSystem("kill -9 " + std::to_string(pid) + " &");
} }
#endif
#endif #endif
+89 -379
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -8,96 +8,63 @@
# include <Windows.h> # include <Windows.h>
#endif #endif
#if defined(TARGET_LINUX) || defined(TARGET_ANDROID) || defined(TARGET_OSX)
# include <arpa/inet.h>
#endif
#include "HL_Rtp.h" #include "HL_Rtp.h"
#include "HL_Exception.h" #include "HL_Exception.h"
#include "HL_Log.h" #include "HL_String.h"
#include "jrtplib/src/rtprawpacket.h" #if defined(USE_RTP_DUMP)
#include "jrtplib/src/rtpipv4address.h" # include "jrtplib/src/rtprawpacket.h"
# include "jrtplib/src/rtpipv4address.h"
#endif
#include <stdexcept> #if !defined(TARGET_WIN)
#include <fstream> # include <alloca.h>
#include <cstring> #endif
#include <cstdio>
#include <chrono>
#define LOG_SUBSYSTEM "network" #include <sstream>
#include <tuple>
static constexpr size_t MAX_RTP_PACKET_SIZE = 65535;
static const char RTPDUMP_SHEBANG[] = "#!rtpplay1.0";
// RTP fixed header (little-endian bit-field layout)
struct RtpHeader struct RtpHeader
{ {
unsigned char cc:4; /* CSRC count */ unsigned char cc:4; /* CSRC count */
unsigned char x:1; /* header extension flag */ unsigned char x:1; /* header extension flag */
unsigned char p:1; /* padding flag */ unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */ unsigned char version:2; /* protocol version */
unsigned char pt:7; /* payload type */ unsigned char pt:7; /* payload type */
unsigned char m:1; /* marker bit */ unsigned char m:1; /* marker bit */
unsigned short seq; /* sequence number */ unsigned short seq; /* sequence number */
unsigned int ts; /* timestamp */ unsigned int ts; /* timestamp */
unsigned int ssrc; /* synchronization source */ unsigned int ssrc; /* synchronization source */
}; };
struct RtcpHeader struct RtcpHeader
{ {
unsigned char rc:5; /* reception report count */ unsigned char rc:5; /* reception report count */
unsigned char p:1; /* padding flag */ unsigned char p:1; /* padding flag */
unsigned char version:2; /* protocol version */ unsigned char version:2; /* protocol version */
unsigned char pt; /* payload type */ unsigned char pt:8; /* payload type */
uint16_t len; /* length */ uint16_t len; /* length */
uint32_t ssrc; /* synchronization source */ uint32_t ssrc; /* synchronization source */
}; };
// --- IPv4 address helpers ---
static std::string ipToString(uint32_t ip)
{
// ip in host byte order → dotted-decimal
return std::to_string((ip >> 24) & 0xFF) + "." +
std::to_string((ip >> 16) & 0xFF) + "." +
std::to_string((ip >> 8) & 0xFF) + "." +
std::to_string( ip & 0xFF);
}
static uint32_t stringToIp(const std::string& s)
{
unsigned a = 0, b = 0, c = 0, d = 0;
if (std::sscanf(s.c_str(), "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
return 0;
if (a > 255 || b > 255 || c > 255 || d > 255)
return 0;
return (a << 24) | (b << 16) | (c << 8) | d;
}
// --- RtpHelper implementation ---
bool RtpHelper::isRtp(const void* buffer, size_t length) bool RtpHelper::isRtp(const void* buffer, size_t length)
{ {
if (length < 12) if (length < 12)
return false; return false;
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer); unsigned char _type = reinterpret_cast<const RtpHeader*>(buffer)->pt;
if (h->version != 2) bool rtp = ( (_type & 0x7F) >= 96 && (_type & 0x7F) <= 127) || ((_type & 0x7F) < 35);
return false;
unsigned char pt = h->pt;
bool rtp = (pt >= 96 && pt <= 127) || (pt < 35);
return rtp; return rtp;
} }
bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length) bool RtpHelper::isRtpOrRtcp(const void* buffer, size_t length)
{ {
// A minimal RTCP packet (e.g. an empty receiver report) is 8 bytes if (length < 12)
if (length < 8)
return false; return false;
const RtcpHeader* h = reinterpret_cast<const RtcpHeader*>(buffer); unsigned char b = ((const unsigned char*)buffer)[0];
return h->version == 2;
return (b & 0xC0 ) == 128;
} }
bool RtpHelper::isRtcp(const void* buffer, size_t length) bool RtpHelper::isRtcp(const void* buffer, size_t length)
@@ -108,18 +75,9 @@ bool RtpHelper::isRtcp(const void* buffer, size_t length)
unsigned RtpHelper::findSsrc(const void* buffer, size_t length) unsigned RtpHelper::findSsrc(const void* buffer, size_t length)
{ {
if (isRtp(buffer, length)) if (isRtp(buffer, length))
return ntohl(reinterpret_cast<const RtpHeader*>(buffer)->ssrc); return reinterpret_cast<const RtpHeader*>(buffer)->ssrc;
else if (isRtpOrRtcp(buffer, length)) else
return ntohl(reinterpret_cast<const RtcpHeader*>(buffer)->ssrc); return reinterpret_cast<const RtcpHeader*>(buffer)->ssrc;
return 0;
}
void RtpHelper::setSsrc(void* buffer, size_t length, uint32_t ssrc)
{
if (isRtp(buffer, length))
reinterpret_cast<RtpHeader*>(buffer)->ssrc = htonl(ssrc);
else if (isRtpOrRtcp(buffer, length))
reinterpret_cast<RtcpHeader*>(buffer)->ssrc = htonl(ssrc);
} }
int RtpHelper::findPtype(const void* buffer, size_t length) int RtpHelper::findPtype(const void* buffer, size_t length)
@@ -140,191 +98,47 @@ int RtpHelper::findPacketNo(const void *buffer, size_t length)
int RtpHelper::findPayloadLength(const void* buffer, size_t length) int RtpHelper::findPayloadLength(const void* buffer, size_t length)
{ {
if (!isRtp(buffer, length)) if (isRtp(buffer, length))
{
return length - 12;
}
else
return -1; return -1;
}
const RtpHeader* h = reinterpret_cast<const RtpHeader*>(buffer); #if defined(USE_RTPDUMP)
const uint8_t* p = static_cast<const uint8_t*>(buffer); RtpDump::RtpDump(const char *filename)
:mFilename(filename)
{}
// Fixed header (12 bytes) + CSRC list (4 * CC bytes) RtpDump::~RtpDump()
size_t offset = 12 + 4u * h->cc; {
if (offset > length) flush();
return -1; for (PacketList::iterator packetIter=mPacketList.begin(); packetIter!=mPacketList.end(); ++packetIter)
{
// Header extension //free(packetIter->mData);
if (h->x) { delete packetIter->mPacket;
if (offset + 4 > length)
return -1;
uint16_t extWords = (static_cast<uint16_t>(p[offset + 2]) << 8) | p[offset + 3];
offset += 4 + 4u * extWords;
if (offset > length)
return -1;
} }
size_t payloadLen = length - offset;
// Padding
if (h->p && payloadLen > 0) {
uint8_t padBytes = p[length - 1];
if (padBytes > payloadLen)
return -1;
payloadLen -= padBytes;
}
return static_cast<int>(payloadLen);
}
std::chrono::microseconds RtpHelper::toMicroseconds(const jrtplib::RTPTime& t)
{
return std::chrono::microseconds(uint64_t(t.GetDouble() * 1000000));
}
// --- RtpDump implementation ---
std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len)
{
if (!data || len < 12 || !RtpHelper::isRtp(data, len))
return nullptr;
try {
// Both are heap-allocated; RTPRawPacket takes ownership and deletes them
jrtplib::RTPIPAddress senderAddress = {jrtplib::RTPIPv4Address(uint32_t(0), uint16_t(0))};
uint8_t* dataCopy = new uint8_t[len];
std::memcpy(dataCopy, data, len);
jrtplib::RTPRawPacket raw(dataCopy, len, senderAddress, jrtplib::RTPTime(0), true);
auto packet = std::make_shared<jrtplib::RTPPacket>(raw);
if (packet->GetCreationError() != 0)
return nullptr;
return packet;
} catch (const std::exception& e) {
ICELogInfo(<< "Failed to parse RTP packet: " << e.what());
return nullptr;
}
}
RtpDump::RtpDump(const char* filename)
: mFilename(filename ? filename : "")
{
}
RtpDump::~RtpDump() = default;
void RtpDump::setSource(uint32_t ip, uint16_t port)
{
mSourceIp = ip;
mSourcePort = port;
} }
void RtpDump::load() void RtpDump::load()
{ {
if (mFilename.empty()) FILE* f = fopen(mFilename.c_str(), "rb");
throw std::runtime_error("No filename specified"); if (!f)
throw Exception(ERR_WAVFILE_FAILED);
std::ifstream input(mFilename, std::ios::binary); while (!feof(f))
if (!input.is_open()) {
throw std::runtime_error("Failed to open RTP dump file: " + mFilename); RtpData data;
fread(&data.mLength, sizeof data.mLength, 1, f);
mPacketList.clear(); data.mData = new char[data.mLength];
fread(data.mData, 1, data.mLength, f);
// --- 1. Text header: "#!rtpplay1.0 <ip>/<port>\n" --- jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
std::string textLine; jrtplib::RTPTime t(0);
std::getline(input, textLine); jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)data.mData, data.mLength, &addr, t, true);
if (textLine.compare(0, sizeof(RTPDUMP_SHEBANG) - 1, RTPDUMP_SHEBANG) != 0) data.mPacket = new jrtplib::RTPPacket(*raw);
throw std::runtime_error("Invalid rtpdump header: expected " + std::string(RTPDUMP_SHEBANG)); mPacketList.push_back(data);
// Parse source address from the text line
size_t spacePos = textLine.find(' ');
if (spacePos != std::string::npos) {
std::string addrPart = textLine.substr(spacePos + 1);
size_t slashPos = addrPart.find('/');
if (slashPos != std::string::npos) {
mSourceIp = stringToIp(addrPart.substr(0, slashPos));
try {
mSourcePort = static_cast<uint16_t>(std::stoi(addrPart.substr(slashPos + 1)));
} catch (...) {
mSourcePort = 0;
}
}
} }
// --- 2. Binary file header (RD_hdr_t, 16 bytes) ---
uint32_t buf32;
uint16_t buf16;
input.read(reinterpret_cast<char*>(&buf32), 4);
mStartSec = ntohl(buf32);
input.read(reinterpret_cast<char*>(&buf32), 4);
mStartUsec = ntohl(buf32);
input.read(reinterpret_cast<char*>(&buf32), 4); // source IP (already NBO in file)
// The binary header stores IP in network byte order; convert to host
mSourceIp = ntohl(buf32);
input.read(reinterpret_cast<char*>(&buf16), 2);
mSourcePort = ntohs(buf16);
input.read(reinterpret_cast<char*>(&buf16), 2); // padding — discard
if (!input.good())
throw std::runtime_error("Failed to read rtpdump binary header");
// --- 3. Packet records ---
size_t packetCount = 0;
while (input.good() && input.peek() != EOF) {
// Packet header: length(2) + plen(2) + offset(4) = 8 bytes
uint16_t recLength, plen;
uint32_t offsetMs;
input.read(reinterpret_cast<char*>(&recLength), 2);
if (input.gcount() != 2) break;
recLength = ntohs(recLength);
input.read(reinterpret_cast<char*>(&plen), 2);
if (input.gcount() != 2) break;
plen = ntohs(plen);
input.read(reinterpret_cast<char*>(&offsetMs), 4);
if (input.gcount() != 4) break;
offsetMs = ntohl(offsetMs);
// All-zeros record signals end of file in some implementations
if (recLength == 0 && plen == 0 && offsetMs == 0)
break;
if (plen == 0 || plen > MAX_RTP_PACKET_SIZE)
throw std::runtime_error("Invalid packet payload length: " + std::to_string(plen));
if (recLength < plen + 8)
throw std::runtime_error("Record length (" + std::to_string(recLength) +
") smaller than payload + header (" + std::to_string(plen + 8) + ")");
// Read body
std::vector<uint8_t> body(plen);
input.read(reinterpret_cast<char*>(body.data()), plen);
if (static_cast<size_t>(input.gcount()) != plen)
throw std::runtime_error("Incomplete packet data in rtpdump file");
// Skip any padding between plen and recLength-8
size_t pad = static_cast<size_t>(recLength) - 8 - plen;
if (pad > 0)
input.seekg(static_cast<std::streamoff>(pad), std::ios::cur);
RtpData entry;
entry.mRawData = std::move(body);
entry.mOffsetMs = offsetMs;
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
mPacketList.push_back(std::move(entry));
packetCount++;
}
ICELogInfo(<< "Loaded " << packetCount << " packets from " << mFilename);
mLoaded = true;
} }
size_t RtpDump::count() const size_t RtpDump::count() const
@@ -334,143 +148,39 @@ size_t RtpDump::count() const
jrtplib::RTPPacket& RtpDump::packetAt(size_t index) jrtplib::RTPPacket& RtpDump::packetAt(size_t index)
{ {
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
if (!mPacketList[index].mPacket)
throw std::runtime_error("No parsed RTP data at index " + std::to_string(index));
return *mPacketList[index].mPacket; return *mPacketList[index].mPacket;
} }
const std::vector<uint8_t>& RtpDump::rawDataAt(size_t index) const
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
return mPacketList[index].mRawData;
}
uint32_t RtpDump::offsetAt(size_t index) const
{
if (index >= mPacketList.size())
throw std::out_of_range("Packet index out of range: " + std::to_string(index));
return mPacketList[index].mOffsetMs;
}
void RtpDump::add(const void* buffer, size_t len) void RtpDump::add(const void* buffer, size_t len)
{ {
if (!buffer || len == 0) RtpData data;
return; data.mData = malloc(len);
memcpy(data.mData, buffer, len);
data.mLength = len;
uint32_t offsetMs = 0; jrtplib::RTPIPv4Address addr(jrtplib::RTPAddress::IPv4Address);
auto now = std::chrono::steady_clock::now(); jrtplib::RTPTime t(0);
jrtplib::RTPRawPacket* raw = new jrtplib::RTPRawPacket((unsigned char*)const_cast<void*>(data.mData), data.mLength, &addr, t, true);
if (!mRecording) { data.mPacket = new jrtplib::RTPPacket(*raw);
mRecording = true; //delete raw;
mRecordStart = now; mPacketList.push_back(data);
// Capture wall-clock start time
auto wallNow = std::chrono::system_clock::now();
auto epoch = wallNow.time_since_epoch();
auto sec = std::chrono::duration_cast<std::chrono::seconds>(epoch);
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(epoch - sec);
mStartSec = static_cast<uint32_t>(sec.count());
mStartUsec = static_cast<uint32_t>(usec.count());
} else {
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - mRecordStart);
offsetMs = static_cast<uint32_t>(elapsed.count());
}
add(buffer, len, offsetMs);
}
void RtpDump::add(const void* buffer, size_t len, uint32_t offsetMs)
{
if (!buffer || len == 0)
return;
// The record length field is 16-bit and covers payload + 8 byte header
if (len > MAX_RTP_PACKET_SIZE - 8)
throw std::runtime_error("Packet too large: " + std::to_string(len));
RtpData entry;
entry.mRawData.assign(static_cast<const uint8_t*>(buffer),
static_cast<const uint8_t*>(buffer) + len);
entry.mOffsetMs = offsetMs;
entry.mPacket = parseRtpData(entry.mRawData.data(), entry.mRawData.size());
mPacketList.push_back(std::move(entry));
} }
void RtpDump::flush() void RtpDump::flush()
{ {
if (mFilename.empty()) FILE* f = fopen(mFilename.c_str(), "wb");
throw std::runtime_error("No filename specified"); if (!f)
throw Exception(ERR_WAVFILE_FAILED);
std::ofstream output(mFilename, std::ios::binary); PacketList::iterator packetIter = mPacketList.begin();
if (!output.is_open()) for (;packetIter != mPacketList.end(); ++packetIter)
throw std::runtime_error("Failed to open file for writing: " + mFilename); {
RtpData& data = *packetIter;
// --- 1. Text header --- // Disabled for debugging only
std::string textLine = std::string(RTPDUMP_SHEBANG) + " " + //fwrite(&data.mLength, sizeof data.mLength, 1, f);
ipToString(mSourceIp) + "/" + fwrite(data.mData, data.mLength, 1, f);
std::to_string(mSourcePort) + "\n";
output.write(textLine.data(), static_cast<std::streamsize>(textLine.size()));
// --- 2. Binary file header (16 bytes) ---
uint32_t buf32;
uint16_t buf16;
buf32 = htonl(mStartSec);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf32 = htonl(mStartUsec);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf32 = htonl(mSourceIp);
output.write(reinterpret_cast<const char*>(&buf32), 4);
buf16 = htons(mSourcePort);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf16 = 0; // padding
output.write(reinterpret_cast<const char*>(&buf16), 2);
// --- 3. Packet records ---
size_t written = 0;
for (const auto& pkt : mPacketList) {
if (pkt.mRawData.empty())
continue;
uint16_t plen = static_cast<uint16_t>(pkt.mRawData.size());
uint16_t recLength = static_cast<uint16_t>(plen + 8);
buf16 = htons(recLength);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf16 = htons(plen);
output.write(reinterpret_cast<const char*>(&buf16), 2);
buf32 = htonl(pkt.mOffsetMs);
output.write(reinterpret_cast<const char*>(&buf32), 4);
output.write(reinterpret_cast<const char*>(pkt.mRawData.data()), plen);
written++;
} }
fclose(f);
if (!output.good())
throw std::runtime_error("Failed to write rtpdump file: " + mFilename);
ICELogInfo(<< "Wrote " << written << " packets to " << mFilename);
} }
#endif
void RtpDump::clear()
{
mPacketList.clear();
mLoaded = false;
mRecording = false;
}
+20 -95
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2026 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -6,14 +6,15 @@
#ifndef __HL_RTP_H #ifndef __HL_RTP_H
#define __HL_RTP_H #define __HL_RTP_H
#include "jrtplib/src/rtppacket.h" #if defined(USE_RTPDUMP)
# include "jrtplib/src/rtppacket.h"
#endif
#include "HL_Uuid.h"
#include "HL_InternetAddress.h"
#include <cstdint>
#include <cstdlib>
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory>
#include <chrono>
// Class to carry rtp/rtcp socket pair // Class to carry rtp/rtcp socket pair
template<class T> template<class T>
@@ -29,122 +30,46 @@ struct RtpPair
:mRtp(rtp), mRtcp(rtcp) :mRtp(rtp), mRtcp(rtcp)
{} {}
bool multiplexed() const { return mRtp == mRtcp; } bool multiplexed() { return mRtp == mRtcp; }
}; };
class RtpHelper class RtpHelper
{ {
public: public:
static bool isRtp(const void* buffer, size_t length); static bool isRtp(const void* buffer, size_t length);
static int findPtype(const void* buffer, size_t length); static int findPtype(const void* buffer, size_t length);
static int findPacketNo(const void *buffer, size_t length); static int findPacketNo(const void* buffer, size_t length);
static bool isRtpOrRtcp(const void* buffer, size_t length); static bool isRtpOrRtcp(const void* buffer, size_t length);
static bool isRtcp(const void* buffer, size_t length); static bool isRtcp(const void* buffer, size_t length);
static unsigned findSsrc(const void* buffer, size_t length); static unsigned findSsrc(const void* buffer, size_t length);
static void setSsrc(void* buffer, size_t length, uint32_t ssrc); static int findPayloadLength(const void* buffer, size_t length);
static int findPayloadLength(const void* buffer, size_t length);
static std::chrono::microseconds toMicroseconds(const jrtplib::RTPTime& t);
}; };
/** #if defined(USE_RTPDUMP)
* @brief Standard rtpdump file format (rtptools / Wireshark compatible)
*
* Conforms to the rtpdump format defined by rtptools:
* https://formats.kaitai.io/rtpdump/
*
* File layout:
* 1. Text header line:
* "#!rtpplay1.0 <source_ip>/<source_port>\n"
*
* 2. Binary file header (RD_hdr_t, 16 bytes, all big-endian):
* uint32_t start_sec - recording start time, seconds since epoch
* uint32_t start_usec - recording start time, microseconds
* uint32_t source_ip - source IP address (network byte order)
* uint16_t source_port - source port
* uint16_t padding - always 0
*
* 3. Packet records (repeated until EOF):
* Per-packet header (RD_packet_t, 8 bytes, all big-endian):
* uint16_t length - total record length (this 8-byte header + plen)
* uint16_t plen - RTP/RTCP payload length in bytes
* uint32_t offset - milliseconds since recording start
* Followed by plen bytes of RTP/RTCP packet data.
*
* Maximum single packet payload: 65535 bytes (enforced for safety).
*/
class RtpDump class RtpDump
{ {
protected: protected:
struct RtpData struct RtpData
{ {
std::shared_ptr<jrtplib::RTPPacket> mPacket; jrtplib::RTPPacket* mPacket;
std::vector<uint8_t> mRawData; void* mData;
uint32_t mOffsetMs = 0; size_t mLength;
}; };
typedef std::vector<RtpData> PacketList; typedef std::vector<RtpData> PacketList;
PacketList mPacketList; PacketList mPacketList;
std::string mFilename; std::string mFilename;
bool mLoaded = false;
// File header fields
uint32_t mSourceIp = 0;
uint16_t mSourcePort = 0;
uint32_t mStartSec = 0;
uint32_t mStartUsec = 0;
// Auto-compute packet offsets during recording
bool mRecording = false;
std::chrono::steady_clock::time_point mRecordStart;
std::shared_ptr<jrtplib::RTPPacket> parseRtpData(const uint8_t* data, size_t len);
public: public:
explicit RtpDump(const char* filename); RtpDump(const char* filename);
~RtpDump(); ~RtpDump();
/** Set source address for the file header (host byte order). */
void setSource(uint32_t ip, uint16_t port);
uint32_t sourceIp() const { return mSourceIp; }
uint16_t sourcePort() const { return mSourcePort; }
/**
* @brief Load packets from an rtpdump file
* @throws std::runtime_error on file/format error
*/
void load(); void load();
bool isLoaded() const { return mLoaded; }
size_t count() const; size_t count() const;
/**
* @brief Get parsed RTP packet at index
* @throws std::out_of_range if index is invalid
* @throws std::runtime_error if packet could not be parsed as RTP
*/
jrtplib::RTPPacket& packetAt(size_t index); jrtplib::RTPPacket& packetAt(size_t index);
/** @brief Get raw packet bytes at index */
const std::vector<uint8_t>& rawDataAt(size_t index) const;
/** @brief Get packet time offset in milliseconds */
uint32_t offsetAt(size_t index) const;
/** @brief Add a packet; time offset is auto-computed from first add() call */
void add(const void* data, size_t len); void add(const void* data, size_t len);
/** @brief Add a packet with an explicit millisecond offset */
void add(const void* data, size_t len, uint32_t offsetMs);
/**
* @brief Write all packets to file in rtpdump format
* @throws std::runtime_error on file error
*/
void flush(); void flush();
void clear();
const std::string& filename() const { return mFilename; }
}; };
#endif
#endif #endif
+8 -25
View File
@@ -3,14 +3,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../engine_config.h" #include "../config.h"
#ifdef WIN32 #ifdef WIN32
#include <winsock2.h> #include <winsock2.h>
#include <windows.h> #include <windows.h>
#endif #endif
#include <set> #include <set>
#include <assert.h> #include <assert.h>
#include <chrono>
#if !defined(TARGET_WIN) #if !defined(TARGET_WIN)
# include <unistd.h> // Responsible for close() call on Linux # include <unistd.h> // Responsible for close() call on Linux
#endif #endif
@@ -20,7 +20,7 @@
#include "HL_Sync.h" #include "HL_Sync.h"
#include "HL_Exception.h" #include "HL_Exception.h"
#define LOG_SUBSYSTEM "network" #define LOG_SUBSYSTEM "[SocketHeap]"
#ifndef WIN32 #ifndef WIN32
#define WSAGetLastError(X) errno #define WSAGetLastError(X) errno
@@ -28,7 +28,6 @@
#define WSAEADDRINUSE EADDRINUSE #define WSAEADDRINUSE EADDRINUSE
#endif #endif
using namespace std::chrono_literals;
// ----------------------------- SocketSink ------------------------- // ----------------------------- SocketSink -------------------------
SocketSink::~SocketSink() SocketSink::~SocketSink()
@@ -97,20 +96,7 @@ RtpPair<PDatagramSocket> SocketHeap::allocSocketPair(int family, SocketSink *sin
rtcp = allocSocket(family, sink, rtp->localport() + 1); rtcp = allocSocket(family, sink, rtp->localport() + 1);
} }
catch(...) catch(...)
{ {}
// Release a partially allocated pair before retrying - otherwise
// the RTP socket from this attempt leaks into the socket map.
if (rtp)
{
freeSocket(rtp);
rtp.reset();
}
if (rtcp)
{
freeSocket(rtcp);
rtcp.reset();
}
}
} }
if (!rtp || !rtcp) if (!rtp || !rtcp)
@@ -141,7 +127,7 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
if (sock == INVALID_SOCKET) if (sock == INVALID_SOCKET)
{ {
// Return null socket // Return null socket
auto result = std::make_shared<DatagramSocket>(); PDatagramSocket result(new DatagramSocket());
result->mLocalPort = port; result->mLocalPort = port;
result->mFamily = family; result->mFamily = family;
return result; return result;
@@ -152,9 +138,6 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
sockaddr_in6 addr6; sockaddr_in6 addr6;
int result = 0; int result = 0;
int testport; int testport;
// A fixed port cannot be retried (it would loop forever if the port is
// owned by another process); random ports get a bounded number of attempts.
int attemptsLeft = port ? 1 : 100;
do do
{ {
testport = port ? port : rand() % ((mFinish - mStart) / 2) * 2 + mStart; testport = port ? port : rand() % ((mFinish - mStart) / 2) * 2 + mStart;
@@ -180,14 +163,14 @@ PDatagramSocket SocketHeap::allocSocket(int family, SocketSink* sink, int port)
break; break;
} }
} while (result == WSAEADDRINUSE && --attemptsLeft > 0); } while (result == WSAEADDRINUSE);
if (result) if (result)
{ {
closesocket(sock); closesocket(sock);
throw Exception(ERR_NET_FAILED, WSAGetLastError()); throw Exception(ERR_NET_FAILED, WSAGetLastError());
} }
auto resultObject = std::make_shared<DatagramSocket>(); PDatagramSocket resultObject(new DatagramSocket());
resultObject->mLocalPort = testport; resultObject->mLocalPort = testport;
resultObject->mHandle = sock; resultObject->mHandle = sock;
if (!resultObject->setBlocking(false)) if (!resultObject->setBlocking(false))
@@ -270,7 +253,7 @@ void SocketHeap::thread()
// If set is not empty // If set is not empty
if (agreggator.count() > 0) if (agreggator.count() > 0)
{ {
if (agreggator.waitForData(10ms)) if (agreggator.waitForData(10))
{ {
ICELogMedia(<< "There is data on UDP sockets"); ICELogMedia(<< "There is data on UDP sockets");
Lock l(mGuard); Lock l(mGuard);
+1 -1
View File
@@ -6,7 +6,7 @@
#ifndef __SOCKET_HEAP_H #ifndef __SOCKET_HEAP_H
#define __SOCKET_HEAP_H #define __SOCKET_HEAP_H
#include "../engine_config.h" #include "../config.h"
#include <map> #include <map>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
+7 -7
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2023 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2016 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -10,13 +10,13 @@
enum class StreamState enum class StreamState
{ {
Sending = 1, // Transmitting RTP. Set this flag to allow outgoing media stream. Sending = 1, // Transmitting RTP. Set this flag to allow outgoing media stream.
Receiving = 2, // Receiving RTP. Set this flag to allow receiving media stream. Receiving = 2, // Receiving RTP. Set this flag to allow receiving media stream.
Playing = 4, // Play to audio. Unmutes the audio from specified stream. Playing = 4, // Play to audio. Unmutes the audio from specified stream.
Grabbing = 8, // Capture audio. Unmutes the audio to specified stream. Grabbing = 8, // Capture audio. Unmutes the audio to specified stream.
Srtp = 16, // Use SRTP. Make attempt Srtp = 16, // Use SRTP. Make attempt
SipSend = 32, // Declare send capability in SDP SipSend = 32, // Declare send capability in SDP
SipRecv = 64 // Declare recv capability in SDP SipRecv = 64 // Declare recv capability in SDP
}; };
+142 -111
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2023 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -8,7 +8,6 @@
#include <iomanip> #include <iomanip>
#include <memory.h> #include <memory.h>
#include <algorithm> #include <algorithm>
#include <inttypes.h>
#ifdef TARGET_WIN #ifdef TARGET_WIN
# include <WinSock2.h> # include <WinSock2.h>
@@ -16,7 +15,7 @@
# include <cctype> # include <cctype>
#endif #endif
std::string strx::extractFilename(const std::string& path) std::string StringHelper::extractFilename(const std::string& path)
{ {
if (path.empty()) if (path.empty())
return std::string(); return std::string();
@@ -31,7 +30,7 @@ std::string strx::extractFilename(const std::string& path)
return path.substr(p_bs + 1); return path.substr(p_bs + 1);
} }
std::string strx::appendPath(const std::string& s1, const std::string& s2) std::string StringHelper::appendPath(const std::string& s1, const std::string& s2)
{ {
std::string result = s1; std::string result = s1;
if (!endsWith(result, "/") && !endsWith(result, "\\")) if (!endsWith(result, "/") && !endsWith(result, "\\"))
@@ -45,42 +44,36 @@ std::string strx::appendPath(const std::string& s1, const std::string& s2)
return result + s2; return result + s2;
} }
std::string strx::makeUtf8(const std::tstring &arg) std::string StringHelper::makeUtf8(const std::tstring &arg)
{ {
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
int required = WideCharToMultiByte(CP_UTF8, 0, arg.c_str(), -1, NULL, 0, NULL, NULL); size_t required = WideCharToMultiByte(CP_UTF8, 0, arg.c_str(), -1, NULL, 0, NULL, NULL);
if (required <= 0) char *result = (char*)_alloca(required + 1);
return std::string(); WideCharToMultiByte(CP_UTF8, 0, arg.c_str(), -1, result, required+1, NULL, NULL);
std::string result(static_cast<size_t>(required), '\0');
WideCharToMultiByte(CP_UTF8, 0, arg.c_str(), -1, &result[0], required, NULL, NULL);
result.resize(strlen(result.c_str())); // strip the trailing NUL written by the API
return result; return result;
#else #else
return arg; return arg;
#endif #endif
} }
std::string strx::toUtf8(const std::tstring &arg) std::string StringHelper::toUtf8(const std::tstring &arg)
{ {
return makeUtf8(arg); return makeUtf8(arg);
} }
std::tstring strx::makeTstring(const std::string& arg) std::tstring StringHelper::makeTstring(const std::string& arg)
{ {
#if defined(TARGET_WIN) #if defined(TARGET_WIN)
int count = MultiByteToWideChar(CP_UTF8, 0, arg.c_str(), -1, NULL, 0); size_t count = MultiByteToWideChar(CP_UTF8, 0, arg.c_str(), -1, NULL, 0);
if (count <= 0) wchar_t* result = (wchar_t*)_alloca(count * 2);
return std::tstring(); MultiByteToWideChar(CP_UTF8, 0, arg.c_str(), -1, result, count);
std::wstring result(static_cast<size_t>(count), L'\0');
MultiByteToWideChar(CP_UTF8, 0, arg.c_str(), -1, &result[0], count);
result.resize(wcslen(result.c_str())); // strip the trailing NUL written by the API
return result; return result;
#else #else
return arg; return arg;
#endif #endif
} }
int strx::toInt(const char *s, int defaultValue, bool* isOk) int StringHelper::toInt(const char *s, int defaultValue, bool* isOk)
{ {
int result; int result;
if (sscanf(s, "%d", &result) != 1) if (sscanf(s, "%d", &result) != 1)
@@ -96,10 +89,14 @@ int strx::toInt(const char *s, int defaultValue, bool* isOk)
return result; return result;
} }
uint64_t strx::toUint64(const char* s, uint64_t def, bool *isOk) uint64_t StringHelper::toUint64(const char* s, uint64_t def, bool *isOk)
{ {
uint64_t result = def; uint64_t result = def;
if (sscanf(s, "%" SCNu64, &result) != 1) #if defined(TARGET_WIN)
if (sscanf(s, "%I64d", &result) != 1)
#else
if (sscanf(s, "%llu", &result) != 1)
#endif
{ {
if (isOk) if (isOk)
*isOk = false; *isOk = false;
@@ -112,14 +109,14 @@ uint64_t strx::toUint64(const char* s, uint64_t def, bool *isOk)
return result; return result;
} }
std::string strx::toHex(unsigned int value) std::string StringHelper::toHex(unsigned int value)
{ {
char buffer[32]; char buffer[32];
std::snprintf(buffer, sizeof(buffer), "%x", value); sprintf(buffer, "%x", value);
return buffer; return buffer;
} }
std::string strx::toHex(const void *ptr) std::string StringHelper::toHex(const void *ptr)
{ {
std::ostringstream oss; std::ostringstream oss;
oss << std::fixed << std::setw(8) << std::setfill('0') << std::hex << ptr; oss << std::fixed << std::setw(8) << std::setfill('0') << std::hex << ptr;
@@ -129,7 +126,7 @@ std::string strx::toHex(const void *ptr)
//must be lowercase for MD5 //must be lowercase for MD5
static const char hexmap[] = "0123456789abcdef"; static const char hexmap[] = "0123456789abcdef";
std::string strx::toHex(const uint8_t* input, size_t inputLength) std::string StringHelper::toHex(const uint8_t* input, size_t inputLength)
{ {
std::string result; result.resize(inputLength * 2); std::string result; result.resize(inputLength * 2);
@@ -145,11 +142,12 @@ std::string strx::toHex(const uint8_t* input, size_t inputLength)
*r++ = hexmap[hi]; *r++ = hexmap[hi];
*r++ = hexmap[low]; *r++ = hexmap[low];
} }
*r = 0;
return result; return result;
} }
std::string strx::prefixLines(const std::string &source, const std::string &prefix) std::string StringHelper::prefixLines(const std::string &source, const std::string &prefix)
{ {
// Read source line by line // Read source line by line
std::istringstream iss(source); std::istringstream iss(source);
@@ -162,7 +160,7 @@ std::string strx::prefixLines(const std::string &source, const std::string &pref
return oss.str(); return oss.str();
} }
std::string strx::doubleToString(double value, int precision) std::string StringHelper::doubleToString(double value, int precision)
{ {
std::stringstream ss; std::stringstream ss;
ss << std::fixed << std::setprecision(precision) << value; ss << std::fixed << std::setprecision(precision) << value;
@@ -170,15 +168,17 @@ std::string strx::doubleToString(double value, int precision)
} }
const char* strx::findSubstring(const char* buffer, const char* substring, size_t bufferLength) const char* StringHelper::findSubstring(const char* buffer, const char* substring, size_t bufferLength)
{ {
// The buffer is not necessarily NUL-terminated, so a bounded search is #if defined(TARGET_WIN)
// required on every platform (a memmem replacement for MSVC is provided below). return (const char*)strstr(buffer, substring);
#else
return (const char*)memmem(buffer, bufferLength, substring, strlen(substring)); return (const char*)memmem(buffer, bufferLength, substring, strlen(substring));
#endif
} }
void strx::split(const std::string& src, std::vector<std::string>& dst, const std::string& delims) void StringHelper::split(const std::string& src, std::vector<std::string>& dst, const std::string& delims)
{ {
dst.clear(); dst.clear();
std::string::size_type p = 0; std::string::size_type p = 0;
@@ -202,21 +202,21 @@ void strx::split(const std::string& src, std::vector<std::string>& dst, const st
} }
} }
std::vector<std::string> strx::split(const std::string& src, const std::string& delims) std::vector<std::string> StringHelper::split(const std::string& src, const std::string& delims)
{ {
std::vector<std::string> r; std::vector<std::string> r;
split(src, r, delims); split(src, r, delims);
return r; return r;
} }
std::pair<std::string, int> strx::parseHost(const std::string& host, int defaultPort) std::pair<std::string, int> StringHelper::parseHost(const std::string& host, int defaultPort)
{ {
std::pair<std::string, int> result; std::pair<std::string, int> result;
std::size_t p = host.find(':'); std::size_t p = host.find(':');
if (p != std::string::npos) if (p != std::string::npos)
{ {
result.first = host.substr(0, p); result.first = host.substr(0, p);
result.second = strx::toInt(host.c_str() + p + 1, defaultPort); result.second = StringHelper::toInt(host.c_str() + p + 1, defaultPort);
} }
else else
{ {
@@ -226,15 +226,15 @@ std::pair<std::string, int> strx::parseHost(const std::string& host, int default
return result; return result;
} }
std::pair<std::string, std::string> strx::parseAssignment(const std::string& s, bool trimQuotes) std::pair<std::string, std::string> StringHelper::parseAssignment(const std::string& s, bool trimQuotes)
{ {
std::pair<std::string, std::string> result; std::pair<std::string, std::string> result;
std::string::size_type p = s.find('='); std::string::size_type p = s.find('=');
if (p != std::string::npos) if (p != std::string::npos)
{ {
result.first = strx::trim(s.substr(0, p)); result.first = StringHelper::trim(s.substr(0, p));
result.second = strx::trim(s.substr(p+1)); result.second = StringHelper::trim(s.substr(p+1));
if (trimQuotes && result.second.size() >= 2) if (trimQuotes && result.second.size() >= 2)
{ {
if ((result.second[0] == '"' && result.second[result.second.size()-1] == '"') || if ((result.second[0] == '"' && result.second[result.second.size()-1] == '"') ||
@@ -243,19 +243,19 @@ std::pair<std::string, std::string> strx::parseAssignment(const std::string& s,
} }
} }
else else
result.first = strx::trim(s); result.first = StringHelper::trim(s);
return result; return result;
} }
std::string strx::intToString(int value) std::string StringHelper::intToString(int value)
{ {
char buffer[32]; char buffer[32];
std::snprintf(buffer, sizeof(buffer), "%d", value); sprintf(buffer, "%d", value);
return buffer; return buffer;
} }
float strx::toFloat(const std::string &s, float v, bool* isOk) float StringHelper::toFloat(const std::string &s, float v, bool* isOk)
{ {
float result = 0.0; float result = 0.0;
int code = sscanf(s.c_str(), "%f", &result); int code = sscanf(s.c_str(), "%f", &result);
@@ -274,14 +274,14 @@ float strx::toFloat(const std::string &s, float v, bool* isOk)
return result; return result;
} }
std::string strx::trim(const std::string &s) std::string StringHelper::trim(const std::string &s)
{ {
auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { return std::isspace(c) || c == '\r' || c == '\n'; }); auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { return std::isspace(c) || c == '\r' || c == '\n'; });
auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c) { return std::isspace(c) || c == '\r' || c == '\n'; }).base(); auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c) { return std::isspace(c) || c == '\r' || c == '\n'; }).base();
return (wsback <= wsfront ? std::string() : std::string(wsfront,wsback)); return (wsback <= wsfront ? std::string() : std::string(wsfront,wsback));
} }
std::string strx::timeToString(time_t t) std::string StringHelper::timeToString(time_t t)
{ {
char buffer[128] = ""; char buffer[128] = "";
struct tm lt; struct tm lt;
@@ -295,18 +295,15 @@ std::string strx::timeToString(time_t t)
return buffer; return buffer;
} }
std::string strx::millisecondsToString(uint64_t t) std::string StringHelper::millisecondsToString(uint64_t t)
{ {
return timeToString(t/1000); return timeToString(t/1000);
} }
int strx::fromHex2Int(const std::string &s) int StringHelper::fromHex2Int(const std::string &s)
{ {
int result = 0; int result = 0;
int retcode = sscanf(s.c_str(), "%x", &result); sscanf(s.c_str(), "%x", &result);
if (retcode == 0)
return 0;
return result; return result;
} }
@@ -321,32 +318,32 @@ static int hex2code(char s)
return 0; return 0;
} }
/*static int hex2code(const char* s) static int hex2code(const char* s)
{ {
return (hex2code(s[0]) << 4) + hex2code(s[1]); return (hex2code(s[0]) << 4) + hex2code(s[1]);
}*/ }
std::string strx::fromHex2String(const std::string& s) std::string StringHelper::fromHex2String(const std::string& s)
{ {
std::string result; result.resize(s.size() / 2); std::string result; result.resize(s.size() / 2);
const char* t = s.c_str(); const char* t = s.c_str();
for (size_t i = 0; i < result.size(); i++) for (size_t i = 0; i < result.size(); i++)
result[i] = static_cast<char>((hex2code(t[i*2]) << 4) | hex2code(t[i*2+1])); result[i] = hex2code(t[i*2]);
return result; return result;
} }
std::string strx::replace(const std::string& s, char f, char r) std::string StringHelper::replace(const std::string& s, char f, char r)
{ {
std::string result(s); std::string result(s);
for (std::string::size_type i = 0; i < result.size(); i++) for (std::string::size_type i = 0; i < result.size(); i++)
if (result[i] == f) if (result[i] == 'f')
result[i] = r; result[i] = r;
return result; return result;
} }
std::string strx::replace(const std::string& s, const std::string& tmpl, const std::string& n) std::string StringHelper::replace(const std::string& s, const std::string& tmpl, const std::string& n)
{ {
std::string result(s); std::string result(s);
std::string::size_type p = 0; std::string::size_type p = 0;
@@ -359,26 +356,22 @@ std::string strx::replace(const std::string& s, const std::string& tmpl, const s
return result; return result;
} }
std::string strx::decodeUri(const std::string& s) std::string StringHelper::decodeUri(const std::string& s)
{ {
std::string ret; std::string ret;
ret.reserve(s.size()); ret.reserve(s.size());
char ch; char ch;
int i, ii = 0; int i, ii;
for (i=0; i<(int)s.length(); i++) for (i=0; i<(int)s.length(); i++)
{ {
if (s[i] == '%' && i + 2 < (int)s.length()) if (s[i] == 37)
{ {
if (sscanf(s.substr(i+1,2).c_str(), "%x", &ii) == 1) sscanf(s.substr(i+1,2).c_str(), "%x", &ii);
{ ch = static_cast<char>(ii);
ch = static_cast<char>(ii); ret += ch;
ret += ch; i += 2;
i += 2;
}
else
ret += s[i];
} }
else else
ret += s[i]; ret += s[i];
@@ -386,21 +379,19 @@ std::string strx::decodeUri(const std::string& s)
return ret; return ret;
} }
bool strx::startsWith(const std::string& s, const std::string& prefix) bool StringHelper::startsWith(const std::string& s, const std::string& prefix)
{ {
if (prefix.size() > s.size()) std::string::size_type p = s.find(prefix);
return false; return p == 0;
return s.compare(0, prefix.size(), prefix) == 0;
} }
bool strx::endsWith(const std::string& s, const std::string& suffix) bool StringHelper::endsWith(const std::string& s, const std::string& suffix)
{ {
if (suffix.size() > s.size()) std::string::size_type p = s.rfind(suffix);
return false; return (p == s.size() - suffix.size());
return s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
} }
int strx::stringToDuration(const std::string& s) int StringHelper::stringToDuration(const std::string& s)
{ {
if (endsWith(s, "ms")) if (endsWith(s, "ms"))
return std::stoi(s.substr(0, s.size()-2)); return std::stoi(s.substr(0, s.size()-2));
@@ -414,52 +405,92 @@ int strx::stringToDuration(const std::string& s)
return std::stoi(s) * 1000; return std::stoi(s) * 1000;
} }
std::string strx::uppercase(const std::string& s) #define XML_HEADER "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
// --------------------- XcapHelper -----------------
std::string XcapHelper::buildBuddyList(std::string listName, std::vector<std::string> buddies)
{ {
std::string r(s); std::ostringstream result;
std::transform(r.begin(), r.end(), r.begin(), ::toupper); result << XML_HEADER <<
return r; "<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\">" <<
"<list name=\"" << listName.c_str() << "\">";
// to test CT only!
//result << "<entry uri=\"" << "sip:dbogovych1@10.11.1.25" << "\"/>";
//result << "<entry uri=\"" << "sip:dbogovych2@10.11.1.25" << "\"/>";
for (unsigned i = 0; i<buddies.size(); i++)
{
result << "<entry uri=\"" << normalizeSipUri(buddies[i]).c_str() << "\"/>";
}
result << "</list></resource-lists>";
return result.str();
} }
std::string strx::lowercase(const std::string& s) std::string XcapHelper::buildRules(std::vector<std::string> buddies)
{ {
std::string r(s); std::ostringstream result;
std::transform(r.begin(), r.end(), r.begin(), ::tolower); result << XML_HEADER <<
return r; "<ruleset xmlns=\"urn:ietf:params:xml:ns:common-policy\">" <<
"<rule id=\"presence_allow\">" <<
"<conditions>";
for (unsigned i = 0; i<buddies.size(); i++)
{
result << "<identity><one id=\"" <<
normalizeSipUri(buddies[i]).c_str() << "\"/></identity>";
}
result << "</conditions>" <<
"<actions>" <<
"<sub-handling xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"allow" <<
"</sub-handling>" <<
"</actions>" <<
"<transformations>" <<
"<provide-devices xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"<all-devices/>" <<
"</provide-devices>" <<
"<provide-persons xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"<all-persons/>" <<
"</provide-persons>" <<
"<provide-services xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"<all-services/>" <<
"</provide-services>" <<
"</transformations>" <<
"</rule>" <<
"</ruleset>";
return result.str();
} }
std::string strx::removeQuotes(const std::string& s) std::string XcapHelper::buildServices(std::string serviceUri, std::string listRef)
{ {
std::string r(s); std::ostringstream result;
if (s.empty())
return s;
if (r.front() == '"') result << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl <<
r = r.substr(1); "<rls-services xmlns=\"urn:ietf:params:xml:ns:rls-services\"" << std::endl <<
"xmlns:rl=\"urn:ietf:params:xml:ns:resource-lists\"" << std::endl <<
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" << std::endl <<
"<service uri=\"" << normalizeSipUri(serviceUri).c_str() << "\">" << std::endl <<
"<resource-list>" << listRef.c_str() << "</resource-list>" << std::endl <<
"<packages>" << std::endl <<
"<package>presence</package>" << std::endl <<
"</packages>" << std::endl <<
"</service>" << std::endl <<
"</rls-services>";
if (r.back() == '"') return result.str();
r = r.substr(0, r.size()-1);
return r;
} }
#if defined(TARGET_WIN) std::string XcapHelper::normalizeSipUri(std::string uri)
// MSVC++ lacks memmem support
const void *memmem(const void *haystack, size_t haystack_len,
const void * const needle, const size_t needle_len)
{ {
if (!haystack || !haystack_len || !needle || !needle_len) if (uri.length())
return nullptr; {
if (uri[0] == '<')
for (const char *h = (const char*)haystack; uri.erase(0,1);
haystack_len >= needle_len; if (uri.length())
++h, --haystack_len) { {
if (!memcmp(h, needle, needle_len)) { if (uri[uri.length()-1] == '>')
return h; uri.erase(uri.length()-1, 1);
} }
} }
return nullptr; return uri;
} }
#endif
+6 -21
View File
@@ -9,7 +9,6 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <cstdint>
#include "HL_Types.h" #include "HL_Types.h"
#ifdef TARGET_OSX #ifdef TARGET_OSX
@@ -17,7 +16,7 @@
#endif #endif
class strx class StringHelper
{ {
public: public:
static std::string extractFilename(const std::string& path); static std::string extractFilename(const std::string& path);
@@ -26,7 +25,6 @@ public:
static std::string makeUtf8(const std::tstring& arg); static std::string makeUtf8(const std::tstring& arg);
static std::string toUtf8(const std::tstring& arg); static std::string toUtf8(const std::tstring& arg);
static std::tstring makeTstring(const std::string& arg); static std::tstring makeTstring(const std::string& arg);
static int toInt(const char* s, int defaultValue, bool* isOk = nullptr); static int toInt(const char* s, int defaultValue, bool* isOk = nullptr);
static uint64_t toUint64(const char* s, uint64_t def, bool *isOk = nullptr); static uint64_t toUint64(const char* s, uint64_t def, bool *isOk = nullptr);
static std::string toHex(unsigned int value); static std::string toHex(unsigned int value);
@@ -35,14 +33,9 @@ public:
static std::string intToString(int value); static std::string intToString(int value);
static std::string prefixLines(const std::string& source, const std::string& prefix); static std::string prefixLines(const std::string& source, const std::string& prefix);
static std::string doubleToString(double value, int precision); static std::string doubleToString(double value, int precision);
static int fromHex2Int(const std::string& s);
static std::string fromHex2String(const std::string& s);
static float toFloat(const std::string& s, float defaultValue = 0.0, bool* isOk = nullptr);
static const char* findSubstring(const char* buffer, const char* substring, size_t bufferLength); static const char* findSubstring(const char* buffer, const char* substring, size_t bufferLength);
static void split(const std::string& src, std::vector<std::string>& dst, const std::string& delims);
static void split(const std::string& src, std::vector<std::string>& dst, const std::string& delims);
static std::vector<std::string> split(const std::string& src, const std::string& delims = "\n"); static std::vector<std::string> split(const std::string& src, const std::string& delims = "\n");
template <typename T> template <typename T>
@@ -51,7 +44,7 @@ public:
std::ostringstream s; std::ostringstream s;
for (const auto& i : v) for (const auto& i : v)
{ {
if (&i != &v.front()) if (&i != &v[0])
s << delimiter; s << delimiter;
s << i; s << i;
} }
@@ -60,19 +53,18 @@ public:
static std::pair<std::string, int> parseHost(const std::string& host, int defaultPort); static std::pair<std::string, int> parseHost(const std::string& host, int defaultPort);
static std::pair<std::string, std::string> parseAssignment(const std::string& s, bool trimQuotes = true); static std::pair<std::string, std::string> parseAssignment(const std::string& s, bool trimQuotes = true);
static float toFloat(const std::string& s, float defaultValue = 0.0, bool* isOk = nullptr);
static std::string trim(const std::string& s); static std::string trim(const std::string& s);
static std::string timeToString(time_t t); static std::string timeToString(time_t t);
static std::string millisecondsToString(uint64_t t); static std::string millisecondsToString(uint64_t t);
static int fromHex2Int(const std::string& s);
static std::string fromHex2String(const std::string& s);
static std::string replace(const std::string& s, char f, char r); static std::string replace(const std::string& s, char f, char r);
static std::string replace(const std::string& s, const std::string& tmpl, const std::string& n); static std::string replace(const std::string& s, const std::string& tmpl, const std::string& n);
static std::string decodeUri(const std::string& s); static std::string decodeUri(const std::string& s);
static bool startsWith(const std::string& s, const std::string& prefix); static bool startsWith(const std::string& s, const std::string& prefix);
static bool endsWith(const std::string& s, const std::string& suffix); static bool endsWith(const std::string& s, const std::string& suffix);
static int stringToDuration(const std::string& s); static int stringToDuration(const std::string& s);
static std::string uppercase(const std::string& s);
static std::string lowercase(const std::string& s);
static std::string removeQuotes(const std::string& s);
}; };
class XcapHelper class XcapHelper
@@ -85,11 +77,4 @@ public:
}; };
#if defined(TARGET_WIN)
// MSVC++ lacks memmem support
extern const void *memmem(const void *haystack, size_t haystack_len,
const void * const needle, const size_t needle_len);
#endif
#endif #endif
+44 -60
View File
@@ -20,8 +20,7 @@
void SyncHelper::delay(unsigned int microseconds) void SyncHelper::delay(unsigned int microseconds)
{ {
#ifdef TARGET_WIN #ifdef TARGET_WIN
// Round up so sub-millisecond delays do not become Sleep(0) ::Sleep(microseconds/1000);
::Sleep((microseconds + 999) / 1000);
#endif #endif
#if defined(TARGET_OSX) || defined(TARGET_LINUX) #if defined(TARGET_OSX) || defined(TARGET_LINUX)
timespec requested, remaining; timespec requested, remaining;
@@ -33,12 +32,24 @@ void SyncHelper::delay(unsigned int microseconds)
#endif #endif
} }
long SyncHelper::increment(long *value)
{
assert(value);
#ifdef TARGET_WIN
return ::InterlockedIncrement((LONG*)value);
#elif TARGET_OSX
return OSAtomicIncrement32((int32_t*)value);
#elif TARGET_LINUX
return -1;
#else
return -1;
#endif
}
// ------------------- ThreadHelper ------------------- // ------------------- ThreadHelper -------------------
void ThreadHelper::setName(const std::string &name) void ThreadHelper::setName(const std::string &name)
{ {
#if defined(TARGET_LINUX) #if defined(TARGET_LINUX)
// The name will be truncated to 8 or 16 characters
int retcode = pthread_setname_np(pthread_self(), name.c_str()); int retcode = pthread_setname_np(pthread_self(), name.c_str());
if (retcode != 0) if (retcode != 0)
{ {
@@ -66,77 +77,46 @@ uint64_t ThreadHelper::getCurrentId()
// ------------------- TimeHelper --------------- // ------------------- TimeHelper ---------------
using namespace std::chrono; using namespace std::chrono;
// Milliseconds starting from the epoch
static uint64_t TimestampStartPoint = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count(); static uint64_t TimestampStartPoint = duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
// Seconds starting from the epoch
static time_t TimestampBase = time(nullptr); static time_t TimestampBase = time(nullptr);
// Returns number of milliseconds starting from 01 Jan 1970 GMT uint64_t TimeHelper::getTimestamp()
std::chrono::milliseconds chronox::getTimestamp()
{
time_point<steady_clock> t = steady_clock::now();
uint64_t ms = duration_cast< milliseconds >(t.time_since_epoch()).count();
return std::chrono::milliseconds(ms - TimestampStartPoint + TimestampBase * 1000);
}
std::chrono::milliseconds chronox::getUptime()
{ {
time_point<steady_clock> t = steady_clock::now(); time_point<steady_clock> t = steady_clock::now();
uint64_t ms = duration_cast< milliseconds >(t.time_since_epoch()).count(); uint64_t ms = duration_cast< milliseconds >(t.time_since_epoch()).count();
return std::chrono::milliseconds(ms - TimestampStartPoint); return ms - TimestampStartPoint + TimestampBase * 1000;
} }
uint32_t chronox::getDelta(uint32_t later, uint32_t earlier) uint64_t TimeHelper::getUptime()
{
time_point<steady_clock> t = steady_clock::now();
uint64_t ms = duration_cast< milliseconds >(t.time_since_epoch()).count();
return ms - TimestampStartPoint;
}
uint32_t TimeHelper::getDelta(uint32_t later, uint32_t earlier)
{ {
if (later > earlier) if (later > earlier)
return later - earlier; return later - earlier;
// Counter wrapped: unsigned subtraction yields the correct modulo-2^32 delta
if (later < earlier && later < 0x7FFFFFFF && earlier >= 0x7FFFFFFF) if (later < earlier && later < 0x7FFFFFFF && earlier >= 0x7FFFFFFF)
return later - earlier; return 0xFFFFFFFF - earlier + later;
return 0; return 0;
} }
timespec chronox::toTimespec(uint64_t milliseconds) TimeHelper::ExecutionTime::ExecutionTime()
{ {
timespec r; mStart = TimeHelper::getTimestamp();
r.tv_sec = milliseconds / 1000;
r.tv_nsec = milliseconds % 1000;
r.tv_nsec *= 1000 * 1000;
return r;
} }
uint64_t chronox::toTimestamp(const timeval& ts) uint64_t TimeHelper::ExecutionTime::getSpentTime() const
{ {
return (uint64_t)ts.tv_sec * 1000 + ts.tv_usec / 1000; return TimeHelper::getTimestamp() - mStart;
}
int64_t chronox::getDelta(const timespec& a, const timespec& b)
{
int64_t ms_a = (int64_t)a.tv_sec * 1000 + a.tv_nsec / 1000000;
int64_t ms_b = (int64_t)b.tv_sec * 1000 + b.tv_nsec / 1000000;
return ms_a - ms_b;
}
int64_t chronox::getDelta(const timeval& a, const timeval& b)
{
int64_t diff_seconds = a.tv_sec - b.tv_sec;
int64_t diff_microseconds = a.tv_usec - b.tv_usec;
return diff_seconds * 1000 + diff_microseconds / 1000;
}
chronox::ExecutionTime::ExecutionTime()
{
mStart = chronox::getTimestamp();
}
std::chrono::milliseconds chronox::ExecutionTime::getSpentTime() const
{
return chronox::getTimestamp() - mStart;
} }
// --------------- BufferQueue ----------------- // --------------- BufferQueue -----------------
@@ -164,11 +144,13 @@ void BufferQueue::push(const void* data, int bytes)
BufferQueue::PBlock BufferQueue::pull(int milliseconds) BufferQueue::PBlock BufferQueue::pull(int milliseconds)
{ {
std::unique_lock<std::mutex> l(mMutex); std::unique_lock<std::mutex> l(mMutex);
mSignal.wait_for(l, std::chrono::milliseconds(milliseconds), std::cv_status status = mBlockList.empty() ? std::cv_status::timeout : std::cv_status::no_timeout;
[this]() { return !mBlockList.empty(); });
if (mBlockList.empty())
status = mSignal.wait_for(l, std::chrono::milliseconds(milliseconds));
PBlock r; PBlock r;
if (!mBlockList.empty()) if (status == std::cv_status::no_timeout && !mBlockList.empty())
{ {
r = mBlockList.front(); r = mBlockList.front();
mBlockList.pop_front(); mBlockList.pop_front();
@@ -196,10 +178,10 @@ void Semaphore::wait()
m_count--; m_count--;
} }
bool Semaphore::waitFor(std::chrono::milliseconds timeout) { bool Semaphore::waitFor(int milliseconds) {
std::unique_lock<std::mutex> lock(m_mtx); std::unique_lock<std::mutex> lock(m_mtx);
if (!m_cv.wait_for(lock, timeout, [this]() { return m_count > 0; })) if (!m_cv.wait_for(lock, std::chrono::milliseconds(milliseconds), [this]() { return m_count > 0; }))
return false; return false;
m_count--; m_count--;
@@ -280,8 +262,7 @@ size_t TimerQueue::cancel(uint64_t id) {
//! Cancels all timers //! Cancels all timers
// \return // \return
// The number of timers cancelled // The number of timers cancelled
size_t TimerQueue::cancelAll() size_t TimerQueue::cancelAll() {
{
// Setting all "end" to 0 (for immediate execution) is ok, // Setting all "end" to 0 (for immediate execution) is ok,
// since it maintains the heap integrity // since it maintains the heap integrity
std::unique_lock<std::mutex> lk(m_mtx); std::unique_lock<std::mutex> lk(m_mtx);
@@ -306,8 +287,11 @@ void TimerQueue::run()
auto end = calcWaitTime(); auto end = calcWaitTime();
if (end.first) if (end.first)
{ {
// Timers found, so wait until it expires (or something else changes) // Timers found, so wait until it expires (or something else
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(end.second - std::chrono::steady_clock::now()); // changes)
int milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>
(end.second - std::chrono::steady_clock::now()).count();
//std::cout << "Waiting m_checkWork for " << milliseconds * 1000 << "ms." << std::endl;
m_checkWork.waitFor(milliseconds); m_checkWork.waitFor(milliseconds);
} else { } else {
// No timers exist, so wait forever until something changes // No timers exist, so wait forever until something changes
+23 -35
View File
@@ -14,18 +14,14 @@
#include <functional> #include <functional>
#include <assert.h> #include <assert.h>
#if defined(TARGET_WIN)
# include <WinSock2.h>
# include <Windows.h>
#endif
typedef std::recursive_mutex Mutex; typedef std::recursive_mutex Mutex;
typedef std::unique_lock<std::recursive_mutex> Lock; typedef std::unique_lock<std::recursive_mutex> Lock;
class SyncHelper class SyncHelper
{ {
public: public:
static void delay(unsigned microseconds); static void delay(unsigned microseconds);
static long increment(long* value);
}; };
class Semaphore class Semaphore
@@ -35,7 +31,7 @@ public:
void notify(); void notify();
void wait(); void wait();
bool waitFor(std::chrono::milliseconds timeout); bool waitFor(int milliseconds);
private: private:
std::mutex m_mtx; std::mutex m_mtx;
@@ -46,42 +42,34 @@ private:
class ThreadHelper class ThreadHelper
{ {
public: public:
static void setName(const std::string& name); static void setName(const std::string& name);
static uint64_t getCurrentId(); static uint64_t getCurrentId();
}; };
class chronox class TimeHelper
{ {
public: public:
// Returns current timestamp in milliseconds // Returns current timestamp in milliseconds
static std::chrono::milliseconds getTimestamp(); static uint64_t getTimestamp();
// Returns uptime (of calling process) in milliseconds // Returns uptime (of calling process) in milliseconds
static std::chrono::milliseconds getUptime(); static uint64_t getUptime();
// Finds time delta between 'later' and 'earlier' time points. // Finds time delta between 'later' and 'earlier' time points.
// Handles cases when clock is wrapped. // Handles cases when clock is wrapped.
static uint32_t getDelta(uint32_t later, uint32_t earlier); static uint32_t getDelta(uint32_t later, uint32_t earlier);
// Converts number of milliseconds starting from Epoch begin to timespec. class ExecutionTime
static timespec toTimespec(uint64_t milliseconds); {
static uint64_t toTimestamp(const timeval& ts); public:
ExecutionTime();
// Returns difference between timestamps in milliseconds uint64_t getSpentTime() const;
static int64_t getDelta(const timespec& a, const timespec& b); protected:
static int64_t getDelta(const timeval& a, const timeval& b); uint64_t mStart;
};
class ExecutionTime
{
public:
ExecutionTime();
std::chrono::milliseconds getSpentTime() const;
protected:
std::chrono::milliseconds mStart;
};
}; };
class BufferQueue class BufferQueue
{ {
public: public:
@@ -159,7 +147,7 @@ private:
std::mutex m_mtx; std::mutex m_mtx;
// Inheriting from priority_queue, so we can access the internal container // Inheriting from priority_queue, so we can access the internal container
class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>, class Queue : public std::priority_queue<WorkItem, std::vector<WorkItem>,
std::greater<WorkItem>> std::greater<WorkItem>>
{ {
public: public:
std::vector<WorkItem>& getContainer(); std::vector<WorkItem>& getContainer();
+5 -25
View File
@@ -7,7 +7,7 @@ thread_pool::thread_pool(size_t num_of_threads, const std::string& name)
num_of_threads = std::thread::hardware_concurrency(); num_of_threads = std::thread::hardware_concurrency();
for(size_t idx = 0; idx < num_of_threads; idx++) for(size_t idx = 0; idx < num_of_threads; idx++)
this->workers.emplace_back(std::thread(&thread_pool::run_worker, this)); this->workers.push_back(std::thread(&thread_pool::run_worker, this));
} }
// Add new work item to the pool // Add new work item to the pool
@@ -20,22 +20,6 @@ void thread_pool::enqueue(const thread_pool::task& t)
this->condition.notify_one(); this->condition.notify_one();
} }
void thread_pool::wait(std::chrono::milliseconds interval)
{
while (size() != 0)
std::this_thread::sleep_for(interval);
}
size_t thread_pool::size()
{
std::unique_lock l(this->queue_mutex);
return this->tasks.size();
}
size_t thread_pool::threads()
{
return this->workers.size();
}
// the destructor joins all threads // the destructor joins all threads
thread_pool::~thread_pool() thread_pool::~thread_pool()
@@ -53,19 +37,15 @@ void thread_pool::run_worker()
{ {
ThreadHelper::setName(this->name); ThreadHelper::setName(this->name);
task t; task t;
while (!this->stop) while(!this->stop)
{ {
{ {
std::unique_lock<std::mutex> lock(this->queue_mutex); std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [this]{return !this->tasks.empty() || this->stop;}); this->condition.wait(lock, [this]{return !this->tasks.empty() || this->stop;});
if (!tasks.empty()) t = this->tasks.front();
{ this->tasks.pop();
t = tasks.front();
tasks.pop();
}
} }
if (t) t(); // function<void()> type
t(); // function<void()> type
} }
} }
+2 -5
View File
@@ -18,13 +18,10 @@ class thread_pool
public: public:
typedef std::function<void()> task; typedef std::function<void()> task;
thread_pool(size_t num_of_threads, const std::string& thread_name); thread_pool(size_t num_of_threads, const std::string&);
~thread_pool(); ~thread_pool();
void enqueue(const task& task); void enqueue(const task& task);
void wait(std::chrono::milliseconds interval = std::chrono::milliseconds(50));
size_t size();
size_t threads();
private: private:
// need to keep track of threads so we can join them // need to keep track of threads so we can join them
@@ -36,7 +33,7 @@ private:
// synchronization // synchronization
std::mutex queue_mutex; std::mutex queue_mutex;
std::condition_variable condition; std::condition_variable condition;
std::atomic_bool stop = false; bool stop = false;
// thread name prefix for worker threads // thread name prefix for worker threads
std::string name; std::string name;
+3 -46
View File
@@ -1,53 +1,10 @@
#include "HL_Time.h" #include "HL_Time.h"
#include <time.h> #include <time.h>
#include <chrono>
/* return current time in milliseconds */ /* return current time in milliseconds */
double now_ms(void) double now_ms(void) {
{
#if defined(TARGET_WIN)
auto tp = std::chrono::steady_clock::now();
auto result = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch()).count();
return result;
#else
struct timespec res; struct timespec res;
clock_gettime(CLOCK_MONOTONIC, &res); clock_gettime(CLOCK_MONOTONIC, &res);
return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6; return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
#endif }
}
int compare_timespec(const timespec& lhs, const timespec& rhs)
{
if (lhs.tv_sec < rhs.tv_sec)
return -1;
if (lhs.tv_sec > rhs.tv_sec)
return 1;
if (lhs.tv_nsec < rhs.tv_nsec)
return -1;
if (lhs.tv_nsec > rhs.tv_nsec)
return 1;
return 0;
}
bool operator < (const timespec& lhs, const timespec& rhs)
{
return compare_timespec(lhs, rhs) < 0;
}
bool operator == (const timespec& lhs, const timespec& rhs)
{
return compare_timespec(lhs, rhs) == 0;
}
bool operator > (const timespec& lhs, const timespec& rhs)
{
return compare_timespec(lhs, rhs) > 0;
}
bool is_zero(const timespec& ts)
{
return !ts.tv_sec && !ts.tv_nsec;
}
-11
View File
@@ -1,17 +1,6 @@
#ifndef __HELPER_TIME_H #ifndef __HELPER_TIME_H
#define __HELPER_TIME_H #define __HELPER_TIME_H
#include <time.h>
// Return current monotonic number of milliseconds starting from some point
extern double now_ms(); extern double now_ms();
// Compare the timespec.
// Returns -1 if lhs < rhs, 1 if lhs > rhs, 0 if equal
extern int compare_timespec(const timespec& lhs, const timespec& rhs);
extern bool operator < (const timespec& lhs, const timespec& rhs);
extern bool operator == (const timespec& lhs, const timespec& rhs);
extern bool operator > (const timespec& lhs, const timespec& rhs);
extern bool is_zero(const timespec& ts);
#endif #endif
-1
View File
@@ -1 +0,0 @@
#include "HL_Types.h"
+1 -136
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2025 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -26,139 +26,4 @@ enum SdpDirection
Sdp_Offer Sdp_Offer
}; };
#include <unordered_map>
#include <utility>
#include <stdexcept>
#include <map>
template<
class K, class V,
class HashK = std::hash<K>, class EqK = std::equal_to<K>,
class HashV = std::hash<V>, class EqV = std::equal_to<V>
>
class BiMap {
public:
using key_type = K;
using mapped_type = V;
BiMap(const std::map<K,V>& initializers) {
for (const auto& item: initializers) {
insert(item.first, item.second);
}
}
// Insert a new (key, value) pair. Returns false if either key or value already exists.
bool insert(const K& k, const V& v) {
if (contains_key(k) || contains_value(v)) return false;
auto ok = forward_.emplace(k, v);
try {
auto ov = reverse_.emplace(v, k);
if (!ov.second) { // shouldn't happen given the guard above
forward_.erase(k);
return false;
}
} catch (...) {
forward_.erase(k);
throw;
}
return ok.second;
}
bool insert(K&& k, V&& v) {
if (contains_key(k) || contains_value(v)) return false;
auto ok = forward_.emplace(std::move(k), std::move(v));
try {
auto ov = reverse_.emplace(ok.first->second, ok.first->first); // use stored refs
if (!ov.second) {
forward_.erase(ok.first);
return false;
}
} catch (...) {
forward_.erase(ok.first);
throw;
}
return ok.second;
}
// Replace value for existing key (and update reverse map). Returns false if value is already bound elsewhere.
bool replace_by_key(const K& k, const V& new_v) {
auto it = forward_.find(k);
if (it == forward_.end()) return false;
if (contains_value(new_v)) return false;
// remove old reverse, insert new reverse, then update forward
reverse_.erase(it->second);
reverse_.emplace(new_v, k);
it->second = new_v;
return true;
}
// Replace key for existing value (and update forward map). Returns false if key is already bound elsewhere.
bool replace_by_value(const V& v, const K& new_k) {
auto it = reverse_.find(v);
if (it == reverse_.end()) return false;
if (contains_key(new_k)) return false;
forward_.erase(it->second);
forward_.emplace(new_k, v);
it->second = new_k;
return true;
}
// Erase by key/value. Return number erased (0 or 1).
size_t erase_key(const K& k) {
auto it = forward_.find(k);
if (it == forward_.end()) return 0;
reverse_.erase(it->second);
forward_.erase(it);
return 1;
}
size_t erase_value(const V& v) {
auto it = reverse_.find(v);
if (it == reverse_.end()) return 0;
forward_.erase(it->second);
reverse_.erase(it);
return 1;
}
// Lookup
bool contains_key(const K& k) const { return forward_.find(k) != forward_.end(); }
bool contains_value(const V& v) const { return reverse_.find(v) != reverse_.end(); }
const V* find_by_key(const K& k) const {
auto it = forward_.find(k);
return (it == forward_.end()) ? nullptr : &it->second;
}
const K* find_by_value(const V& v) const {
auto it = reverse_.find(v);
return (it == reverse_.end()) ? nullptr : &it->second;
}
// at() variants throw std::out_of_range on missing entries
const V& at_key(const K& k) const { return forward_.at(k); }
const K& at_value(const V& v) const { return reverse_.at(v); }
void clear() noexcept {
forward_.clear();
reverse_.clear();
}
bool empty() const noexcept { return forward_.empty(); }
size_t size() const noexcept { return forward_.size(); }
// Reserve buckets for performance (optional)
void reserve(size_t n) {
forward_.reserve(n);
reverse_.reserve(n);
}
private:
std::unordered_map<K, V, HashK, EqK> forward_;
std::unordered_map<V, K, HashV, EqV> reverse_;
};
#include <chrono>
typedef std::chrono::steady_clock::time_point timepoint_t;
typedef std::chrono::steady_clock monoclock_t;
#endif #endif
+47 -27
View File
@@ -1,57 +1,77 @@
#include "HL_Uuid.h" #include "HL_Uuid.h"
#include <memory.h> #include <memory.h>
#include <random>
#include <span>
#if defined(TARGET_LINUX)
#define UUID_SYSTEM_GENERATOR
#endif
#include "uuid.h"
Uuid::Uuid() Uuid::Uuid()
{ {
#if defined(TARGET_WIN) || defined(TARGET_LINUX) || defined(TARGET_OSX)
memset(&mUuid, 0, sizeof mUuid); memset(&mUuid, 0, sizeof mUuid);
#endif
} }
Uuid Uuid::generateOne() Uuid Uuid::generateOne()
{ {
Uuid result; Uuid result;
// #if defined(TARGET_LINUX) #if !defined(USE_NULL_UUID)
// auto id = uuids::uuid_system_generator{}(); #if defined(TARGET_LINUX) || defined(TARGET_OSX)
// #else uuid_generate(result.mUuid);
std::random_device rd; #endif
auto seed_data = std::array<int, std::mt19937::state_size> {}; #if defined(TARGET_WIN)
std::generate(std::begin(seed_data), std::end(seed_data), std::ref(rd)); UuidCreate(&result.mUuid);
std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); #endif
std::mt19937 generator(seq); #endif
uuids::uuid_random_generator gen{generator};
auto id = gen();
// #endif
memcpy(result.mUuid, id.as_bytes().data(), id.as_bytes().size_bytes());
return result; return result;
} }
Uuid Uuid::parse(const std::string &s) Uuid Uuid::parse(const std::string &s)
{ {
Uuid result; Uuid result;
auto id = uuids::uuid::from_string(s); #if !defined(USE_NULL_UUID)
if (id) #if defined(TARGET_LINUX) || defined(TARGET_OSX)
memcpy(result.mUuid, id->as_bytes().data(), id->as_bytes().size_bytes()); uuid_parse(s.c_str(), result.mUuid);
#endif
#if defined(TARGET_WIN)
UuidFromStringA((RPC_CSTR)s.c_str(), &result.mUuid);
#endif
#endif
return result; return result;
} }
std::string Uuid::toString() const std::string Uuid::toString() const
{ {
auto s = std::span<uuids::uuid::value_type, 16>{(uuids::uuid::value_type*)mUuid, 16}; #if defined(USE_NULL_UUID)
uuids::uuid id(s); return "UUID_disabled";
return uuids::to_string(id); #else
char buf[64];
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
uuid_unparse_lower(mUuid, buf);
#endif
#if defined(TARGET_WIN)
RPC_CSTR s = nullptr;
UuidToStringA(&mUuid, &s);
if (s)
{
strcpy(buf, (const char*)s);
RpcStringFreeA(&s);
s = nullptr;
}
#endif
return buf;
#endif
} }
bool Uuid::operator < (const Uuid& right) const bool Uuid::operator < (const Uuid& right) const
{ {
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
return memcmp(mUuid, right.mUuid, sizeof(mUuid)) < 0; return memcmp(mUuid, right.mUuid, sizeof(mUuid)) < 0;
#endif
#if defined(TARGET_WIN)
return memcmp(&mUuid, &right.mUuid, sizeof(mUuid)) < 0;
#endif
return false; return false;
} }
+27 -7
View File
@@ -2,19 +2,39 @@
#define __HL_UUID_H #define __HL_UUID_H
#include <string> #include <string>
#include <stdint.h>
#if (defined(TARGET_LINUX) || defined(TARGET_OSX)) && !defined(USE_NULL_UUID)
// Please do not forget "sudo apt install uuid-dev" on Ubuntu
# include <uuid/uuid.h>
#endif
#if defined(TARGET_WIN)
# include <rpc.h>
#endif
class Uuid class Uuid
{ {
public: public:
Uuid(); Uuid();
static Uuid generateOne(); static Uuid generateOne();
static Uuid parse(const std::string& s); static Uuid parse(const std::string& s);
std::string toString() const; std::string toString() const;
bool operator < (const Uuid& right) const; bool operator < (const Uuid& right) const;
protected: protected:
uint8_t mUuid[16]; #if defined(USE_NULL_UUID)
unsigned char mUuid[16];
#else
#if defined(TARGET_LINUX) || defined(TARGET_OSX)
uuid_t mUuid;
#endif
#if defined(TARGET_WIN)
UUID mUuid;
#endif
#if defined(TARGET_ANDROID)
// Stub only
#endif
#endif
}; };
+4 -4
View File
@@ -240,7 +240,7 @@ int64_t Variant::asInt64() const
if (mType != VTYPE_INT64) if (mType != VTYPE_INT64)
throw Exception(ERR_BAD_VARIANT_TYPE); throw Exception(ERR_BAD_VARIANT_TYPE);
return mInt64; return mInt;
} }
bool Variant::asBool() const bool Variant::asBool() const
@@ -299,18 +299,18 @@ std::string Variant::asStdString() const
return mString; return mString;
case VTYPE_INT: case VTYPE_INT:
std::snprintf(buffer, sizeof(buffer), "%d", mInt); sprintf(buffer, "%d", mInt);
return buffer; return buffer;
case VTYPE_INT64: case VTYPE_INT64:
std::snprintf(buffer, sizeof(buffer), "%lli", static_cast<long long int>(mInt64)); sprintf(buffer, "%lli", static_cast<long long int>(mInt64));
return buffer; return buffer;
case VTYPE_BOOL: case VTYPE_BOOL:
return mBool ? "true" : "false"; return mBool ? "true" : "false";
case VTYPE_FLOAT: case VTYPE_FLOAT:
std::snprintf(buffer, sizeof(buffer), "%f", mFloat); sprintf(buffer, "%f", mFloat);
return buffer; return buffer;
default: default:
-109
View File
@@ -1,109 +0,0 @@
/* Copyright(C) 2007-2023 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_Xcap.h"
#include <sstream>
#include <iomanip>
#include <memory.h>
#include <algorithm>
#include <inttypes.h>
#ifdef TARGET_WIN
# include <WinSock2.h>
# include <Windows.h>
# include <cctype>
#endif
#define XML_HEADER "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
// --------------------- XcapHelper -----------------
std::string XcapHelper::buildBuddyList(const std::string& listName, const std::vector<std::string>& buddies)
{
std::ostringstream result;
result << XML_HEADER <<
"<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\">" <<
"<list name=\"" << listName.c_str() << "\">";
// to test CT only!
//result << "<entry uri=\"" << "sip:dbogovych1@10.11.1.25" << "\"/>";
//result << "<entry uri=\"" << "sip:dbogovych2@10.11.1.25" << "\"/>";
for (unsigned i = 0; i<buddies.size(); i++)
{
result << "<entry uri=\"" << normalizeSipUri(buddies[i]).c_str() << "\"/>";
}
result << "</list></resource-lists>";
return result.str();
}
std::string XcapHelper::buildRules(const std::vector<std::string>& buddies)
{
std::ostringstream result;
result << XML_HEADER <<
"<ruleset xmlns=\"urn:ietf:params:xml:ns:common-policy\">" <<
"<rule id=\"presence_allow\">" <<
"<conditions>";
for (unsigned i = 0; i<buddies.size(); i++)
{
result << "<identity><one id=\"" <<
normalizeSipUri(buddies[i]).c_str() << "\"/></identity>";
}
result << "</conditions>" <<
"<actions>" <<
"<sub-handling xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"allow" <<
"</sub-handling>" <<
"</actions>" <<
"<transformations>" <<
"<provide-devices xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"<all-devices/>" <<
"</provide-devices>" <<
"<provide-persons xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"<all-persons/>" <<
"</provide-persons>" <<
"<provide-services xmlns=\"urn:ietf:params:xml:ns:pres-rules\">" <<
"<all-services/>" <<
"</provide-services>" <<
"</transformations>" <<
"</rule>" <<
"</ruleset>";
return result.str();
}
std::string XcapHelper::buildServices(const std::string& serviceUri, const std::string& listRef)
{
std::ostringstream result;
result << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl <<
"<rls-services xmlns=\"urn:ietf:params:xml:ns:rls-services\"" << std::endl <<
"xmlns:rl=\"urn:ietf:params:xml:ns:resource-lists\"" << std::endl <<
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" << std::endl <<
"<service uri=\"" << normalizeSipUri(serviceUri).c_str() << "\">" << std::endl <<
"<resource-list>" << listRef.c_str() << "</resource-list>" << std::endl <<
"<packages>" << std::endl <<
"<package>presence</package>" << std::endl <<
"</packages>" << std::endl <<
"</service>" << std::endl <<
"</rls-services>";
return result.str();
}
std::string XcapHelper::normalizeSipUri(const std::string& uri)
{
std::string t(uri);
if (t.length())
{
if (t[0] == '<')
t.erase(0,1);
if (t.length())
{
if (t[t.length()-1] == '>')
t.erase(uri.length()-1, 1);
}
}
return t;
}
-23
View File
@@ -1,23 +0,0 @@
/* Copyright(C) 2007-2024 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 __HELPER_STRING_H
#define __HELPER_STRING_H
#include <vector>
#include <string>
#include <sstream>
class XcapHelper
{
public:
static std::string buildBuddyList(const std::string& listName, const std::vector<std::string>& buddies);
static std::string buildRules(const std::vector<std::string>& buddies);
static std::string buildServices(const std::string& serviceUri, const std::string& listRef);
static std::string normalizeSipUri(const std::string& uri);
};
#endif
+38 -42
View File
@@ -1,16 +1,19 @@
project (media_lib) project (media_lib)
if (NOT DEFINED LIB_PLATFORM) # Rely on C++ 11
message(FATAL_ERROR media_lib project requires LIB_PLATFORM to be set - it uses libraries from that directory) set (CMAKE_CXX_STANDARD 11)
endif()
# Rely on C++ 20
set (CMAKE_CXX_STANDARD 20)
set (CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_STANDARD_REQUIRED ON)
# Produce PIC code always # Produce PIC code always
set (CMAKE_POSITION_INDEPENDENT_CODE ON) set (CMAKE_POSITION_INDEPENDENT_CODE ON)
# Set of variables to control references to codecs
set (USE_AMR_CODEC OFF CACHE BOOL "Use AMR codec. Requires libraries.")
set (USE_EVS_CODEC OFF CACHE BOOL "Use EVS codec." )
set (USE_OPUS_CODEC OFF CACHE BOOL "Use Opus codec." )
set (USE_PVQA_LIB OFF CACHE BOOL "Build with Sevana PVQA library" )
set (USE_AQUA_LIB OFF CACHE BOOL "Build with Sevana AQuA library" )
set (SOURCES set (SOURCES
MT_Statistics.cpp MT_Statistics.cpp
MT_WebRtc.cpp MT_WebRtc.cpp
@@ -26,8 +29,6 @@ set (SOURCES
MT_AudioReceiver.cpp MT_AudioReceiver.cpp
MT_AudioCodec.cpp MT_AudioCodec.cpp
MT_CngHelper.cpp MT_CngHelper.cpp
MT_AmrCodec.cpp
MT_EvsCodec.cpp
MT_Statistics.h MT_Statistics.h
MT_WebRtc.h MT_WebRtc.h
@@ -43,52 +44,44 @@ set (SOURCES
MT_AudioReceiver.h MT_AudioReceiver.h
MT_AudioCodec.h MT_AudioCodec.h
MT_CngHelper.h MT_CngHelper.h
MT_AmrCodec.h
MT_EvsCodec.h
) )
add_library(media_lib ${SOURCES})
set (LIBS_CODEC)
if (USE_AMR_CODEC) if (USE_AMR_CODEC)
include (${LIB_PLATFORM}/platform_libs.cmake) message("AMR NB and WB codecs will be included.")
message("Media: AMR NB and WB codecs will be included.") add_definitions(-DUSE_AMR_CODEC)
target_compile_definitions(media_lib PUBLIC USE_AMR_CODEC) set(SOURCES ${SOURCES} MT_AmrCodec.cpp MT_AmrCodec.h)
list (APPEND LIBS_CODEC ${OPENCORE_AMRNB} ${OPENCORE_AMRWB})
endif() endif()
if (USE_EVS_CODEC) if (USE_EVS_CODEC)
message("Media: EVS codec will be included.") message("EVS codec will be included.")
target_compile_definitions (media_lib PUBLIC USE_EVS_CODEC) add_definitions (-DUSE_EVS_CODEC)
list (APPEND LIBS_CODEC evs_codec) set (SOURCES ${SOURCES} MT_EvsCodec.cpp MT_EvsCodec.h)
endif() endif()
message("Media: Opus codec will be included.") if (USE_OPUS_CODEC)
list (APPEND LIBS_CODEC opus) message("Opus codec will be included.")
add_definitions(-DUSE_OPUS_CODEC)
if(CMAKE_SYSTEM MATCHES "Linux*" OR CMAKE_SYSTEM MATCHES "Darwin*")
target_compile_definitions(media_lib PUBLIC HAVE_NETINET_IN_H)
endif() endif()
if(CMAKE_SYSTEM MATCHES "Linux*")
add_definitions(-DHAVE_NETINET_IN_H)
endif()
if(CMAKE_SYSTEM MATCHES "Darwin*")
# OS X Specific flags
add_definitions(-DHAVE_NETINET_IN_H)
endif()
if (CMAKE_SYSTEM MATCHES "Windows*") if (CMAKE_SYSTEM MATCHES "Windows*")
# Windows Specific flags - MSVC expected # Windows Specific flags - MSVC expected
target_compile_definitions(media_lib PRIVATE add_definitions(-D_CRT_SECURE_NO_WARNINGS -DHAVE_WINSOCK2_H
_CRT_SECURE_NO_WARNINGS -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS -DUNICODE -D_UNICODE )
HAVE_WINSOCK2_H
_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
UNICODE
_UNICODE )
endif() endif()
add_library(media_lib ${SOURCES})
# Depending on ice stack library to provide bit level operations
# Dependency on ice_stack - Linux build requires it target_link_libraries(media_lib PUBLIC ice_stack)
# Codec libraries as well
target_link_libraries(media_lib ice_stack ${LIBS_CODEC})
set_property(TARGET media_lib PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
target_include_directories(media_lib target_include_directories(media_lib
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/ PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/
@@ -96,9 +89,9 @@ target_include_directories(media_lib
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/include ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/include
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/crypto/include ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/srtp/crypto/include
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/webrtc ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/webrtc
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/opus/include/
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/resiprocate/ ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/resiprocate/
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/
${LIB_PLATFORM}/opus/include
) )
target_include_directories(media_lib target_include_directories(media_lib
@@ -108,5 +101,8 @@ target_include_directories(media_lib
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/basic_op ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/basic_op
${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/basic_math ${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libevs/basic_math
) )
if (USE_RESIP_INTEGRATION)
message("USE_RESIP_INTEGRATION is turned on!")
target_compile_definitions(media_lib PUBLIC -DUSE_RESIP_INTEGRATION)
endif()
File diff suppressed because it is too large Load Diff
+94 -109
View File
@@ -1,10 +1,8 @@
#ifndef MT_AMRCODEC_H #ifndef MT_AMRCODEC_H
#define MT_AMRCODEC_H #define MT_AMRCODEC_H
#include "../engine_config.h" #include "../config.h"
#include <map> #include <map>
#include <span>
#include "MT_Codec.h" #include "MT_Codec.h"
#include "../helper/HL_Pointer.h" #include "../helper/HL_Pointer.h"
@@ -16,157 +14,144 @@
namespace MT namespace MT
{ {
struct AmrCodecConfig struct AmrCodecConfig
{ {
bool mIuUP = false; bool mIuUP;
bool mOctetAligned = false; bool mOctetAligned;
int mPayloadType = -1; int mPayloadType;
}; };
class AmrNbCodec : public Codec class AmrNbCodec : public Codec
{ {
protected: protected:
void* mEncoderCtx = nullptr; void* mEncoderCtx;
void* mDecoderCtx = nullptr; void* mDecoderCtx;
AmrCodecConfig mConfig; AmrCodecConfig mConfig;
unsigned mCurrentDecoderTimestamp = 0; unsigned mCurrentDecoderTimestamp;
int mPreviousPacketLength = 0; int mSwitchCounter;
size_t mCngCounter = 0; int mPreviousPacketLength;
size_t mSwitchCounter = 0;
// opencore-amr encoder/decoder state is allocated lazily on first encode/decode. public:
// Network-MOS-only streams resolve codec metadata (name/samplerate/frame timing)
// but never decode, so they must not pay for a context they never use - at scale
// this is ~a decoder state (several KB) saved per network-only stream.
void ensureEncoder();
void ensureDecoder();
public:
class CodecFactory: public Factory class CodecFactory: public Factory
{ {
public: public:
CodecFactory(const AmrCodecConfig& config); CodecFactory(const AmrCodecConfig& config);
const char* name() override; const char* name() override;
int samplerate() override; int samplerate() override;
int payloadType() override; int payloadType() override;
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; #ifdef USE_RESIP_INTEGRATION
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override; int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override;
PCodec create() override; #endif
PCodec create() override;
protected: protected:
AmrCodecConfig mConfig; AmrCodecConfig mConfig;
}; };
AmrNbCodec(const AmrCodecConfig& config); AmrNbCodec(const AmrCodecConfig& config);
~AmrNbCodec();
Info info() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override;
virtual ~AmrNbCodec();
const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
int getSwitchCounter() const; int getSwitchCounter() const;
int getCngCounter() const; };
};
struct AmrWbStatistics class AmrWbCodec : public Codec
{ {
int mDiscarded = 0; protected:
int mNonParsed = 0; void* mEncoderCtx;
}; void* mDecoderCtx;
extern AmrWbStatistics GAmrWbStatistics;
class AmrWbCodec : public Codec
{
protected:
void* mEncoderCtx = nullptr;
void* mDecoderCtx = nullptr;
AmrCodecConfig mConfig; AmrCodecConfig mConfig;
uint64_t mCurrentDecoderTimestamp = 0; uint64_t mCurrentDecoderTimestamp;
size_t mSwitchCounter = 0; int mSwitchCounter;
size_t mCngCounter = 0;
int mPreviousPacketLength; int mPreviousPacketLength;
// Decoder state is allocated lazily on first decode/plc (see AmrNbCodec) so public:
// network-MOS-only streams never instantiate the AMR-WB decoder.
void ensureDecoder();
DecodeResult decodeIuup(std::span<const uint8_t> input, std::span<uint8_t> output);
DecodeResult decodePlain(std::span<const uint8_t> input, std::span<uint8_t> output);
public:
class CodecFactory: public Factory class CodecFactory: public Factory
{ {
public: public:
CodecFactory(const AmrCodecConfig& config); CodecFactory(const AmrCodecConfig& config);
const char* name() override; const char* name() override;
int samplerate() override; int samplerate() override;
int payloadType() override; int payloadType() override;
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; #ifdef USE_RESIP_INTEGRATION
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override; int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override;
PCodec create() override; #endif
PCodec create() override;
protected: protected:
AmrCodecConfig mConfig; AmrCodecConfig mConfig;
}; };
AmrWbCodec(const AmrCodecConfig& config); AmrWbCodec(const AmrCodecConfig& config);
virtual ~AmrWbCodec(); virtual ~AmrWbCodec();
Info info() override; const char* name() override;
int pcmLength() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int rtpLength() override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int frameTime() override;
size_t plc(int lostFrames, std::span<uint8_t> output) override; int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
int getSwitchCounter() const; int getSwitchCounter() const;
int getCngCounter() const; };
};
class GsmEfrCodec : public Codec class GsmEfrCodec : public Codec
{ {
protected: protected:
void* mEncoderCtx = nullptr; void* mEncoderCtx;
void* mDecoderCtx = nullptr; void* mDecoderCtx;
bool mIuUP = false; bool mIuUP;
public: public:
class GsmEfrFactory: public Factory class GsmEfrFactory: public Factory
{ {
public: public:
GsmEfrFactory(bool iuup, int ptype); GsmEfrFactory(bool iuup, int ptype);
const char* name() override; const char* name() override;
int samplerate() override; int samplerate() override;
int payloadType() override; int payloadType() override;
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; #ifdef USE_RESIP_INTEGRATION
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override; int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
void create(CodecMap& codecs) override;
#endif
PCodec create() override;
PCodec create() override;
protected: protected:
bool mIuUP; bool mIuUP;
int mPayloadType; int mPayloadType;
}; };
GsmEfrCodec(bool iuup = false); GsmEfrCodec(bool iuup = false);
~GsmEfrCodec();
Info info() override; virtual ~GsmEfrCodec();
const char* name() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int pcmLength() override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int rtpLength() override;
size_t plc(int lostFrames, std::span<uint8_t> output) override; int frameTime() override;
}; int samplerate() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
};
} // End of MT namespace } // End of MT namespace
File diff suppressed because it is too large Load Diff
+102 -68
View File
@@ -6,7 +6,7 @@
#ifndef __AUDIO_CODEC_H #ifndef __AUDIO_CODEC_H
#define __AUDIO_CODEC_H #define __AUDIO_CODEC_H
#include "../engine_config.h" #include "../config.h"
#include <map> #include <map>
#include "MT_Codec.h" #include "MT_Codec.h"
#include "../audio/Audio_Resampler.h" #include "../audio/Audio_Resampler.h"
@@ -24,7 +24,9 @@ extern "C"
#include "libg729/g729_typedef.h" #include "libg729/g729_typedef.h"
#include "libg729/g729_ld8a.h" #include "libg729/g729_ld8a.h"
#include "opus.h" #if defined(USE_OPUS_CODEC)
# include "opus.h"
#endif
namespace MT namespace MT
{ {
@@ -40,48 +42,49 @@ public:
{ {
public: public:
const char* name() override; const char* name() override;
int channels() override; int channels() override;
int samplerate() override; int samplerate() override;
int payloadType() override; int payloadType() override;
#if defined(USE_RESIP_INTEGRATION)
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
#endif
PCodec create() override; PCodec create() override;
}; };
G729Codec(); G729Codec();
~G729Codec() override; ~G729Codec() override;
Info info() override; const char* name() override;
int pcmLength() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int rtpLength() override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int frameTime() override;
size_t plc(int lostFrames, std::span<uint8_t> output) override; int samplerate() override;
int channels() override;
int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
int plc(int lostFrames, void* output, int outputCapacity) override;
}; };
#if defined(USE_OPUS_CODEC)
class OpusCodec: public Codec class OpusCodec: public Codec
{ {
protected: protected:
OpusEncoder *mEncoderCtx = nullptr; OpusEncoder *mEncoderCtx;
OpusDecoder *mDecoderCtx = nullptr; OpusDecoder *mDecoderCtx;
int mPTime = 0, int mPTime, mSamplerate, mChannels;
mSamplerate = 0, Audio::SpeexResampler mDecodeResampler;
mChannels = 0;
int mDecoderChannels = 0;
public: public:
struct Params struct Params
{ {
bool mUseDtx = false, bool mUseDtx, mUseInbandFec, mStereo;
mUseInbandFec = false, int mPtime, mTargetBitrate, mExpectedPacketLoss;
mStereo = false;
int mPtime = 0,
mTargetBitrate = 0,
mExpectedPacketLoss = 0;
Params(); Params();
#if defined(USE_RESIP_INTEGRATION)
resip::Data toString() const; resip::Data toString() const;
void parse(const resip::Data& params); void parse(const resip::Data& params);
#endif
}; };
class OpusFactory: public Factory class OpusFactory: public Factory
@@ -99,31 +102,35 @@ public:
int channels() override; int channels() override;
int samplerate() override; int samplerate() override;
int payloadType() override; int payloadType() override;
#if defined(USE_RESIP_INTEGRATION)
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override; int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction) override;
#endif
PCodec create() override; PCodec create() override;
}; };
OpusCodec(Audio::Format fmt, int ptime); OpusCodec(int samplerate, int channels, int ptime);
~OpusCodec(); ~OpusCodec();
void applyParams(const Params& params); void applyParams(const Params& params);
Info info() override; const char* name();
int pcmLength();
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int rtpLength();
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int frameTime();
size_t plc(int lostFrames, std::span<uint8_t> output) override; int samplerate();
int channels();
size_t getNumberOfSamples(std::span<const uint8_t> payload); int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
}; };
#endif
class IlbcCodec: public Codec class IlbcCodec: public Codec
{ {
protected: protected:
int mPacketTime = 0; /// Single frame time (20 or 30 ms) int mPacketTime; /// Single frame time (20 or 30 ms)
iLBC_encinst_t* mEncoderCtx = nullptr; iLBC_encinst_t* mEncoderCtx;
iLBC_decinst_t* mDecoderCtx = nullptr; iLBC_decinst_t* mDecoderCtx;
public: public:
class IlbcFactory: public Factory class IlbcFactory: public Factory
@@ -138,19 +145,24 @@ public:
const char* name(); const char* name();
int samplerate(); int samplerate();
int payloadType(); int payloadType();
#if defined(USE_RESIP_INTEGRATION)
void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction); void updateSdp(resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction); int processSdp(const resip::SdpContents::Session::Medium::CodecContainer& codecs, SdpDirection direction);
void create(CodecMap& codecs); void create(CodecMap& codecs);
#endif
PCodec create(); PCodec create();
}; };
IlbcCodec(int packetTime); IlbcCodec(int packetTime);
virtual ~IlbcCodec(); virtual ~IlbcCodec();
Info info() override; const char* name();
int pcmLength();
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int rtpLength();
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int frameTime();
size_t plc(int lostFrames, std::span<uint8_t> output) override; int samplerate();
int encode(const void* input, int inputBytes, void* output, int outputCapacity);
int decode(const void* input, int inputBytes, void* output, int outputCapacity);
int plc(int lostFrames, void* output, int outputCapacity);
}; };
class G711Codec: public Codec class G711Codec: public Codec
@@ -183,11 +195,15 @@ public:
G711Codec(int type); G711Codec(int type);
~G711Codec(); ~G711Codec();
Info info() override; const char* name();
int pcmLength();
int frameTime();
int rtpLength();
int samplerate();
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int encode(const void* input, int inputBytes, void* output, int outputCapacity);
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int decode(const void* input, int inputBytes, void* output, int outputCapacity);
size_t plc(int lostSamples, std::span<uint8_t> output) override ; int plc(int lostSamples, void* output, int outputCapacity);
protected: protected:
int mType; /// Determines if it is u-law or a-law codec. Its value is ALaw or ULaw. int mType; /// Determines if it is u-law or a-law codec. Its value is ALaw or ULaw.
@@ -196,9 +212,9 @@ protected:
class IsacCodec: public Codec class IsacCodec: public Codec
{ {
protected: protected:
int mSamplerate = 0; int mSamplerate;
ISACFIX_MainStruct* mEncoderCtx = nullptr; ISACFIX_MainStruct* mEncoderCtx;
ISACFIX_MainStruct* mDecoderCtx = nullptr; ISACFIX_MainStruct* mDecoderCtx;
public: public:
class IsacFactory16K: public Factory class IsacFactory16K: public Factory
{ {
@@ -230,11 +246,15 @@ public:
IsacCodec(int sampleRate); IsacCodec(int sampleRate);
~IsacCodec(); ~IsacCodec();
Info info() override; const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int encode(const void* input, int inputBytes, void* output, int outputCapacity);
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int decode(const void* input, int inputBytes, void* output, int outputCapacity);
size_t plc(int lostFrames, std::span<uint8_t> output) override; int plc(int lostFrames, void* output, int outputCapacity);
}; };
@@ -298,13 +318,17 @@ public:
GsmCodec(Type codecType); GsmCodec(Type codecType);
/*! Destructor. */ /*! Destructor. */
~GsmCodec(); virtual ~GsmCodec();
Info info() override; const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int encode(const void* input, int inputBytes, void* output, int outputCapacity);
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int decode(const void* input, int inputBytes, void* output, int outputCapacity);
size_t plc(int lostFrames, std::span<uint8_t> output) override; int plc(int lostFrames, void* output, int outputCapacity);
}; };
/// GSM MIME name /// GSM MIME name
@@ -343,19 +367,25 @@ public:
PCodec create(); PCodec create();
}; };
G722Codec(); G722Codec();
~G722Codec(); virtual ~G722Codec();
Info info() override; const char* name();
int pcmLength();
int rtpLength();
int frameTime();
int samplerate();
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int encode(const void* input, int inputBytes, void* output, int outputCapacity);
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int decode(const void* input, int inputBytes, void* output, int outputCapacity);
size_t plc(int lostFrames, std::span<uint8_t> output) override; int plc(int lostFrames, void* output, int outputCapacity);
//unsigned GetSamplerate() { return 16000; }
}; };
class GsmHrCodec: public Codec class GsmHrCodec: public Codec
{ {
protected: protected:
void* mDecoder = nullptr; void* mDecoder;
public: public:
class GsmHrFactory: public Factory class GsmHrFactory: public Factory
@@ -375,11 +405,15 @@ public:
GsmHrCodec(); GsmHrCodec();
~GsmHrCodec() override; ~GsmHrCodec() override;
Info info() override; const char* name() override;
int pcmLength() override;
int rtpLength() override;
int frameTime() override;
int samplerate() override;
EncodeResult encode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int encode(const void* input, int inputBytes, void* output, int outputCapacity) override;
DecodeResult decode(std::span<const uint8_t> input, std::span<uint8_t> output) override; int decode(const void* input, int inputBytes, void* output, int outputCapacity) override;
size_t plc(int lostFrames, std::span<uint8_t> output) override; int plc(int lostFrames, void* output, int outputCapacity) override;
}; };
} }
File diff suppressed because it is too large Load Diff
+116 -226
View File
@@ -1,4 +1,4 @@
/* Copyright(C) 2007-2025 VoIPobjects (voipobjects.com) /* Copyright(C) 2007-2017 VoIPobjects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -6,125 +6,96 @@
#ifndef __MT_AUDIO_RECEIVER_H #ifndef __MT_AUDIO_RECEIVER_H
#define __MT_AUDIO_RECEIVER_H #define __MT_AUDIO_RECEIVER_H
#include "../engine_config.h"
#include "MT_Stream.h" #include "MT_Stream.h"
#include "MT_CodecList.h" #include "MT_CodecList.h"
#include "MT_AudioCodec.h"
#include "MT_CngHelper.h" #include "MT_CngHelper.h"
#include "../helper/HL_Pointer.h"
#include "../helper/HL_Sync.h" #include "../helper/HL_Sync.h"
#include "../helper/HL_Optional.hpp"
#include "jrtplib/src/rtppacket.h" #include "jrtplib/src/rtppacket.h"
#include "jrtplib/src/rtcppacket.h"
#include "jrtplib/src/rtpsourcedata.h" #include "jrtplib/src/rtpsourcedata.h"
#include "../audio/Audio_DataWindow.h" #include "../audio/Audio_DataWindow.h"
#include "../audio/Audio_Resampler.h" #include "../audio/Audio_Resampler.h"
#include <optional> #if defined(USE_PVQA_LIBRARY)
#include <chrono> # include "pvqa++.h"
#include <vector> #endif
#include <cstdint>
using namespace std::chrono_literals; #include <map>
// #define DUMP_DECODED
namespace MT namespace MT
{ {
using jrtplib::RTPPacket; using jrtplib::RTPPacket;
class RtpBuffer class RtpBuffer
{ {
public: public:
enum class FetchResult
{
RegularPacket,
Gap,
NoPacket
};
// Owns rtp packet data // Owns rtp packet data
class Packet class Packet
{ {
public: public:
Packet(const std::shared_ptr<RTPPacket>& packet, std::chrono::milliseconds timelen, int samplerate); Packet(const std::shared_ptr<RTPPacket>& packet, int timelen, int rate);
std::shared_ptr<RTPPacket> rtp() const; std::shared_ptr<RTPPacket> rtp() const;
std::chrono::milliseconds timelength() const; int timelength() const;
int samplerate() const; int rate() const;
const std::vector<short>& pcm() const; const std::vector<short>& pcm() const;
std::vector<short>& pcm(); std::vector<short>& pcm();
const std::chrono::microseconds& timestamp() const;
std::chrono::microseconds& timestamp();
protected: protected:
std::shared_ptr<RTPPacket> mRtp; std::shared_ptr<RTPPacket> mRtp;
std::chrono::milliseconds mTimelength = 0ms; int mTimelength = 0, mRate = 0;
int mSamplerate = 0; std::vector<short> mPcm;
std::vector<short> mPcm;
std::chrono::microseconds mTimestamp = 0us;
};
struct FetchResult
{
enum class Status
{
RegularPacket,
Gap,
NoPacket
};
Status mStatus = Status::NoPacket;
std::shared_ptr<Packet> mPacket;
std::string toString() const
{
switch (mStatus)
{
case Status::RegularPacket: return "packet";
case Status::Gap: return "gap";
case Status::NoPacket: return "empty";
}
}
}; };
RtpBuffer(Statistics& stat); RtpBuffer(Statistics& stat);
~RtpBuffer(); ~RtpBuffer();
unsigned ssrc() const; unsigned ssrc();
void setSsrc(unsigned ssrc); void setSsrc(unsigned ssrc);
void setHigh(std::chrono::milliseconds t); void setHigh(int milliseconds);
std::chrono::milliseconds high() const; int high();
void setLow(std::chrono::milliseconds t); void setLow(int milliseconds);
std::chrono::milliseconds low() const; int low();
void setPrebuffer(std::chrono::milliseconds t); void setPrebuffer(int milliseconds);
std::chrono::milliseconds prebuffer() const; int prebuffer();
int getNumberOfReturnedPackets() const; int getNumberOfReturnedPackets() const;
int getNumberOfAddPackets() const; int getNumberOfAddPackets() const;
std::chrono::milliseconds findTimelength(); int findTimelength();
int getCount() const; int getCount() const;
// Returns false if packet was not add - maybe too old or too new or duplicate // Returns false if packet was not add - maybe too old or too new or duplicate
std::shared_ptr<Packet> add(const std::shared_ptr<RTPPacket>& packet, std::chrono::milliseconds timelength, int rate); std::shared_ptr<Packet> add(std::shared_ptr<RTPPacket> packet, int timelength, int rate);
typedef std::vector<std::shared_ptr<Packet>> ResultList; typedef std::vector<std::shared_ptr<Packet>> ResultList;
typedef std::shared_ptr<ResultList> PResultList; typedef std::shared_ptr<ResultList> PResultList;
FetchResult fetch(); FetchResult fetch(ResultList& rl);
// Drop oldest packets so buffered audio stays within the high-water mark, protected:
// recording packet-loss events for any sequence gaps crossed (the same unsigned mSsrc = 0;
// accounting fetch() performs). Used to bound memory on streams that never int mHigh = RTP_BUFFER_HIGH,
// call fetch() - i.e. network-MOS-only streams with audio decode disabled, mLow = RTP_BUFFER_LOW,
// which would otherwise retain every packet for the whole call. mPrebuffer = RTP_BUFFER_PREBUFFER;
// int mReturnedCounter = 0,
// maxPackets, when non-zero, additionally caps the buffer to that many packets mAddCounter = 0;
// regardless of buffered time. The decode path (fetch()) leaves it 0 so jitter
// tolerance stays governed by the time-based high-water mark; the network-only
// path passes a small cap since those packets are never decoded.
void trimToHighWater(size_t maxPackets = 0);
protected:
unsigned mSsrc = 0;
std::chrono::milliseconds mHigh = std::chrono::milliseconds(RTP_BUFFER_HIGH),
mLow = std::chrono::milliseconds(RTP_BUFFER_LOW),
mPrebuffer = std::chrono::milliseconds(RTP_BUFFER_PREBUFFER);
int mReturnedCounter = 0,
mAddCounter = 0;
mutable Mutex mGuard; mutable Mutex mGuard;
typedef std::vector<std::shared_ptr<Packet>> PacketList; typedef std::vector<std::shared_ptr<Packet>> PacketList;
@@ -133,202 +104,121 @@ protected:
bool mFirstPacketWillGo = true; bool mFirstPacketWillGo = true;
jrtplib::RTPSourceStats mRtpStats; jrtplib::RTPSourceStats mRtpStats;
std::shared_ptr<Packet> mFetchedPacket; std::shared_ptr<Packet> mFetchedPacket;
std::optional<uint32_t> mLastSeqno;
std::optional<jrtplib::RTPTime> mLastReceiveTime;
// To calculate average interval between packet add. It is close to jitter but more useful in debugging. // To calculate average interval between packet add. It is close to jitter but more useful in debugging.
float mLastAddTime = 0.0f; float mLastAddTime = 0.0;
}; };
class Receiver class Receiver
{ {
public: public:
Receiver(Statistics& stat); Receiver(Statistics& stat);
virtual ~Receiver(); virtual ~Receiver();
protected: protected:
Statistics& mStat; Statistics& mStat;
}; };
class DtmfReceiver: public Receiver class AudioReceiver: public Receiver
{ {
private: public:
char mEvent = 0;
bool mEventEnded = false;
std::chrono::milliseconds mEventStart = 0ms;
std::function<void(char)> mCallback;
public:
DtmfReceiver(Statistics& stat);
~DtmfReceiver();
void add(const std::shared_ptr<RTPPacket>& p);
void setCallback(std::function<void(char tone)> callback);
};
class AudioReceiver: public Receiver
{
public:
AudioReceiver(const CodecList::Settings& codecSettings, Statistics& stat); AudioReceiver(const CodecList::Settings& codecSettings, Statistics& stat);
~AudioReceiver(); ~AudioReceiver();
// Update codec settings
void setCodecSettings(const CodecList::Settings& codecSettings);
CodecList::Settings& getCodecSettings();
// Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding. // Returns false when packet is rejected as illegal. codec parameter will show codec which will be used for decoding.
// Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container). // Lifetime of pointer to codec is limited by lifetime of AudioReceiver (it is container).
Codec* add(const std::shared_ptr<jrtplib::RTPPacket>& p); bool add(const std::shared_ptr<jrtplib::RTPPacket>& p, Codec** codec = nullptr);
struct DecodeOptions // Returns false when there is no rtp data from jitter
enum DecodeOptions
{ {
bool mRealtimeProcessing = false; // Target PCAP parsing by default DecodeOptions_ResampleToMainRate = 0,
bool mResampleToMainRate = true; // Resample all decoded audio to AUDIO_SAMPLERATE DecodeOptions_DontResample = 1,
bool mFillGapByCNG = false; // Use CNG information if available DecodeOptions_FillCngGap = 2,
bool mSkipDecode = false; // Don't do decode, just dry run - fetch packets, remove them from the jitter buffer DecodeOptions_SkipDecode = 4
std::chrono::milliseconds mElapsed = 0ms; // How much milliseconds should be decoded; zero value means "decode just next packet from the buffer"
DecodeOptions decreaseElapsedBy(std::chrono::milliseconds delta)
{
return
{
.mRealtimeProcessing = mRealtimeProcessing,
.mResampleToMainRate = mResampleToMainRate,
.mFillGapByCNG = mFillGapByCNG,
.mSkipDecode = mSkipDecode,
.mElapsed = std::max(mElapsed - delta, 0ms)
};
}
}; };
struct DecodeResult bool getAudio(Audio::DataWindow& output, int options = DecodeOptions_ResampleToMainRate, int* rate = nullptr);
{
enum class Status
{
Ok, // Decoded ok
Skip, // Just no data - emit silence instead
BadPacket // Error happened during the decode
};
Status mStatus = Status::Ok;
int mSamplerate = 0;
int mChannels = 0;
};
DecodeResult getAudioTo(Audio::DataWindow& output, DecodeOptions options);
// Looks for codec by payload type // Looks for codec by payload type
Codec* findCodec(int payloadType); Codec* findCodec(int payloadType);
RtpBuffer& getRtpBuffer() { return mRtpBuffer; } RtpBuffer& getRtpBuffer() { return mBuffer; }
// Returns size of AudioReceiver's instance in bytes (including size of all data + codecs + etc.) // Returns size of AudioReceiver's instance in bytes (including size of all data + codecs + etc.)
int getSize() const; int getSize() const;
struct MediaInfo // Returns timelength for given packet
{ int timelengthFor(jrtplib::RTPPacket& p);
std::chrono::milliseconds mTimeLength = 0ms;
int mSamplerate = 0;
};
MediaInfo infoFor(jrtplib::RTPPacket& p);
void processDtmf(); // Return samplerate for given packet
int samplerateFor(jrtplib::RTPPacket& p);
void updateDecodingTimeStatistics(); protected:
RtpBuffer mBuffer;
CodecMap mCodecMap;
PCodec mCodec;
int mFrameCount = 0;
CodecList::Settings mCodecSettings;
CodecList mCodecList;
JitterStatistics mJitterStats;
std::shared_ptr<jrtplib::RTPPacket> mCngPacket;
CngDecoder mCngDecoder;
protected: // Decode RTP early, do not wait for speaker callback
// Resolve (and lazily create) the codec for a payload type. Returns null when no bool mEarlyDecode = false;
// factory handles it. Used by add()/findCodec()/infoFor() so mLazyCodecMap works.
Codec* ensureCodec(int payloadType);
// Lazily create the comfort-noise decoder on first use. // Buffer to hold decoded data
CngDecoder& cng(); char mDecodedFrame[65536];
int mDecodedLength = 0;
RtpBuffer mRtpBuffer; // RTP jitter buffer itself; here are audio packets // Buffer to hold data converted to stereo/mono
RtpBuffer mDtmfBuffer; // These two (mDtmfBuffer / mDtmfReceiver) are for our analyzer stack only; in normal softphone logic DTMF packets goes via SingleAudioStream::mDtmfReceiver char mConvertedFrame[32768];
DtmfReceiver mDtmfReceiver; int mConvertedLength = 0;
CodecMap mCodecMap;
PCodec mCodec;
int mFrameCount = 0;
CodecList::Settings mCodecSettings;
CodecList mCodecList;
JitterStatistics mJitterStats;
std::shared_ptr<RtpBuffer::Packet> mCngPacket;
// Lazily created on first CNG use (getAudioTo); its ctor calls WebRtcCng_CreateDec,
// which is wasted for the many streams that never decode comfort noise. Access via cng().
std::unique_ptr<CngDecoder> mCngDecoder;
size_t mDTXSamplesToEmit = 0; // How much silence (or CNG) should be emited before next RTP packet gets into the action
// Already decoded data that can be retrieved without actual decoding - it may happen because of getAudioTo() may be limited by time interval
Audio::DataWindow mAvailable;
// Decode/convert/resample scratch buffers. These were inline arrays
// (MT_MAX_DECODEBUFFER * {1,2,1} * int16_t = 256 KB total) carried by every
// AudioReceiver, hence by every StreamDecoder - including network-MOS-only
// streams that never decode. They are now allocated lazily on the first
// getAudioTo() call via ensureDecodeBuffers(); non-decoding streams keep them
// empty. Once allocated they are sized to full capacity and reused, so decode
// behaviour is unchanged.
std::vector<int16_t> mDecodedFrame; // sized to MT_MAX_DECODEBUFFER
size_t mDecodedLength = 0;
// Buffer to hold data converted to stereo/mono; there is multiplier 2 as it can be stereo audio
std::vector<int16_t> mConvertedFrame; // sized to MT_MAX_DECODEBUFFER * 2
size_t mConvertedLength = 0;
// Buffer to hold data resampled to AUDIO_SAMPLERATE // Buffer to hold data resampled to AUDIO_SAMPLERATE
std::vector<int16_t> mResampledFrame; // sized to MT_MAX_DECODEBUFFER char mResampledFrame[65536];
size_t mResampledLength = 0; int mResampledLength = 0;
// Last packet time length // Last packet time length
int mLastPacketTimeLength = 0; int mLastPacketTimeLength = 0;
std::optional<uint32_t> mLastPacketTimestamp;
int mFailedCount = 0; int mFailedCount = 0;
Audio::Resampler mResampler8, Audio::Resampler mResampler8, mResampler16,
mResampler16, mResampler32, mResampler48;
mResampler32,
mResampler48;
Audio::PWavFileWriter mDecodedDump; Audio::PWavFileWriter mDecodedDump;
std::optional<std::chrono::steady_clock::time_point> mDecodeTimestamp; // Time last call happened to codec->decode() float mLastDecodeTime = 0.0; // Time last call happened to codec->decode()
float mIntervalSum = 0.0f; float mIntervalSum = 0.0;
int mIntervalCount = 0; int mIntervalCount = 0;
std::chrono::milliseconds mRequestedAudio = 0ms;
std::chrono::milliseconds mProducedAudio = 0ms;
// Lazily allocate the decode/convert/resample scratch buffers (mDecodedFrame,
// mConvertedFrame, mResampledFrame) to full capacity on the first decode. A
// no-op once allocated. Called at the top of getAudioTo(); network-MOS-only
// streams never reach it, so they never pay the 256 KB.
void ensureDecodeBuffers();
// Zero rate will make audio mono but resampling will be skipped // Zero rate will make audio mono but resampling will be skipped
void makeMonoAndResample(int rate, int channels); void makeMonoAndResample(int rate, int channels);
// Resamples, sends to analysis, writes to dump and queues to output decoded frames from mDecodedFrame // Resamples, sends to analysis, writes to dump and queues to output decoded frames from mDecodedFrame
void processDecoded(Audio::DataWindow& output, DecodeOptions options); void processDecoded(Audio::DataWindow& output, int options);
void produceSilence(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options);
void produceCNG(std::chrono::milliseconds length, Audio::DataWindow& output, DecodeOptions options);
// Calculate bitrate switch statistics for AMR codecs #if defined(USE_PVQA_LIBRARY) && defined(PVQA_IN_RECEIVER)
void updateAmrCodecStats(Codec* c); std::shared_ptr<sevana::pvqa> mPVQA;
void initPvqa();
void updatePvqa(const void* data, int size);
float calculatePvqaMos(int rate, std::string& report);
DecodeResult decodeGapTo(Audio::DataWindow& output, DecodeOptions options); std::shared_ptr<Audio::DataWindow> mPvqaBuffer;
DecodeResult decodePacketTo(Audio::DataWindow& output, DecodeOptions options, const std::shared_ptr<RtpBuffer::Packet>& p); #endif
DecodeResult decodeEmptyTo(Audio::DataWindow& output, DecodeOptions options);
std::optional<std::chrono::steady_clock::time_point> mLastDecodeTimestamp; void processStatisticsWithAmrCodec(Codec* c);
std::chrono::microseconds mIntervalBetweenDecode = 0us; };
size_t mDecodeCount = 0;
void updateDecodeIntervalStatistics(); class DtmfReceiver: public Receiver
{
}; public:
DtmfReceiver(Statistics& stat);
~DtmfReceiver();
void add(std::shared_ptr<RTPPacket> p);
};
} }
#endif #endif
+304 -337
View File
@@ -16,452 +16,419 @@
#include "jrtplib/src/rtptransmitter.h" #include "jrtplib/src/rtptransmitter.h"
#include "jrtplib/src/rtpsessionparams.h" #include "jrtplib/src/rtpsessionparams.h"
#define LOG_SUBSYSTEM "media" #define LOG_SUBSYSTEM "AudioStream"
//#define DUMP_SENDING_AUDIO //#define DUMP_SENDING_AUDIO
using namespace MT; using namespace MT;
AudioStream::AudioStream(const CodecList::Settings& settings) AudioStream::AudioStream(const CodecList::Settings& settings)
:mPacketTime(0), mEncodedTime(0), mCodecSettings(settings), :mPacketTime(0), mEncodedTime(0), mCodecSettings(settings),
mRemoteTelephoneCodec(0), mRtpSession(), mTransmittingPayloadType(-1), mRemoteTelephoneCodec(0), mRtpSession(), mTransmittingPayloadType(-1),
mRtpSender(mStat) mRtpSender(mStat)
{ {
mOutputBuffer.setCapacity(16384); mOutputBuffer.setCapacity(16384);
mCapturedAudio.setCapacity(16384); mCapturedAudio.setCapacity(16384);
mCaptureResampler8.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 8000); mCaptureResampler8.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 8000);
mCaptureResampler16.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 16000); mCaptureResampler16.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 16000);
mCaptureResampler32.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 32000); mCaptureResampler32.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 32000);
mCaptureResampler48.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 48000); mCaptureResampler48.start(AUDIO_CHANNELS, AUDIO_SAMPLERATE, 48000);
// Configure transmitter // Configure transmitter
jrtplib::RTPExternalTransmissionParams params(&mRtpSender, 0); jrtplib::RTPExternalTransmissionParams params(&mRtpSender, 0);
jrtplib::RTPSessionParams sessionParams; jrtplib::RTPSessionParams sessionParams;
sessionParams.SetAcceptOwnPackets(true); sessionParams.SetAcceptOwnPackets(true);
sessionParams.SetMaximumPacketSize(MT_MAXRTPPACKET); sessionParams.SetMaximumPacketSize(MT_MAXRTPPACKET);
sessionParams.SetResolveLocalHostname(false); sessionParams.SetResolveLocalHostname(false);
sessionParams.SetUsePollThread(false); sessionParams.SetUsePollThread(false);
sessionParams.SetOwnTimestampUnit(1/8000.0); sessionParams.SetOwnTimestampUnit(1/8000.0);
mRtpSession.Create(sessionParams, &params, jrtplib::RTPTransmitter::ExternalProto); mRtpSession.Create(sessionParams, &params, jrtplib::RTPTransmitter::ExternalProto);
mRtpDtmfSession.Create(sessionParams, &params, jrtplib::RTPTransmitter::ExternalProto); mRtpDtmfSession.Create(sessionParams, &params, jrtplib::RTPTransmitter::ExternalProto);
// Attach srtp session to sender // Attach srtp session to sender
mRtpSender.setSrtpSession(&mSrtpSession); mRtpSender.setSrtpSession(&mSrtpSession);
//mRtpDump = new RtpDump("d:\\outgoing.rtp"); //mRtpDump = new RtpDump("d:\\outgoing.rtp");
//mRtpSender.setDumpWriter(mRtpDump); //mRtpSender.setDumpWriter(mRtpDump);
#if defined(DUMP_SENDING_AUDIO) #if defined(DUMP_SENDING_AUDIO)
mSendingDump = std::make_shared<WavFileWriter>(); mSendingDump = std::make_shared<WavFileWriter>();
mSendingDump->open("sending_audio.wav", 8000, 1); mSendingDump->open("sending_audio.wav", 8000, 1);
#endif #endif
} }
AudioStream::~AudioStream() AudioStream::~AudioStream()
{ {
ICELogInfo(<< "Delete AudioStream instance"); ICELogInfo(<< "Delete AudioStream instance");
if (mSendingDump) if (mSendingDump)
{ {
mSendingDump->close(); mSendingDump->close();
mSendingDump.reset(); mSendingDump.reset();
} }
// Delete used rtp streams // Delete used rtp streams
for (AudioStreamMap::iterator streamIter = mStreamMap.begin(); streamIter != mStreamMap.end(); ++streamIter) for (AudioStreamMap::iterator streamIter = mStreamMap.begin(); streamIter != mStreamMap.end(); ++streamIter)
delete streamIter->second; delete streamIter->second;
mStreamMap.clear(); mStreamMap.clear();
if (mRtpDtmfSession.IsActive()) if (mRtpDtmfSession.IsActive())
mRtpDtmfSession.Destroy(); mRtpDtmfSession.Destroy();
if (mRtpSession.IsActive()) if (mRtpSession.IsActive())
mRtpSession.Destroy(); mRtpSession.Destroy();
#if defined(USE_RTPDUMP) #if defined(USE_RTPDUMP)
if (mRtpDump) if (mRtpDump)
{ {
mRtpDump->flush(); mRtpDump->flush();
delete mRtpDump; delete mRtpDump;
} }
#endif #endif
mCaptureResampler8.stop(); mCaptureResampler8.stop();
mCaptureResampler16.stop(); mCaptureResampler16.stop();
mCaptureResampler32.stop(); mCaptureResampler32.stop();
mCaptureResampler48.stop(); mCaptureResampler48.stop();
ICELogInfo(<< "Encoded " << mEncodedTime << " milliseconds of audio"); ICELogInfo(<< "Encoded " << mEncodedTime << " milliseconds of audio");
if (mDumpStreams.mStreamForRecordingIncoming) if (mDumpStreams.mStreamForRecordingIncoming)
mDumpStreams.mStreamForRecordingIncoming->close(); mDumpStreams.mStreamForRecordingIncoming->close();
if (mDumpStreams.mStreamForReadingOutgoing) if (mDumpStreams.mStreamForReadingOutgoing)
mDumpStreams.mStreamForReadingOutgoing->close(); mDumpStreams.mStreamForReadingOutgoing->close();
if (mFinalStatistics) if (mFinalStatistics)
*mFinalStatistics = mStat; *mFinalStatistics = mStat;
ICELogInfo(<< mStat.toString()); ICELogInfo(<< mStat.toString());
} }
void AudioStream::setDestination(const RtpPair<InternetAddress>& dest) void AudioStream::setDestination(const RtpPair<InternetAddress>& dest)
{ {
Lock l(mMutex); Lock l(mMutex);
Stream::setDestination(dest); Stream::setDestination(dest);
mRtpSender.setDestination(dest); mRtpSender.setDestination(dest);
} }
void AudioStream::setTransmittingCodec(Codec::Factory& factory, int payloadType) void AudioStream::setTransmittingCodec(Codec::Factory& factory, int payloadType)
{ {
ICELogInfo(<< "Selected codec " << factory.name() << "/" << factory.samplerate() << " for transmitting"); ICELogInfo(<< "Selected codec " << factory.name() << "/" << factory.samplerate() << " for transmitting");
Lock l(mMutex); Lock l(mMutex);
mTransmittingCodec = factory.create(); mTransmittingCodec = factory.create();
mTransmittingPayloadType = payloadType; mTransmittingPayloadType = payloadType;
if (mRtpSession.IsActive()) if (mRtpSession.IsActive())
mRtpSession.SetTimestampUnit(1.0 / mTransmittingCodec->samplerate()); mRtpSession.SetTimestampUnit(1.0 / mTransmittingCodec->samplerate());
} }
PCodec AudioStream::transmittingCodec() PCodec AudioStream::transmittingCodec()
{ {
Lock l(mMutex); Lock l(mMutex);
return mTransmittingCodec; return mTransmittingCodec;
} }
void AudioStream::addData(const void* buffer, int bytes) void AudioStream::addData(const void* buffer, int bytes)
{ {
assert(bytes == AUDIO_MIC_BUFFER_SIZE); assert(bytes == AUDIO_MIC_BUFFER_SIZE);
// Read predefined audio if configured // Read predefined audio if configured
if (mDumpStreams.mStreamForReadingOutgoing) if (mDumpStreams.mStreamForReadingOutgoing)
{ {
if (mDumpStreams.mStreamForReadingOutgoing->isOpened()) if (mDumpStreams.mStreamForReadingOutgoing->isOpened())
mDumpStreams.mStreamForReadingOutgoing->read(const_cast<void*>(buffer), bytes); mDumpStreams.mStreamForReadingOutgoing->read(const_cast<void*>(buffer), bytes);
}
// Read mirrored audio if needed
if (mMirror && mMirrorPrebuffered)
mMirrorBuffer.read(const_cast<void*>(buffer), bytes);
if (mMediaObserver)
mMediaObserver->onMedia(buffer, bytes, MT::Stream::MediaDirection::Outgoing, this, mMediaObserverTag);
Codec* codec = nullptr;
{
Lock l(mMutex);
codec = mTransmittingCodec.get();
if (nullptr == codec) {
// ICELogDebug(<< "No transmitting codec selected.");
return;
} }
}
// Read mirrored audio if needed // Resample
if (mMirror && mMirrorPrebuffered) unsigned dstlen = unsigned(float(codec->samplerate() / float(AUDIO_SAMPLERATE)) * bytes);
mMirrorBuffer.read(const_cast<void*>(buffer), bytes); Audio::Resampler* r = nullptr;
switch (codec->samplerate())
{
case 8000: r = &mCaptureResampler8; break;
case 16000: r = &mCaptureResampler16; break;
case 32000: r = &mCaptureResampler32; break;
case 48000: r = &mCaptureResampler48; break;
default:
assert(0);
}
if (mMediaObserver) size_t processedInput = 0;
mMediaObserver->onMedia(buffer, bytes, MT::Stream::MediaDirection::Outgoing, this, mMediaObserverTag); dstlen = r->processBuffer(buffer, bytes, processedInput, mResampleBuffer, dstlen);
// ProcessedInput output value is ignored - because sample rate of input is always 8/16/32/48K - so all buffer is processed
Codec* codec = nullptr; // See if we need stereo <-> mono conversions
{ unsigned stereolen = 0;
Lock l(mMutex); if (codec->channels() != AUDIO_CHANNELS)
codec = mTransmittingCodec.get(); {
if (nullptr == codec) { if (codec->channels() == 2)
// ICELogDebug(<< "No transmitting codec selected."); stereolen = Audio::ChannelConverter::monoToStereo(mResampleBuffer, dstlen, mStereoBuffer, dstlen * 2);
return;
}
}
// Resample
unsigned dstlen = unsigned(float(codec->samplerate() / float(AUDIO_SAMPLERATE)) * bytes);
Audio::Resampler* r = nullptr;
switch (codec->samplerate())
{
case 8000: r = &mCaptureResampler8; break;
case 16000: r = &mCaptureResampler16; break;
case 32000: r = &mCaptureResampler32; break;
case 48000: r = &mCaptureResampler48; break;
default:
assert(0);
}
size_t processedInput = 0;
dstlen = r->processBuffer(buffer, bytes, processedInput, mResampleBuffer, dstlen);
// ProcessedInput output value is ignored - because sample rate of input is always 8/16/32/48K - so all buffer is processed
// See if we need stereo <-> mono conversions
unsigned stereolen = 0;
if (codec->channels() != AUDIO_CHANNELS)
{
if (codec->channels() == 2)
stereolen = Audio::ChannelConverter::monoToStereo(mResampleBuffer, dstlen, mStereoBuffer, dstlen * 2);
else
dstlen = Audio::ChannelConverter::stereoToMono(mResampleBuffer, dstlen, mResampleBuffer, dstlen / 2);
}
// See if inband dtmf audio should be sent instead
ByteBuffer dtmf;
if (mDtmfContext.type() == DtmfContext::Dtmf_Inband && mDtmfContext.getInband(AUDIO_MIC_BUFFER_LENGTH, codec->samplerate(), dtmf))
mCapturedAudio.add(dtmf.data(), dtmf.size());
else else
mCapturedAudio.add(stereolen ? mStereoBuffer : mResampleBuffer, stereolen ? stereolen : dstlen); dstlen = Audio::ChannelConverter::stereoToMono(mResampleBuffer, dstlen, mResampleBuffer, dstlen / 2);
}
// See if it is time to send RFC2833 tone // See if inband dtmf audio should be sent instead
ByteBuffer rfc2833, stopPacket; ByteBuffer dtmf;
if (mDtmfContext.type() == DtmfContext::Dtmf_Rfc2833 && mDtmfContext.getRfc2833(AUDIO_MIC_BUFFER_LENGTH, rfc2833, stopPacket)) if (mDtmfContext.type() == DtmfContext::Dtmf_Inband && mDtmfContext.getInband(AUDIO_MIC_BUFFER_LENGTH, codec->samplerate(), dtmf))
mCapturedAudio.add(dtmf.data(), dtmf.size());
else
mCapturedAudio.add(stereolen ? mStereoBuffer : mResampleBuffer, stereolen ? stereolen : dstlen);
// See if it is time to send RFC2833 tone
ByteBuffer rfc2833, stopPacket;
if (mDtmfContext.type() == DtmfContext::Dtmf_Rfc2833 && mDtmfContext.getRfc2833(AUDIO_MIC_BUFFER_LENGTH, rfc2833, stopPacket))
{
if (rfc2833.size())
mRtpDtmfSession.SendPacket(rfc2833.data(), rfc2833.size(), mRemoteTelephoneCodec, true, AUDIO_MIC_BUFFER_LENGTH * 8);
if (stopPacket.size())
{ {
if (rfc2833.size()) for (int i=0; i<3; i++)
mRtpDtmfSession.SendPacket(rfc2833.data(), rfc2833.size(), mRemoteTelephoneCodec, true, AUDIO_MIC_BUFFER_LENGTH * 8); mRtpDtmfSession.SendPacket(stopPacket.data(), stopPacket.size(), mRemoteTelephoneCodec, true, AUDIO_MIC_BUFFER_LENGTH * 8);
if (stopPacket.size())
{
for (int i=0; i<3; i++)
mRtpDtmfSession.SendPacket(stopPacket.data(), stopPacket.size(), mRemoteTelephoneCodec, true, AUDIO_MIC_BUFFER_LENGTH * 8);
}
} }
}
int processed = 0;
int encodedTime = 0;
int packetTime = mPacketTime ? mPacketTime : codec->frameTime();
int processed = 0; // Make stereo version if required
int encodedTime = 0; for (int i=0; i<mCapturedAudio.filled() / codec->pcmLength(); i++)
int packetTime = mPacketTime ? mPacketTime : codec->frameTime(); {
if (mSendingDump)
mSendingDump->write((const char*)mCapturedAudio.data() + codec->pcmLength() * i, codec->pcmLength());
// Make stereo version if required int produced;
for (int i=0; i<mCapturedAudio.filled() / codec->pcmLength(); i++) produced = codec->encode((const char*)mCapturedAudio.data() + codec->pcmLength()*i,
{ codec->pcmLength(), mFrameBuffer, MT_MAXAUDIOFRAME);
if (mSendingDump)
mSendingDump->write((const char*)mCapturedAudio.data() + codec->pcmLength() * i, codec->pcmLength());
auto r = codec->encode({(const uint8_t*)mCapturedAudio.data() + codec->pcmLength()*i, (size_t)codec->pcmLength()},
{(uint8_t*)mFrameBuffer, MT_MAXAUDIOFRAME});
// Counter of processed input bytes of raw pcm data from microphone // Counter of processed input bytes of raw pcm data from microphone
processed += codec->pcmLength(); processed += codec->pcmLength();
encodedTime += codec->frameTime(); encodedTime += codec->frameTime();
mEncodedTime += codec->frameTime(); mEncodedTime += codec->frameTime();
if (r.mEncoded) if (produced)
{ {
mEncodedAudio.appendBuffer(mFrameBuffer, r.mEncoded); mEncodedAudio.appendBuffer(mFrameBuffer, produced);
if (packetTime <= encodedTime) if (packetTime <= encodedTime)
{ {
// Time to send packet // Time to send packet
ICELogMedia(<< "Sending RTP packet pt = " << mTransmittingPayloadType << ", plength = " << (int)mEncodedAudio.size() << " to "); ICELogMedia(<< "Sending RTP packet pt = " << mTransmittingPayloadType << ", plength = " << (int)mEncodedAudio.size());
mRtpSession.SendPacketEx(mEncodedAudio.data(), mEncodedAudio.size(), mTransmittingPayloadType, false, mRtpSession.SendPacketEx(mEncodedAudio.data(), mEncodedAudio.size(), mTransmittingPayloadType, false,
packetTime * codec->samplerate()/1000, 0, nullptr, 0); packetTime * codec->samplerate()/1000, 0, NULL, 0);
mEncodedAudio.clear(); mEncodedAudio.clear();
encodedTime = 0; encodedTime = 0;
} }
}
} }
if (processed > 0) }
mCapturedAudio.erase(processed); if (processed > 0)
mCapturedAudio.erase(processed);
} }
void AudioStream::copyDataTo(Audio::Mixer& mixer, int needed) void AudioStream::copyDataTo(Audio::Mixer& mixer, int needed)
{ {
// mStreamMap is also mutated from the network thread (dataArrived) // Local audio mixer - used to send audio to media observer
Lock l(mMutex); Audio::Mixer localMixer;
Audio::DataWindow forObserver;
// Local audio mixer - used to send audio to media observer // Iterate
Audio::Mixer localMixer; for (auto& streamIter: mStreamMap)
Audio::DataWindow forObserver; {
Audio::DataWindow w;
// Iterate w.setCapacity(32768);
for (auto& streamIter: mStreamMap)
SingleAudioStream* sas = streamIter.second;
if (sas)
{ {
Audio::DataWindow w; sas->copyPcmTo(w, needed);
w.setCapacity(32768);
SingleAudioStream* sas = streamIter.second; // Provide mirroring if needed
if (sas) if (mMirror)
{
mMirrorBuffer.add(w.data(), w.filled());
if (!mMirrorPrebuffered)
mMirrorPrebuffered = mMirrorBuffer.filled() >= MT_MIRROR_PREBUFFER;
}
if (!(state() & (int)StreamState::Receiving))
w.zero(needed);
// Check if we do not need input from this stream
if (w.filled())
{
if (mDumpStreams.mStreamForRecordingIncoming)
{ {
sas->copyPcmTo(w, needed); if (mDumpStreams.mStreamForRecordingIncoming->isOpened())
mDumpStreams.mStreamForRecordingIncoming->write(w.data(), w.filled());
// Provide mirroring if needed
if (mMirror)
{
mMirrorBuffer.add(w.data(), w.filled());
if (!mMirrorPrebuffered)
mMirrorPrebuffered = mMirrorBuffer.filled() >= MT_MIRROR_PREBUFFER;
}
if (!(state() & (int)StreamState::Receiving))
w.zero(needed);
// Check if we do not need input from this stream
if (w.filled())
{
if (mDumpStreams.mStreamForRecordingIncoming)
{
if (mDumpStreams.mStreamForRecordingIncoming->isOpened())
mDumpStreams.mStreamForRecordingIncoming->write(w.data(), w.filled());
}
mixer.addPcm(this, streamIter.first, w, AUDIO_SAMPLERATE, false);
if (mMediaObserver)
localMixer.addPcm(this, streamIter.first, w, AUDIO_SAMPLERATE, false);
}
} }
} mixer.addPcm(this, streamIter.first, w, AUDIO_SAMPLERATE, false);
if (mMediaObserver) if (mMediaObserver)
{ localMixer.addPcm(this, streamIter.first, w, AUDIO_SAMPLERATE, false);
int mixedBytes = localMixer.mixAndGetPcm(forObserver); }
if (mixedBytes > 0)
mMediaObserver->onMedia(forObserver.data(), mixedBytes, MT::Stream::MediaDirection::Incoming, this, mMediaObserverTag);
} }
}
if (mMediaObserver)
{
localMixer.mixAndGetPcm(forObserver);
mMediaObserver->onMedia(forObserver.data(), forObserver.capacity(), MT::Stream::MediaDirection::Incoming, this, mMediaObserverTag);
}
} }
void AudioStream::dataArrived(PDatagramSocket s, const void* buffer, int length, InternetAddress& source) void AudioStream::dataArrived(PDatagramSocket s, const void* buffer, int length, InternetAddress& source)
{ {
// Protects mStreamMap (also iterated by copyDataTo on the audio thread) jrtplib::RTPIPv6Address addr6;
// and the receive/decrypt buffers. jrtplib::RTPIPv4Address addr4;
Lock l(mMutex); jrtplib::RTPExternalTransmissionInfo* info = dynamic_cast<jrtplib::RTPExternalTransmissionInfo*>(mRtpSession.GetTransmissionInfo());
assert(info);
// Drop RTP packets if stream is not receiving now; let RTCP go
if (!(state() & (int)StreamState::Receiving) && RtpHelper::isRtp(buffer, length))
{
ICELogMedia(<< "Stream is not allowed to receive RTP stream. Ignore the packet");
return;
}
jrtplib::RTPIPv6Address addr6; // Copy incoming data to temp buffer to perform possible srtp unprotect
jrtplib::RTPIPv4Address addr4; int receiveLength = length;
jrtplib::RTPExternalTransmissionInfo* info = dynamic_cast<jrtplib::RTPExternalTransmissionInfo*>(mRtpSession.GetTransmissionInfo()); memcpy(mReceiveBuffer, buffer, length);
assert(info);
// Drop RTP packets if stream is not receiving now; let RTCP go bool srtpResult;
if (!(state() & (int)StreamState::Receiving) && RtpHelper::isRtp(buffer, length)) if (mSrtpSession.active())
{ {
ICELogMedia(<< "Stream is not allowed to receive RTP stream. Ignore the RTP packet");
return;
}
// Copy incoming data to temp buffer to perform possible srtp unprotect
int receiveLength = length;
memcpy(mReceiveBuffer, buffer, length);
if (mSrtpSession.active())
{
bool srtpResult;
size_t srcLength = length; size_t dstLength = sizeof mSrtpDecodeBuffer;
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
srtpResult = mSrtpSession.unprotectRtp(mReceiveBuffer, srcLength, mSrtpDecodeBuffer, &dstLength);
else
srtpResult = mSrtpSession.unprotectRtcp(mReceiveBuffer, srcLength, mSrtpDecodeBuffer, &dstLength);
if (!srtpResult)
{
ICELogError(<<"Cannot decrypt SRTP packet.");
return;
}
memcpy(mReceiveBuffer, mSrtpDecodeBuffer, dstLength);
receiveLength = dstLength;
}
switch (source.family())
{
case AF_INET:
addr4.SetIP(source.sockaddr4()->sin_addr.s_addr);
addr4.SetPort(source.port());
ICELogMedia(<< "Injecting RTP/RTCP packet into jrtplib");
info->GetPacketInjector()->InjectRTPorRTCP(mReceiveBuffer, receiveLength, addr4);
break;
case AF_INET6:
addr6.SetIP(source.sockaddr6()->sin6_addr);
addr6.SetPort(source.port());
ICELogMedia(<< "Injecting RTP/RTCP packet into jrtplib");
info->GetPacketInjector()->InjectRTPorRTCP(mReceiveBuffer, receiveLength, addr6);
break;
default:
assert(0);
}
mStat.mReceived += length;
auto& perDst = mStat.mPerDestination[source];
perDst.mReceivedBytes += length;
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength)) if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
{ srtpResult = mSrtpSession.unprotectRtp(mReceiveBuffer, &receiveLength);
if (!mStat.mFirstRtpTime)
mStat.mFirstRtpTime = std::chrono::steady_clock::now();
mStat.mReceivedRtp++;
perDst.mReceivedRtp++;
}
else else
srtpResult = mSrtpSession.unprotectRtcp(mReceiveBuffer, &receiveLength);
if (!srtpResult)
{ {
mStat.mReceivedRtcp++; ICELogError(<<"Cannot decrypt SRTP packet.");
perDst.mReceivedRtcp++; return;
} }
}
mRtpSession.Poll(); // maybe it is extra with external transmitter //ICELogDebug(<< "Packet no: " << RtpHelper::findPacketNo(mReceiveBuffer, receiveLength));
bool hasData = mRtpSession.GotoFirstSourceWithData();
while (hasData) switch (source.family())
{
case AF_INET:
addr4.SetIP(source.sockaddr4()->sin_addr.s_addr);
addr4.SetPort(source.port());
ICELogMedia(<< "Injecting RTP/RTCP packet into jrtplib");
info->GetPacketInjector()->InjectRTPorRTCP(mReceiveBuffer, receiveLength, addr4);
break;
case AF_INET6:
addr6.SetIP(source.sockaddr6()->sin6_addr);
addr6.SetPort(source.port());
ICELogMedia(<< "Injecting RTP/RTCP packet into jrtplib");
info->GetPacketInjector()->InjectRTPorRTCP(mReceiveBuffer, receiveLength, addr6);
break;
default:
assert(0);
}
mStat.mReceived += length;
if (RtpHelper::isRtp(mReceiveBuffer, receiveLength))
{
if (!mStat.mFirstRtpTime.is_initialized())
mStat.mFirstRtpTime = std::chrono::system_clock::now();
mStat.mReceivedRtp++;
}
else
mStat.mReceivedRtcp++;
mRtpSession.Poll(); // maybe it is extra with external transmitter
bool hasData = mRtpSession.GotoFirstSourceWithData();
while (hasData)
{
std::shared_ptr<jrtplib::RTPPacket> packet(mRtpSession.GetNextPacket());
if (packet)
{ {
std::shared_ptr<jrtplib::RTPPacket> packet(mRtpSession.GetNextPacket()); ICELogMedia(<< "jrtplib returned packet");
if (packet) // Find right handler for rtp stream
{ SingleAudioStream* rtpStream = nullptr;
ICELogMedia(<< "jrtplib returned packet"); AudioStreamMap::iterator streamIter = mStreamMap.find(packet->GetSSRC());
if (streamIter == mStreamMap.end())
mStreamMap[packet->GetSSRC()] = rtpStream = new SingleAudioStream(mCodecSettings, mStat);
else
rtpStream = streamIter->second;
// Find right handler for rtp stream // Process incoming data packet
SingleAudioStream* rtpStream = nullptr; rtpStream->process(packet);
auto streamIter = mStreamMap.find(packet->GetSSRC());
if (streamIter == mStreamMap.end()) { double rtt = mRtpSession.GetCurrentSourceInfo()->INF_GetRoundtripTime().GetDouble();
rtpStream = new SingleAudioStream(mCodecSettings, mStat); if (rtt > 0)
mStreamMap.insert({packet->GetSSRC(), rtpStream}); mStat.mRttDelay.process(rtt);
}
else
rtpStream = streamIter->second;
// Process incoming data packet
rtpStream->process(packet);
// RTT sanity filter: jrtplib's INF_GetRoundtripTime() does the
// RFC 3550 §6.4.1 math but omits clock-skew / outlier guards;
// without these, a skewed or buggy peer can poison mRttDelay
// (and therefore the Id term in MOS).
double rtt = mRtpSession.GetCurrentSourceInfo()->INF_GetRoundtripTime().GetDouble();
if (rtt > 0 && rtt < 30.0) // reject "RTT not making any sense" (>30s)
{
// Once an average is established, cap a new sample at 3x mean
// so a single outlier can't skew the running RTT.
constexpr double kRttNormalizeFactor = 3.0;
const double meanRtt = mStat.mRttDelay.average();
if (mStat.mRttDelay.is_initialized() && meanRtt > 0.0 &&
rtt > meanRtt * kRttNormalizeFactor)
{
rtt = meanRtt * kRttNormalizeFactor;
}
mStat.mRttDelay.process(rtt);
}
}
hasData = mRtpSession.GotoNextSourceWithData();
} }
hasData = mRtpSession.GotoNextSourceWithData();
}
} }
void AudioStream::setState(unsigned state) void AudioStream::setState(unsigned state)
{ {
Stream::setState(state); Stream::setState(state);
} }
void AudioStream::setTelephoneCodec(int payloadType) void AudioStream::setTelephoneCodec(int payloadType)
{ {
mRemoteTelephoneCodec = payloadType; mRemoteTelephoneCodec = payloadType;
} }
void AudioStream::setSocket(const RtpPair<PDatagramSocket>& socket) void AudioStream::setSocket(const RtpPair<PDatagramSocket>& socket)
{ {
Stream::setSocket(socket); Stream::setSocket(socket);
mRtpSender.setSocket(socket); mRtpSender.setSocket(socket);
} }
DtmfContext& AudioStream::queueOfDtmf() DtmfContext& AudioStream::queueOfDtmf()
{ {
return mDtmfContext; return mDtmfContext;
} }
void AudioStream::readFile(const Audio::PWavFileReader& stream, MediaDirection direction) void AudioStream::readFile(const Audio::PWavFileReader& stream, MediaDirection direction)
{ {
switch (direction) switch (direction)
{ {
case MediaDirection::Outgoing: mDumpStreams.mStreamForReadingOutgoing = stream; break; case MediaDirection::Outgoing: mDumpStreams.mStreamForReadingOutgoing = stream; break;
case MediaDirection::Incoming: mDumpStreams.mStreamForReadingIncoming = stream; break; case MediaDirection::Incoming: mDumpStreams.mStreamForReadingIncoming = stream; break;
} }
} }
void AudioStream::writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction) void AudioStream::writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction)
{ {
switch (direction) switch (direction)
{ {
case MediaDirection::Outgoing: mDumpStreams.mStreamForRecordingOutgoing = writer; break; case MediaDirection::Outgoing: mDumpStreams.mStreamForRecordingOutgoing = writer; break;
case MediaDirection::Incoming: mDumpStreams.mStreamForRecordingIncoming = writer; break; case MediaDirection::Incoming: mDumpStreams.mStreamForRecordingIncoming = writer; break;
} }
} }
void AudioStream::setupMirror(bool enable) void AudioStream::setupMirror(bool enable)
{ {
if (!mMirror && enable) if (!mMirror && enable)
{ {
mMirrorBuffer.setCapacity(MT_MIRROR_CAPACITY); mMirrorBuffer.setCapacity(MT_MIRROR_CAPACITY);
mMirrorPrebuffered = false; mMirrorPrebuffered = false;
} }
mMirror = enable; mMirror = enable;
} }
void AudioStream::setFinalStatisticsOutput(Statistics* stats) void AudioStream::setFinalStatisticsOutput(Statistics* stats)
{ {
mFinalStatistics = stats; mFinalStatistics = stats;
} }
+23 -24
View File
@@ -6,7 +6,7 @@
#ifndef __MT_AUDIOSTREAM_H #ifndef __MT_AUDIOSTREAM_H
#define __MT_AUDIOSTREAM_H #define __MT_AUDIOSTREAM_H
#include "../engine_config.h" #include "../config.h"
#include "MT_Stream.h" #include "MT_Stream.h"
#include "MT_NativeRtpSender.h" #include "MT_NativeRtpSender.h"
#include "MT_SingleAudioStream.h" #include "MT_SingleAudioStream.h"
@@ -25,10 +25,10 @@
namespace MT namespace MT
{ {
class AudioStream: public Stream class AudioStream: public Stream
{ {
public: public:
AudioStream(const CodecList::Settings& codecSettings); AudioStream(const CodecList::Settings& codecSettings);
~AudioStream(); ~AudioStream();
@@ -38,7 +38,7 @@ public:
void setTransmittingCodec(Codec::Factory& factory, int payloadType) override; void setTransmittingCodec(Codec::Factory& factory, int payloadType) override;
PCodec transmittingCodec(); PCodec transmittingCodec();
// Called to queue data captured from microphone. // Called to queue data captured from microphone.
// Buffer holds 16bits PCM data with AUDIO_SAMPLERATE rate and AUDIO_CHANNELS channels. // Buffer holds 16bits PCM data with AUDIO_SAMPLERATE rate and AUDIO_CHANNELS channels.
void addData(const void* buffer, int length); void addData(const void* buffer, int length);
@@ -51,7 +51,7 @@ public:
void setState(unsigned state) override; void setState(unsigned state) override;
void setTelephoneCodec(int payloadType); void setTelephoneCodec(int payloadType);
DtmfContext& queueOfDtmf(); DtmfContext& queueOfDtmf();
void readFile(const Audio::PWavFileReader& stream, MediaDirection direction) override; void readFile(const Audio::PWavFileReader& stream, MediaDirection direction) override;
void writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction) override; void writeFile(const Audio::PWavFileWriter& writer, MediaDirection direction) override;
@@ -59,19 +59,19 @@ public:
void setFinalStatisticsOutput(Statistics* stats); void setFinalStatisticsOutput(Statistics* stats);
protected: protected:
Audio::DataWindow mCapturedAudio; // Data from microphone Audio::DataWindow mCapturedAudio; // Data from microphone
Audio::DataWindow mStereoCapturedAudio; Audio::DataWindow mStereoCapturedAudio;
char mIncomingPcmBuffer[AUDIO_MIC_BUFFER_SIZE] = {0}; // Temporary buffer to allow reading from file char mIncomingPcmBuffer[AUDIO_MIC_BUFFER_SIZE]; // Temporary buffer to allow reading from file
char mResampleBuffer[AUDIO_MIC_BUFFER_SIZE*8] = {0}; // Temporary buffer to hold data char mResampleBuffer[AUDIO_MIC_BUFFER_SIZE*8]; // Temporary buffer to hold data
char mStereoBuffer[AUDIO_MIC_BUFFER_SIZE*16] = {0}; // Temporary buffer to hold data converted to stereo char mStereoBuffer[AUDIO_MIC_BUFFER_SIZE*16]; // Temporary buffer to hold data converted to stereo
PCodec mTransmittingCodec; // Current encoding codec PCodec mTransmittingCodec; // Current encoding codec
int mTransmittingPayloadType = -1; // Payload type to mark outgoing packets int mTransmittingPayloadType; // Payload type to mark outgoing packets
int mPacketTime = 0; // Required packet time int mPacketTime; // Required packet time
char mFrameBuffer[MT_MAXAUDIOFRAME]; // Temporary buffer to hold results of encoder char mFrameBuffer[MT_MAXAUDIOFRAME]; // Temporary buffer to hold results of encoder
ByteBuffer mEncodedAudio; // Encoded frame(s) ByteBuffer mEncodedAudio; // Encoded frame(s)
int mEncodedTime = 0; // Time length of encoded audio int mEncodedTime; // Time length of encoded audio
CodecList::Settings mCodecSettings; // Configuration for stream const CodecList::Settings& mCodecSettings; // Configuration for stream
Mutex mMutex; // Mutex Mutex mMutex; // Mutex
int mRemoteTelephoneCodec; // Payload for remote telephone codec int mRemoteTelephoneCodec; // Payload for remote telephone codec
jrtplib::RTPSession mRtpSession; // Rtp session jrtplib::RTPSession mRtpSession; // Rtp session
@@ -86,16 +86,15 @@ protected:
mCaptureResampler16, mCaptureResampler16,
mCaptureResampler32, mCaptureResampler32,
mCaptureResampler48; mCaptureResampler48;
DtmfContext mDtmfContext; DtmfContext mDtmfContext;
char mReceiveBuffer[MAX_VALID_UDPPACKET_SIZE] = {0}, char mReceiveBuffer[MAX_VALID_UDPPACKET_SIZE];
mSrtpDecodeBuffer[MAX_VALID_UDPPACKET_SIZE] = {0};
struct struct
{ {
Audio::PWavFileWriter mStreamForRecordingIncoming, Audio::PWavFileWriter mStreamForRecordingIncoming,
mStreamForRecordingOutgoing; mStreamForRecordingOutgoing;
Audio::PWavFileReader mStreamForReadingIncoming, Audio::PWavFileReader mStreamForReadingIncoming,
mStreamForReadingOutgoing; mStreamForReadingOutgoing;
} mDumpStreams; } mDumpStreams;
Audio::PWavFileWriter mSendingDump; Audio::PWavFileWriter mSendingDump;
@@ -106,8 +105,8 @@ protected:
Statistics* mFinalStatistics = nullptr; Statistics* mFinalStatistics = nullptr;
// bool decryptSrtp(void* data, int* len); bool decryptSrtp(void* data, int* len);
}; };
}; };
#endif #endif

Some files were not shown because too many files have changed in this diff Show More