initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(DEPExamples)
|
||||
|
||||
set(DANTE_LINUX_EXAMPLES True)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11 -fPIC")
|
||||
add_subdirectory(source/dep_audio_buffers)
|
||||
add_subdirectory(source)
|
||||
include(source/alsalib.cmake)
|
||||
|
||||
|
||||
|
||||
159
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/README.md
Normal file
159
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
DEP OEM Example Applications
|
||||
============================
|
||||
|
||||
There are several example applications in `dep_examples/apps` directory:
|
||||
|
||||
1. **DepLoopback** Loop received audio samples back out the transmit path, with a configurable latency.
|
||||
2. **DepAlsaBridge** Bridge DEP's audio buffers to an ALSA soundcard. Only for bridged clock domains.
|
||||
3. **dsoundcard** ALSA plugin for DEP to appear as a sound card.
|
||||
4. **dinfo** Print information about the DEP shared audio buffers configuration.
|
||||
5. **dplay** Play audio samples from a file out through DEP.
|
||||
6. **drecord** Record audio samples from DEP to a file.
|
||||
7. **dtest** Play test tones into DEP transmit buffers.
|
||||
|
||||
## Application Details
|
||||
|
||||
The DEP Programmer's Guide contains a section describing how these example applications work, including diagrams.
|
||||
Consult that guide and the provided source code for more details.
|
||||
|
||||
### DepLoopback
|
||||
|
||||
`DepLoopback` provides a minimal example for receiving and transmitting audio samples using DEP.
|
||||
A callback handler receives a block of audio samples from DEP, and writes these back to the transmit channels at a configurable latency in the future.
|
||||
The latency value is set by a command line parameter, and must be at least the scheduling frequency (set in `dante.json`).
|
||||
This application can be used to determine the lowest possible round-trip glitch-free audio path within the local software system to/from DEP, given a system state (e.g. subscribed flows, scheduler parameters, system performance level, etc).
|
||||
|
||||
### DepAlsaBridge
|
||||
|
||||
`DepAlsaBridge` acts as a user-space ALSA application, writing and reading samples to/from ALSA device(s).
|
||||
Because the sound cards (ALSA devices) are clocked, they require the rate of samples to be precisely aligned with the application.
|
||||
DEP's audio samples are aligned with the connected Dante network, which is distinctly different from the local clock domain of the ALSA device.
|
||||
For this application to work, the local sound card and Dante network clock domains must be bridged.
|
||||
DEP supports a "hardware clocking" feature, which controls a hardware clock generation circuit to be synchronised with the Dante network time.
|
||||
If these clocks are then used by the local sound card(s), the two clock domains are bridged, and this application can be used.
|
||||
|
||||
**DepAlsaBridge should only be used in conjunction with DEP's Hardware Clocking feature - ie when local audio clocks are synchronised to Dante network time.**
|
||||
|
||||
A software Asynchronous Sample Rate Converter is required to be integrated into an ALSA bridge if the hardware clocking feature is not used.
|
||||
|
||||
### dsoundcard
|
||||
|
||||
ALSA plugin `dsoundcard.so` which provides an ALSA device which bridges application audio to/from DEP.
|
||||
Requires some installation: see below section.
|
||||
This application does not require hardware clocking, and permits use of DEP similar to other (eg USB) sound cards.
|
||||
|
||||
### dinfo
|
||||
|
||||
Print metadata from the DEP shared audio buffers to `stdout` and exist.
|
||||
Use the `-h` flag for more config options.
|
||||
|
||||
### dplay
|
||||
|
||||
This software application plays mp3, wav and flac files to the DEP transmit buffers.
|
||||
Use the `-h` flag for more config options.
|
||||
|
||||
### drecord
|
||||
|
||||
This software application records audio data from the DEP receive buffers into a wav file.
|
||||
Use the `-h` flag for more config options.
|
||||
|
||||
### dtest
|
||||
|
||||
This software application plays tones or pink noise to the DEP transmit buffers.
|
||||
Use the `-h` flag for more config options.
|
||||
|
||||
## Usage
|
||||
|
||||
System requirements for running the pre-compiled applications are:
|
||||
|
||||
* Target environment: `aarch64-linux-gnu`
|
||||
* glibc version: `2.31`
|
||||
* GLIBCXX version: `3.4.28`
|
||||
|
||||
To use `AlsaBridge` or `dsoundcard`, ALSA is also required:
|
||||
|
||||
* alsa-lib version: `1.2.1.2`
|
||||
* alsa-utils version: `1.2.1`
|
||||
|
||||
After the DEP container is fully started you can start the application.
|
||||
It is recommended to run it as a background task, e.g.
|
||||
|
||||
```
|
||||
./DepLoopback &
|
||||
```
|
||||
|
||||
### Installing and using dsoundcard
|
||||
|
||||
To install `dsoundcard`, run the script `install.sh` and copy the contents of `asoundrc` into `~/.asoundrc`, then reboot.
|
||||
Check that the soundcard has been added to the list of ALSA devices by running `aplay -L` and ensuring `dsoundcard` appears in the list.
|
||||
|
||||
DEP can be used as an ALSA output device by specifying `dsoundcard` as the ALSA device name.
|
||||
For example, to play audio to dep, run `aplay -D dsoundcard file.wav` or similar.
|
||||
|
||||
Recording audio received by DEP is similar: `arecord -D dsoundcard -c 2 -f S32_LE -r 48000 file.wav`.
|
||||
Note the specification of the audio format and configuration to use.
|
||||
|
||||
#### Pulse Audio
|
||||
|
||||
The plugin can be registered as a PulseAudio output by running `pacmd load-module module-alsa-sink sink_name=dsoundcard device=dsoundcard format=s32le rate=48000` (adjust sample rate to match DEP sample rate).
|
||||
Sometimes you need to run `pacmd update-sink-proplist dsoundcard device.description=DEP_Example_Soundcard` in order to get the output to display on `pavucontrol`.
|
||||
Using `pavucontrol` you should be able to redirect the output of an audio-producing app to DEP.
|
||||
Deregister from the plugin by running `pactl unload-module module-alsa-sink`.
|
||||
|
||||
Register the plugin as a PulseAudio input by running `pacmd load-module module-alsa-source source_name=dsoundcard device=dsoundcard format=s32le rate=48000` (adjust sample rate to match DEP sample rate).
|
||||
Sometimes you need to run `pacmd update-source-proplist dsoundcard device.description=DEP_Example_Soundcard` in order to get the output to display on pavucontrol.
|
||||
An example command to record audio from PulseAudio would be `ffmpeg -f pulse -i dsoundcard output.wav`.
|
||||
Deregister from the plugin by running `pactl unload-module module-alsa-source`.
|
||||
|
||||
NOTE: PulseAudio can get quite confused if you register the `dsoundcard` plugin before DEP is running, or if DEP stops before the plugin has been deregistered.
|
||||
Try to keep DEP running while the plugin is loaded.
|
||||
|
||||
NOTE: Due to the way dsoundcard works, it cannot be opened by PulseAudio for both input and output simultaneously. Please refer to the dsoundcard section in the appendix of the DEP Programmer's Guide
|
||||
for details on how to create another ALSA plugin to achieve this task.
|
||||
|
||||
## Source Code Compilation
|
||||
|
||||
Source code for the DEP example applications is in the `dep_examples/source` directory.
|
||||
To compile, GNU make, CMake and an appropriate toolchain are required. The build steps below will by default download an appropriate toolchain.
|
||||
|
||||
The provided binaries were compiled with `aarch64-buildroot-linux-gnu-gcc` and `alsa-lib 1.0.29`.
|
||||
|
||||
Steps to compile:
|
||||
|
||||
1. Compile example applications.
|
||||
|
||||
Generate make files from cmake. The following step configures the build to download and build a default version of the alsalib package. To skip the alsalib build and use a pre built/installed alsalib add the parameter `-DALSA_INSTALL_PATH=<alsa install path>` to the first cmake command below.
|
||||
```
|
||||
cd dep_examples && rm -rf build && mkdir -p build && cd build && cmake -DCMAKE_TOOLCHAIN_FILE=../source/toolchain.cmake ..
|
||||
```
|
||||
|
||||
Navigate into the `dep_examples/build` directory.
|
||||
To build all examples, call
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
To build specific example, first list the available building targets by calling
|
||||
```
|
||||
make help
|
||||
```
|
||||
|
||||
Then call make on the specific target, e.g. to build DepLoopback, call
|
||||
```
|
||||
make dep_example_loopback
|
||||
```
|
||||
|
||||
2. Binary output locations.
|
||||
|
||||
After successful compilation the applications are found in the following folders.
|
||||
|
||||
| Application | Location |
|
||||
| ------------ | ----------------------------------------------------------- |
|
||||
| loopback | `source/DepLoopback` |
|
||||
| ALSA Bridge | `source/DepAlsaBridge` |
|
||||
| dinfo | `source/dinfo` |
|
||||
| dplay | `source/dplay` |
|
||||
| drecord | `source/drecord` |
|
||||
| dtest | `source/dtest` |
|
||||
| dsoundcard | `source/dsoundcard.so` |
|
||||
|
||||
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/DepAlsaBridge
Executable file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/DepAlsaBridge
Executable file
Binary file not shown.
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/DepLoopback
Normal file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/DepLoopback
Normal file
Binary file not shown.
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/DepTiming
Normal file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/DepTiming
Normal file
Binary file not shown.
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/dinfo
Normal file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/dinfo
Normal file
Binary file not shown.
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/dplay
Normal file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/dplay
Normal file
Binary file not shown.
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/drecord
Executable file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/drecord
Executable file
Binary file not shown.
Binary file not shown.
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/dtest
Normal file
BIN
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/apps/dtest
Normal file
Binary file not shown.
@@ -0,0 +1,19 @@
|
||||
# Capabilities
|
||||
|
||||
For more details on capabilities, see the Programmer's guide.
|
||||
|
||||
## config.json
|
||||
|
||||
`config.json` is the standard OCI configuration file. It contains metadata that describes how to setup and run an OCI container. The syntax and format are described as part of the OCI run time specification which can be found at https://github.com/opencontainers/runtime-spec/blob/master/config.md. There are example `config.json` files in this example configuration directory - one for systems using CGroups V1 and one for CGroups V2. The config will need to be adjusted to be compatible with the end system it is used on - for example, some mounts may need their paths to be changed and some mounts may need to be removed or added.
|
||||
|
||||
## dante.json
|
||||
|
||||
`dante.json` is the DEP configuration file. It contains settings that control the Dante device behaviour and is described in more detail in the Programmer's Guide.
|
||||
There are a number of example `dante.json` files in this directory. These files can be used as a starting point for the final `dante.json`. The example `dante.json` files include
|
||||
- `dante.base_stereo.json`: 2x2 channel DEP device with a single network configuration.
|
||||
- `dante.base_stereo_redundant.json`: 2x2 channel DEP device with a redundant network configuration.
|
||||
- `dante.cpu_share.json`: 2x2 channel DEP device with an 80% share of a single CPU core.
|
||||
- `dante.hardware_clocking.json`: 2x2 channel DEP device with external hardware clocking support.
|
||||
- `dante.max.json`: 64x64 channel DEP device with a single network configuration.
|
||||
- `dante.switched_network_redundant.json`: 64x64 channel DEP device with a redundant network configuration and packets with DSA tags.
|
||||
- `dante.alsa_asrc.json`: 2x2 channel DEP device which uses ALSA ASRC to play and capture audio through ALSA. The ALSA configuration options will need to be adjusted based on the capabilities and name of your ALSA device.
|
||||
@@ -0,0 +1,284 @@
|
||||
{
|
||||
"ociVersion": "1.0.1",
|
||||
"process": {
|
||||
"terminal": false,
|
||||
"user": {
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
},
|
||||
"args": [
|
||||
"./dep_manager",
|
||||
"/dante_data/capability/dante.json"
|
||||
],
|
||||
"env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/dante",
|
||||
"TERM=xterm"
|
||||
],
|
||||
"cwd": "/dante",
|
||||
"capabilities": {
|
||||
"bounding": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"effective": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"inheritable": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"permitted": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"ambient": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
]
|
||||
},
|
||||
"rlimits": [
|
||||
{
|
||||
"type": "RLIMIT_NOFILE",
|
||||
"hard": 1024,
|
||||
"soft": 1024
|
||||
}
|
||||
],
|
||||
"noNewPrivileges": true
|
||||
},
|
||||
"root": {
|
||||
"path": "rootfs",
|
||||
"readonly": false
|
||||
},
|
||||
"hostname": "",
|
||||
"mounts": [
|
||||
{
|
||||
"destination": "/proc",
|
||||
"type": "proc",
|
||||
"source": "proc"
|
||||
},
|
||||
{
|
||||
"destination": "/var/run",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/run/dante",
|
||||
"type": "bind",
|
||||
"source": "/var/run/dante",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/lib/dbus/machine-id",
|
||||
"type": "bind",
|
||||
"source": "/var/lib/dbus/machine-id",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/pts",
|
||||
"type": "devpts",
|
||||
"source": "devpts",
|
||||
"options": ["nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/shm",
|
||||
"type": "bind",
|
||||
"source": "/dev/shm",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/snd",
|
||||
"type": "bind",
|
||||
"source": "/dev/snd",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/tmp",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/log",
|
||||
"type": "bind",
|
||||
"source": "/var/log",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/etc/machine-id",
|
||||
"type": "bind",
|
||||
"source": "/etc/machine-id",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/etc/resolv.conf",
|
||||
"type": "bind",
|
||||
"source": "/etc/resolv.conf",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/dante_data",
|
||||
"type": "bind",
|
||||
"source": "/opt/dep/dante_package/dante_data",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/dante_data/capability",
|
||||
"type": "bind",
|
||||
"source": "/opt/dep/dante_package/dante_data/capability",
|
||||
"options": ["bind", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/sys",
|
||||
"type": "sysfs",
|
||||
"source": "sysfs",
|
||||
"options": ["nosuid", "noexec", "nodev", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/sys/fs/cgroup",
|
||||
"type": "cgroup",
|
||||
"source": "cgroup",
|
||||
"options": ["nosuid", "noexec", "nodev", "relatime", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/usr/share/alsa/alsa.conf",
|
||||
"type": "bind",
|
||||
"source": "/usr/share/alsa/alsa.conf",
|
||||
"options": ["bind", "ro"]
|
||||
}
|
||||
],
|
||||
"linux": {
|
||||
"resources": {
|
||||
"devices": [
|
||||
{
|
||||
"allow": false,
|
||||
"access": "rwm"
|
||||
},
|
||||
{
|
||||
"allow": true,
|
||||
"type": "c",
|
||||
"major": 242,
|
||||
"minor": 0,
|
||||
"access": "rw"
|
||||
},
|
||||
{
|
||||
"allow": true,
|
||||
"type": "c",
|
||||
"major": 116,
|
||||
"access": "rw"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namespaces": [
|
||||
{ "type": "pid" },
|
||||
{ "type": "ipc" },
|
||||
{ "type": "mount" },
|
||||
{ "type": "uts" }
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"path": "/dev/ptp0",
|
||||
"type": "c",
|
||||
"major": 251,
|
||||
"minor": 0,
|
||||
"fileMode": 384,
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
}
|
||||
],
|
||||
"maskedPaths": [
|
||||
"/proc/kcore",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/proc/scsi"
|
||||
],
|
||||
"readonlyPaths": [
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
{
|
||||
"ociVersion": "1.0.1",
|
||||
"process": {
|
||||
"terminal": false,
|
||||
"user": {
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
},
|
||||
"args": [
|
||||
"./dep_manager",
|
||||
"/dante_data/capability/dante.json"
|
||||
],
|
||||
"env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/dante",
|
||||
"TERM=xterm"
|
||||
],
|
||||
"cwd": "/dante",
|
||||
"capabilities": {
|
||||
"bounding": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"effective": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"inheritable": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"permitted": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"ambient": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
]
|
||||
},
|
||||
"rlimits": [
|
||||
{
|
||||
"type": "RLIMIT_NOFILE",
|
||||
"hard": 1024,
|
||||
"soft": 1024
|
||||
}
|
||||
],
|
||||
"noNewPrivileges": true
|
||||
},
|
||||
"root": {
|
||||
"path": "rootfs",
|
||||
"readonly": false
|
||||
},
|
||||
"hostname": "",
|
||||
"mounts": [
|
||||
{
|
||||
"destination": "/proc",
|
||||
"type": "proc",
|
||||
"source": "proc"
|
||||
},
|
||||
{
|
||||
"destination": "/var/run",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/run/dante",
|
||||
"type": "bind",
|
||||
"source": "/var/run/dante",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/lib/dbus/machine-id",
|
||||
"type": "bind",
|
||||
"source": "/var/lib/dbus/machine-id",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/pts",
|
||||
"type": "devpts",
|
||||
"source": "devpts",
|
||||
"options": ["nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/shm",
|
||||
"type": "bind",
|
||||
"source": "/dev/shm",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/snd",
|
||||
"type": "bind",
|
||||
"source": "/dev/snd",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/tmp",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/log",
|
||||
"type": "bind",
|
||||
"source": "/var/log",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/etc/machine-id",
|
||||
"type": "bind",
|
||||
"source": "/etc/machine-id",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/etc/resolv.conf",
|
||||
"type": "bind",
|
||||
"source": "/etc/resolv.conf",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/dante_data",
|
||||
"type": "bind",
|
||||
"source": "/opt/dep/dante_package/dante_data",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/dante_data/capability",
|
||||
"type": "bind",
|
||||
"source": "/opt/dep/dante_package/dante_data/capability",
|
||||
"options": ["bind", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/sys",
|
||||
"type": "sysfs",
|
||||
"source": "sysfs",
|
||||
"options": ["nosuid", "noexec", "nodev", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/sys/fs/cgroup",
|
||||
"type": "cgroup2",
|
||||
"source": "cgroup2",
|
||||
"options": ["nosuid", "noexec", "nodev", "relatime", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/usr/share/alsa/alsa.conf",
|
||||
"type": "bind",
|
||||
"source": "/usr/share/alsa/alsa.conf",
|
||||
"options": ["bind", "ro"]
|
||||
}
|
||||
],
|
||||
"linux": {
|
||||
"cgroupsPath": "dante",
|
||||
"namespaces": [
|
||||
{ "type": "pid" },
|
||||
{ "type": "ipc" },
|
||||
{ "type": "mount" },
|
||||
{ "type": "uts" },
|
||||
{ "type": "cgroup" }
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"path": "/dev/ptp0",
|
||||
"type": "c",
|
||||
"major": 242,
|
||||
"minor": 0,
|
||||
"fileMode": 384,
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
}
|
||||
],
|
||||
"maskedPaths": [
|
||||
"/proc/kcore",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/proc/scsi"
|
||||
],
|
||||
"readonlyPaths": [
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"logDirectory" : "/var/log"
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 2,
|
||||
"rxChannels" : 2,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 2,
|
||||
"networkLatencyDefaultMs" : 5,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 1
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eno1"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"alsaAsrc":
|
||||
{
|
||||
"enableAlsaAsrc": true,
|
||||
"deviceConfigurations": [
|
||||
{
|
||||
"deviceIdentifier": "hw:0,0",
|
||||
"direction": "playback",
|
||||
"bitDepth": 16,
|
||||
"bufferSize": 192
|
||||
},
|
||||
{
|
||||
"deviceIdentifier": "hw:0,0",
|
||||
"direction": "capture",
|
||||
"bitDepth": 16,
|
||||
"bufferSize": 192
|
||||
}
|
||||
]
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"cgroupVersion": 2,
|
||||
"logDirectory" : "/var/log"
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 2,
|
||||
"rxChannels" : 2,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 2,
|
||||
"networkLatencyDefaultMs" : 5,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 1,
|
||||
"defaultChannelNamesFile": "/dante_data/capability/stereo_channel_names.json"
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eno1"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"cgroupVersion": 2,
|
||||
"logDirectory" : "/var/log"
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 2,
|
||||
"rxChannels" : 2,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 2,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 1
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eno1",
|
||||
"eno2"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"cgroupVersion": 2,
|
||||
"logDirectory" : "/var/log"
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 2,
|
||||
"rxChannels" : 2,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 2,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 1,
|
||||
"percentCpuShare" : 80
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eno1"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"cgroupVersion": 2,
|
||||
"logDirectory" : "/var/log",
|
||||
"maxNumLogs" : 6
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 2,
|
||||
"rxChannels" : 2,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 2,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 1
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eno1"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : true,
|
||||
"circuitName" : "Si5351B with MCP47CVB02",
|
||||
"circuitRevision" : 0,
|
||||
"i2cBus" : "/dev/i2c-1",
|
||||
"i2cAddr" : "0x62",
|
||||
"extClockInputDev" : "/dev/extclkin",
|
||||
"bitClocks" :
|
||||
[
|
||||
{
|
||||
"sampleRate": 48000,
|
||||
"tdmChannels": 16,
|
||||
"bitDepth": 32
|
||||
},
|
||||
{
|
||||
"sampleRate": 96000,
|
||||
"tdmChannels": 8,
|
||||
"bitDepth": 32
|
||||
},
|
||||
{
|
||||
"tdmChannels": 4,
|
||||
"bitDepth": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Schema for Dante Embedded Platform dante.json configuration file.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "Removes superfluous warning on the schema field introduced by additionalProperties = false."
|
||||
},
|
||||
"platform": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cgroupVersion": {
|
||||
"type": "integer",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [1, 2]
|
||||
}
|
||||
],
|
||||
"description": "Tells DEP which version of cgroups the system is configured for. Setting this value allows DEP to optimise the system more appropriately. If this value is not set at all then DEP will still function but possibly in a less optimal way."
|
||||
},
|
||||
"logDirectory": {
|
||||
"type": "string",
|
||||
"description": "Directory to write DEP log files. The directory must be available inside the container, DEP must have read/write access to the directory and the directory must exist before the DEP container is started."
|
||||
},
|
||||
"maxNumLogs": {
|
||||
"type": "integer",
|
||||
"default": 6,
|
||||
"description": "Maximum number of versions of each DEP log file to keep."
|
||||
},
|
||||
"maxLogSize": {
|
||||
"type": "integer",
|
||||
"default": 102400,
|
||||
"description": "Maximum number of bytes of each DEP log file."
|
||||
},
|
||||
"logLevel": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"Error",
|
||||
"Warning",
|
||||
"Notice",
|
||||
"Info",
|
||||
"Debug"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": "Warning",
|
||||
"description": "The minimum log level to write to the log files."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"logDirectory"
|
||||
]
|
||||
},
|
||||
"audio": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rxChannels": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 512,
|
||||
"description": "The number of receive channels. Actual number of available channels will be the minimum of this value and the licensed channels."
|
||||
},
|
||||
"txChannels": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 512,
|
||||
"description": "The number of transmit channels. Actual number of available channels will be the minimum of this value and the licensed channels."
|
||||
},
|
||||
"maxRxFlows": {
|
||||
"type": "integer",
|
||||
"description": "Maximum receive flows that DEP will allow. Default is 'MAX(2, (rxChannels + 1) / 2)'."
|
||||
},
|
||||
"maxTxFlows": {
|
||||
"type": "integer",
|
||||
"description": "Maximum transmit flows that DEP will allow. Default is 'MAX(2, (txChannels + 1) / 2)'."
|
||||
},
|
||||
"sampleRate": {
|
||||
"type": "integer",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": 48000,
|
||||
"description": "Default sample rate of DEP."
|
||||
},
|
||||
"availableSampleRates": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"contains": {
|
||||
"const": 48000
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "A list of sample rates that can be selected for the DEP device."
|
||||
},
|
||||
"samplesPerPeriod": {
|
||||
"type": "integer",
|
||||
"default": 16,
|
||||
"description": "The number of samples between audio period events (ticks)."
|
||||
},
|
||||
"periodsPerBuffer": {
|
||||
"type": "integer",
|
||||
"default": 3000,
|
||||
"description": "The number of periods in the buffer."
|
||||
},
|
||||
"networkLatencyMinMs": {
|
||||
"type": "integer",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": 2,
|
||||
"description": "The minimum latency in milliseconds (ms) that this device can support."
|
||||
},
|
||||
"networkLatencyDefaultMs": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 40,
|
||||
"default": 4,
|
||||
"description": "Default network latency in milliseconds (ms)."
|
||||
},
|
||||
"numDepCores": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "List of CPU core IDs DEP will run on."
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The number of CPU cores DEP will run on. DEP will run on the given number of consecutive cores starting from core 0"
|
||||
}
|
||||
],
|
||||
"description": "The CPU cores DEP will run on. The host system is responsible for ensuring the configured cores are isolated exclusively for DEP use."
|
||||
},
|
||||
"percentCpuShare": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
"default": 100,
|
||||
"description": "The share of CPU time DEP will be allocated when CPU time is under contention. NOTE: this setting has no effect when cgroups v2 is in use and will be deprecated soon."
|
||||
},
|
||||
"defaultEncoding": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "(DEPRECATED) The default native device encoding value. This field should no longer be used. The default should instead be specified as the first entry in supportedEncodings"
|
||||
},
|
||||
"supportedEncodings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
"PCM24"
|
||||
],
|
||||
"uniqueItems": true,
|
||||
"description": "A list of supported native device encoding values."
|
||||
},
|
||||
"aes67Supported": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether this device supports the AES67 protocol."
|
||||
},
|
||||
"channelGroupsFile": {
|
||||
"type": "string",
|
||||
"description": "The full path to a separate JSON file that specifies logical channel groupings."
|
||||
},
|
||||
"defaultChannelNamesFile": {
|
||||
"type": "string",
|
||||
"description": "The full path to a separate JSON file that specifies custom default channel names."
|
||||
},
|
||||
"perChannelEncodingsFile": {
|
||||
"type": "string",
|
||||
"description": "The full path to a separate JSON file that specifies per-channel encodings."
|
||||
},
|
||||
"enableSelfSubscription": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether the device self-subscription capability should be enabled."
|
||||
},
|
||||
"silenceHeadDelayMs" : {
|
||||
"type": "integer",
|
||||
"default": 20,
|
||||
"description": "DEP erases audio in the rx and tx audio buffer shortly after the network time for those frames passes. This controls the delay on this erasure measured in milliseconds."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"txChannels",
|
||||
"rxChannels",
|
||||
"availableSampleRates",
|
||||
"numDepCores"
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"interfaceMode": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"Switched",
|
||||
"Direct"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": "Direct",
|
||||
"description": "DEP network interface mode. Direct means connected to the network via a PHY; Switched means connected to the network via a switch."
|
||||
},
|
||||
"interfaces": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 2,
|
||||
"uniqueItems": true,
|
||||
"description": "List of network interface names or indexes DEP will use to connect to the Dante network."
|
||||
},
|
||||
"preferredLinkSpeed": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"LINK_SPEED_10G",
|
||||
"LINK_SPEED_1G",
|
||||
"LINK_SPEED_100M"
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": "LINK_SPEED_1G",
|
||||
"description": "The preferred link speed of the network interface/s used by DEP."
|
||||
},
|
||||
"webSocketPort": {
|
||||
"type": "integer",
|
||||
"minimum": 1024,
|
||||
"maximum": 65535,
|
||||
"description": "The websocket port used by DEP. If not set an ephemeral port is used."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"interfaces"
|
||||
]
|
||||
},
|
||||
"mdns": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"restrictInterfaces": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether to restrict mDNS advertisements to only the specified network interfaces."
|
||||
}
|
||||
}
|
||||
},
|
||||
"clock": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enableHwTimestamping": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"const": "v1"
|
||||
}
|
||||
],
|
||||
"default": false,
|
||||
"description": "Whether to use hardware packet timestamping at the Network Interface Card (NIC) level."
|
||||
},
|
||||
"dsaTaggedPackets": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether packets read from the network interface have a DSA tag attached."
|
||||
},
|
||||
"hardwareInterfaces": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{ "type": "string" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 2,
|
||||
"description": "List of network interface names or indexes that support hardware packet timestamping."
|
||||
},
|
||||
"followerOnly": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether the device should be in follower only mode. When true, DEP cannot become clock leader."
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"enableHwTimestamping": {
|
||||
"anyOf": [
|
||||
{ "const": true },
|
||||
{ "const": "v1" }
|
||||
]
|
||||
},
|
||||
"dsaTaggedPackets": { "const": true }
|
||||
},
|
||||
"required": [
|
||||
"enableHwTimestamping", "dsaTaggedPackets"
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"hardwareInterfaces"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hardwareClock": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"useHwClock": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enable use of clocking hardware."
|
||||
},
|
||||
"circuitName": {
|
||||
"type": "string",
|
||||
"description": "Name of the clock generator and adjustment circuitry. This field must be one of the supported strings in a DEP release."
|
||||
},
|
||||
"circuitRevision": {
|
||||
"type": "integer",
|
||||
"description": "An integer representing the circuit revision to use. This field must correspond to a supported revision and circuit in a DEP release."
|
||||
},
|
||||
"i2cBus": {
|
||||
"type": "string",
|
||||
"default": "/dev/i2c-0",
|
||||
"description": "The I2C bus device to use to communicate with the clock circuitry. If not present, the first I2C bus device '/dev/i2c-0' is used.",
|
||||
"pattern": "^\\/dev"
|
||||
},
|
||||
"i2cAddr": {
|
||||
"type": "string",
|
||||
"description": "The I2C address configurable for a circuit. If not present, the default addresses for the circuit are used."
|
||||
},
|
||||
"extClockInputDev": {
|
||||
"type": "string",
|
||||
"default": "/dev/extclkin",
|
||||
"description": "The device path to the external clock input driver used in the clock feedback algorithm. If not present, this field defaults to '/dev/extclkin'.",
|
||||
"pattern": "^\\/dev"
|
||||
},
|
||||
"bitClocks": {
|
||||
"type": "array",
|
||||
"description": "An array of mappings between the sample rate and bit clock configurations.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sampleRate": {
|
||||
"type": "integer",
|
||||
"description": "Sample rate of the mapping."
|
||||
},
|
||||
"tdmChannels": {
|
||||
"type": "integer",
|
||||
"description": "Number of TDM channels."
|
||||
},
|
||||
"bitDepth": {
|
||||
"type": "integer",
|
||||
"description": "Bit depth of mapping."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tdmChannels",
|
||||
"bitDepth"
|
||||
]
|
||||
}
|
||||
},
|
||||
"loadCapacitance": {
|
||||
"type": "integer",
|
||||
"default": -1,
|
||||
"description": "Value for the internal load capacitance in pf to set for the clock circuit. If not set or set to a negative number the circuit’s default will be used. The default and set of valid values are clock circuit specific. For DEP supported si5351b based clock circuits the default load capacitance is 10pF and the set of valid values for this field are 6, 8 and 10."
|
||||
}
|
||||
},
|
||||
"if": {
|
||||
"properties": {
|
||||
"useHwClock": { "const": true }
|
||||
},
|
||||
"required": ["useHwClock"]
|
||||
},
|
||||
"then": {
|
||||
"required": ["circuitName"]
|
||||
}
|
||||
},
|
||||
"hostcpu": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enableDdp": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enable the 'Dante Device Protocol'."
|
||||
}
|
||||
},
|
||||
"required": ["enableDdp"]
|
||||
},
|
||||
"alsaAsrc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enableAlsaAsrc": {
|
||||
"type": "boolean",
|
||||
"description": "Set to true to enable ALSA ASRC and false to disable."
|
||||
},
|
||||
"txLatencySamples": {
|
||||
"type": "integer",
|
||||
"default": 48,
|
||||
"description": "Offset used by ASRC when writing audio to the DEP TX buffer measured in samples."
|
||||
},
|
||||
"pollMode": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, ALSA ASRC will not wait on the DEP shared memory semaphore and will instead poll the memory to determine when more data is available."
|
||||
},
|
||||
"schedulingPriority": {
|
||||
"type": "integer",
|
||||
"default": 70,
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"description": "The real-time scheduling priority to run the ALSA ASRC application at."
|
||||
},
|
||||
"cpuAffinity": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "The CPU core ID which should be exclusively assigned to ASRC. NOTE: for optimal performance, ensure that the selected CPU core ID is not already listed in the numDepCores value"
|
||||
},
|
||||
"deviceConfigurations": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "List of devices to open. This key is required if Asrc is enabled.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"description": "Configuration options for each ALSA device to be opened",
|
||||
"properties": {
|
||||
"deviceIdentifier": {
|
||||
"type": "string",
|
||||
"description": "The ALSA device identifier for this device, e.g. \"hw:1,0\" or \"hw:CARD=sofhdadsp,DEV=0\"."
|
||||
},
|
||||
"direction": {
|
||||
"enum": [
|
||||
"playback",
|
||||
"capture"
|
||||
],
|
||||
"description": "The direction to open the ALSA device in. Must be \"capture\" or \"playback\"."
|
||||
},
|
||||
"bitDepth": {
|
||||
"enum": [
|
||||
16,
|
||||
24,
|
||||
32
|
||||
],
|
||||
"default": 24,
|
||||
"description": "The PCM bit depth to open the ALSA device with. The device will be opened with the first format it claims to support which is that depth. Typically this maps 8 to S8, 16 to S16_LE, 24 to S24_LE and 32 to S32_LE."
|
||||
},
|
||||
"bitWidthOverride": {
|
||||
"enum": [
|
||||
16,
|
||||
24,
|
||||
32
|
||||
],
|
||||
"description": "The number of bits each sample is packed into. For example, \"bitDepth\": 24, \"bitWidthOverride\": 24 is equivalent to S24_3LE, so the application writes samples aligned to 3 bytes. This overrides the alignment of the selected format. So if, for example, a device only claims to support S24_LE (24 bits aligned to 32 bit words) but actually writes 24 bit samples aligned to 24 bits, this setting can account for this."
|
||||
},
|
||||
"alsaFormat": {
|
||||
"enum": [
|
||||
"S16_LE",
|
||||
"S24_LE",
|
||||
"S32_LE",
|
||||
"FLOAT_LE",
|
||||
"S24_3LE",
|
||||
"S16",
|
||||
"S24",
|
||||
"S32",
|
||||
"FLOAT"
|
||||
],
|
||||
"description": "The specific ALSA format name to open the device with. Incompatible with bitDepth."
|
||||
},
|
||||
"numOpenChannels": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 2,
|
||||
"description": "The number of channels to open on the ALSA device."
|
||||
},
|
||||
"alsaChannelRange": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+-[0-9]+$",
|
||||
"description": "The block of ALSA channels to use. Can only be provided if numOpenChannels is specified. String of the form \"X-Y\" where X and Y are zero indexed channel numbers, specifying the block [X,Y] inclusive. Defaults to 0-(numOpenChannels - 1)"
|
||||
},
|
||||
"danteChannelRange": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+-[0-9]+$",
|
||||
"description": "The block of DEP channels this device will read from or write to. Can only be provided if numOpenChannels is specified. String of the form \"X-Y\" where X and Y are zero indexed channel numbers, specifying the block [X,Y] inclusive. Defaults to 0-(numOpenChannels-1)"
|
||||
},
|
||||
"gain": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Positive or negative gain in dB to apply to the audio for this device."
|
||||
},
|
||||
"bufferSize": {
|
||||
"type": "integer",
|
||||
"default": 64,
|
||||
"description": "Size of the ALSA buffer to request the device to open with. Should never be less than 2 DEP periods or 2 ALSA periods. Note that the exact numbers for bufferSize and samplesPerPeriod are merely a request, and individual ALSA drivers are entitled to find other nearby valid values, if necessary."
|
||||
},
|
||||
"samplesPerPeriod": {
|
||||
"type": "integer",
|
||||
"default": 8,
|
||||
"description": "Samples per period to request the ALSA device to open with. Note that the exact numbers for bufferSize and samplesPerPeriod are merely a request, and individual ALSA drivers are entitled to find other nearby valid values, if necessary."
|
||||
},
|
||||
"latency": {
|
||||
"type": "integer",
|
||||
"description": "By default, ASRC maintains the ALSA buffer at its halfway point - which corresponds to the insertion latency of ASRC. This key overrides this behaviour, specifying the target buffer point in samples."
|
||||
},
|
||||
"readWriteiBuffer": {
|
||||
"type": "integer",
|
||||
"description": "For drivers that don't support MMAP (memory-mapped) buffer operations, the application can emulate the memory mapping internally by inserting an additional buffer and services that through ALSA R/W calls. If this value is >0, it specifies the size of this additional buffer."
|
||||
},
|
||||
"forceArtificialAudioTime": {
|
||||
"type": "boolean",
|
||||
"description": "This setting provides an override for drivers which don't provide correct audio timestamps. If this is set to true, ASRC overrides the audio time with an artificial one calculated from sample counts."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"deviceIdentifier",
|
||||
"direction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The alsaFormat option is incompatible with the bitDepth and bitWidthOverride option",
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{"required": ["bitDepth"]},
|
||||
{"required": ["bitWidthOverride"]}
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"not": {"required": ["alsaFormat"]}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "alsaChannelRange and danteChannelRange can each only be defined if numOpenChannels is specified",
|
||||
"if": {
|
||||
"anyOf": [
|
||||
{"required": ["alsaChannelRange"]},
|
||||
{"required": ["danteChannelRange"]}
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"required": ["numOpenChannels"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enableAlsaAsrc"
|
||||
]
|
||||
},
|
||||
"product": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"manfId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"description": "The ID of the device manufacturer. This value is assigned to the manufacturer by Audinate when signing up as a DEP licensee."
|
||||
},
|
||||
"manfName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 31,
|
||||
"description": "Human-readable manufacturer name that users will see in Dante Controller."
|
||||
},
|
||||
"modelId": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 8,
|
||||
"description": "The device model ID, up to 8 characters long and unique for each product type produced by a manufacturer."
|
||||
},
|
||||
"modelName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 31,
|
||||
"description": "Human-readable model name that users will see in Dante Controller."
|
||||
},
|
||||
"modelVersion": {
|
||||
"type": "object",
|
||||
"description": "3-part version number of the DEP device, this will be shown in Dante Controller.",
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"description": "Product version major number."
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"description": "Product version minor number."
|
||||
},
|
||||
"bugfix": {
|
||||
"type": "integer",
|
||||
"description": "Product version bugfix number."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"bugfix"
|
||||
]
|
||||
},
|
||||
"modelVersionString": {
|
||||
"type": "string",
|
||||
"description": "An arbitrary string that overrides the 'modelVersion'. If not set the 'modelVersion' fields will be used to construct a model version string.",
|
||||
"minLength": 1,
|
||||
"maxLength": 31
|
||||
},
|
||||
"devicePrefix": {
|
||||
"type": "string",
|
||||
"default": "DEP",
|
||||
"minLength": 1,
|
||||
"maxLength": 24,
|
||||
"pattern": "^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,23})?$",
|
||||
"description": "Dante device name prefix. Up to 24 characters, legal characters are A-Z, a-z, 0-9, and '-' ('-' cannot be the first character)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"manfId",
|
||||
"manfName",
|
||||
"modelId",
|
||||
"modelName",
|
||||
"modelVersion"
|
||||
]
|
||||
},
|
||||
"trialMode": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Set to true to start the container in 'Trial Mode'. If excluded or false, DEP will require activation."
|
||||
},
|
||||
"misc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enableIdentify": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Set to true to enable the device 'Identify' function and false to disable."
|
||||
}
|
||||
}
|
||||
},
|
||||
"ddhi": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enable": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Set to true to enable Dante Device Host Interface (DDHI) and false to disable. All other properties in the ddhi object are only used if this value is true."
|
||||
},
|
||||
"clientRpcs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of DDHI RPCs supported by the platform DDHI client(s)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"platform",
|
||||
"audio",
|
||||
"network",
|
||||
"product"
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"cgroupVersion": 2,
|
||||
"logDirectory" : "/var/log",
|
||||
"maxNumLogs" : 6
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 64,
|
||||
"rxChannels" : 64,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 1,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 3
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eno1"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"cgroupVersion": 2,
|
||||
"logDirectory" : "/var/log",
|
||||
"maxNumLogs" : 6
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 2,
|
||||
"rxChannels" : 2,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 2,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 1
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Switched",
|
||||
"interfaces" :
|
||||
[
|
||||
"lan1",
|
||||
"lan2"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : true,
|
||||
"dsaTaggedPackets" : true,
|
||||
"hardwareInterfaces" :
|
||||
[
|
||||
"eth0",
|
||||
"eth0"
|
||||
]
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 9,
|
||||
"minor" : 9,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Schema for Dante Embedded Platform Default Channel Names JSON configuration file.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "Removes superfluous warning on the schema field introduced by additionalProperties = false."
|
||||
},
|
||||
"rxChannelDefaultNames": {
|
||||
"type": "array",
|
||||
"description": "Array of default receive (rx) channel names. Channel numbers cannot be repeated, and no two rx channels should have the same defaultName.",
|
||||
"items":
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"channel": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "A channel number, not less than 1. Channels cannot be specified more than once. Values greater than the configured Rx channel count are ignored."
|
||||
},
|
||||
"defaultName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 31,
|
||||
"pattern": "^[^@.=]{1,31}$",
|
||||
"description": "Rx channel name string up to 31 characters. A name may use any character except = . @. A Rx channel name must be unique on the device."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"channel",
|
||||
"defaultName"
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"txChannelDefaultNames": {
|
||||
"type": "array",
|
||||
"description": "Array of default transmit (tx) channel names. Channel numbers cannot be repeated, and no two tx channels should have the same defaultName.",
|
||||
"items":
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"channel": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"description": "A channel number, not less than 1. Channels cannot be specified more than once. Values greater than the configured Tx channel count are ignored."
|
||||
},
|
||||
"defaultName": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 31,
|
||||
"pattern": "^[^@.=]{1,31}$",
|
||||
"description": "Tx channel name string up to 31 characters. A name may use any character except = . @. A Tx channel name must be unique on the device."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"channel",
|
||||
"defaultName"
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [ "rxChannelDefaultNames" ]
|
||||
},
|
||||
{
|
||||
"required": [ "txChannelDefaultNames" ]
|
||||
}
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Schema for Dante Embedded Platform Per-Channel Encodings JSON configuration file.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "Removes superfluous warning on the schema field introduced by additionalProperties = false."
|
||||
},
|
||||
"defaultEncoding": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "The default encoding for any Tx/Rx channels not explicitly assigned their own encoding."
|
||||
},
|
||||
"txChannelEncodings": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"description": "Array of encodings assigned to specific Tx channel numbers. Channel numbers cannot overlap or be duplicated inside a 'channels' string, or between array entries.",
|
||||
"items":
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"channels": {
|
||||
"type": "string",
|
||||
"pattern": "^(([1-9][0-9]*)(-[1-9][0-9]*)?)(,([1-9][0-9]*)(-[1-9][0-9]*)?)*$",
|
||||
"description": "A string of one or more values, separated by commas (no spaces permitted). Each value can be a single channel number, or a dash-separated channel range where the second value must be greater than the first."
|
||||
},
|
||||
"encoding": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "The encoding assigned to the specified channel(s)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"channels",
|
||||
"encoding"
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"rxChannelEncodings": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"description": "Array of encodings assigned to specific Rx channel numbers. Channel numbers cannot overlap or be duplicated inside a 'channels' string, or between array entries.",
|
||||
"items":
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"channels": {
|
||||
"type": "string",
|
||||
"pattern": "^(([1-9][0-9]*)(-[1-9][0-9]*)?)(,([1-9][0-9]*)(-[1-9][0-9]*)?)*$",
|
||||
"description": "A string of one or more values, separated by commas (no spaces permitted). Each value can be a single channel number, or a dash-separated channel range where the second value must be greater than the first."
|
||||
},
|
||||
"encoding": {
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "The encoding assigned to the specified channel(s)."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"channels",
|
||||
"encoding"
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"defaultEncoding"
|
||||
],
|
||||
"additionalProperties" : false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "./default_channel_names.json_schema.json",
|
||||
"rxChannelDefaultNames" : [
|
||||
{
|
||||
"channel" : 1,
|
||||
"defaultName" : "Stereo In:Left"
|
||||
},
|
||||
{
|
||||
"channel" : 2,
|
||||
"defaultName" : "Stereo In:Right"
|
||||
}
|
||||
],
|
||||
"txChannelDefaultNames" : [
|
||||
{
|
||||
"channel" : 1,
|
||||
"defaultName" : "Stereo Out:Left"
|
||||
},
|
||||
{
|
||||
"channel" : 2,
|
||||
"defaultName" : "Stereo Out:Right"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"ociVersion": "1.0.1",
|
||||
"process": {
|
||||
"terminal": false,
|
||||
"user": {
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
},
|
||||
"args": [
|
||||
"./dep_manager",
|
||||
"/dante_data/capability/dante.json"
|
||||
],
|
||||
"env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/dante",
|
||||
"TERM=xterm"
|
||||
],
|
||||
"cwd": "/dante",
|
||||
"capabilities": {
|
||||
"bounding": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"effective": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"inheritable": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"permitted": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
],
|
||||
"ambient": [
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FSETID",
|
||||
"CAP_FOWNER",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETUID",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_KILL",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_NET_ADMIN"
|
||||
]
|
||||
},
|
||||
"rlimits": [
|
||||
{
|
||||
"type": "RLIMIT_NOFILE",
|
||||
"hard": 1024,
|
||||
"soft": 1024
|
||||
}
|
||||
],
|
||||
"noNewPrivileges": true
|
||||
},
|
||||
"root": {
|
||||
"path": "rootfs",
|
||||
"readonly": false
|
||||
},
|
||||
"hostname": "",
|
||||
"mounts": [
|
||||
{
|
||||
"destination": "/proc",
|
||||
"type": "proc",
|
||||
"source": "proc"
|
||||
},
|
||||
{
|
||||
"destination": "/var/run",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/run/dante",
|
||||
"type": "bind",
|
||||
"source": "/var/run/dante",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/lib/dbus/machine-id",
|
||||
"type": "bind",
|
||||
"source": "/var/lib/dbus/machine-id",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/pts",
|
||||
"type": "devpts",
|
||||
"source": "devpts",
|
||||
"options": ["nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/shm",
|
||||
"type": "bind",
|
||||
"source": "/dev/shm",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/dev/snd",
|
||||
"type": "bind",
|
||||
"source": "/dev/snd",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/tmp",
|
||||
"type": "tmpfs",
|
||||
"source": "tmpfs",
|
||||
"options": ["nosuid", "strictatime", "mode=755", "size=65536k"]
|
||||
},
|
||||
{
|
||||
"destination": "/var/log",
|
||||
"type": "bind",
|
||||
"source": "/var/log",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/etc/machine-id",
|
||||
"type": "bind",
|
||||
"source": "/etc/machine-id",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/etc/resolv.conf",
|
||||
"type": "bind",
|
||||
"source": "/etc/resolv.conf",
|
||||
"options": ["ro", "rbind", "rprivate", "nosuid", "noexec", "nodev"]
|
||||
},
|
||||
{
|
||||
"destination": "/dante_data",
|
||||
"type": "bind",
|
||||
"source": "/opt/dep/dante_package/dante_data",
|
||||
"options": ["bind", "rw"]
|
||||
},
|
||||
{
|
||||
"destination": "/dante_data/capability",
|
||||
"type": "bind",
|
||||
"source": "/opt/dep/dante_package/dante_data/capability",
|
||||
"options": ["bind", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/sys",
|
||||
"type": "sysfs",
|
||||
"source": "sysfs",
|
||||
"options": ["nosuid", "noexec", "nodev", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/sys/fs/cgroup",
|
||||
"type": "cgroup2",
|
||||
"source": "cgroup2",
|
||||
"options": ["nosuid", "noexec", "nodev", "relatime", "ro"]
|
||||
},
|
||||
{
|
||||
"destination": "/usr/share/alsa/alsa.conf",
|
||||
"type": "bind",
|
||||
"source": "/usr/share/alsa/alsa.conf",
|
||||
"options": ["bind", "ro"]
|
||||
}
|
||||
],
|
||||
"linux": {
|
||||
"cgroupsPath": "dante",
|
||||
"namespaces": [
|
||||
{ "type": "pid" },
|
||||
{ "type": "ipc" },
|
||||
{ "type": "mount" },
|
||||
{ "type": "uts" },
|
||||
{ "type": "cgroup" }
|
||||
],
|
||||
"devices": [
|
||||
{
|
||||
"path": "/dev/ptp0",
|
||||
"type": "c",
|
||||
"major": 242,
|
||||
"minor": 0,
|
||||
"fileMode": 384,
|
||||
"uid": 0,
|
||||
"gid": 0
|
||||
}
|
||||
],
|
||||
"maskedPaths": [
|
||||
"/proc/kcore",
|
||||
"/proc/latency_stats",
|
||||
"/proc/timer_list",
|
||||
"/proc/timer_stats",
|
||||
"/proc/sched_debug",
|
||||
"/proc/scsi"
|
||||
],
|
||||
"readonlyPaths": [
|
||||
"/proc/asound",
|
||||
"/proc/bus",
|
||||
"/proc/fs",
|
||||
"/proc/irq",
|
||||
"/proc/sys",
|
||||
"/proc/sysrq-trigger"
|
||||
],
|
||||
"resources": {
|
||||
"devices": [
|
||||
{
|
||||
"allow": true,
|
||||
"type": "c",
|
||||
"major": 116,
|
||||
"access": "rw"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"$schema": "./dante.json_schema.json",
|
||||
"platform":
|
||||
{
|
||||
"logDirectory" : "/var/log",
|
||||
"maxNumLogs" : 3,
|
||||
"cgroupVersion" : 2
|
||||
},
|
||||
"audio" :
|
||||
{
|
||||
"txChannels" : 8,
|
||||
"maxTxFlows" : 8,
|
||||
"rxChannels" : 8,
|
||||
"maxRxFlows" : 8,
|
||||
"sampleRate" : 48000,
|
||||
"availableSampleRates" :
|
||||
[
|
||||
44100,
|
||||
48000,
|
||||
88200,
|
||||
96000
|
||||
],
|
||||
"samplesPerPeriod" : 16,
|
||||
"periodsPerBuffer" : 3000,
|
||||
"networkLatencyMinMs" : 1,
|
||||
"supportedEncodings" :
|
||||
[
|
||||
"PCM16",
|
||||
"PCM24",
|
||||
"PCM32"
|
||||
],
|
||||
"defaultEncoding" : "PCM24",
|
||||
"numDepCores" : 0,
|
||||
"aes67Supported" : true
|
||||
},
|
||||
"network" :
|
||||
{
|
||||
"interfaceMode" : "Direct",
|
||||
"interfaces" :
|
||||
[
|
||||
"eth0"
|
||||
],
|
||||
"preferredLinkSpeed" : "LINK_SPEED_1G"
|
||||
},
|
||||
"clock" :
|
||||
{
|
||||
"enableHwTimestamping" : false
|
||||
},
|
||||
"hardwareClock" :
|
||||
{
|
||||
"useHwClock" : false
|
||||
},
|
||||
"hostcpu" :
|
||||
{
|
||||
"enableDdp" : false
|
||||
},
|
||||
"alsaAsrc":
|
||||
{
|
||||
"enableAlsaAsrc": true,
|
||||
"deviceConfigurations": [
|
||||
{
|
||||
"deviceIdentifier": "hw:0,0",
|
||||
"direction": "playback",
|
||||
"bitDepth": 16,
|
||||
"bufferSize": 48000
|
||||
}
|
||||
]
|
||||
},
|
||||
"product" :
|
||||
{
|
||||
"manfId" : "Audinate",
|
||||
"manfName" : "Audinate Pty Ltd",
|
||||
"modelId" : "OEMDEP",
|
||||
"modelName" : "RPi - Linux Dante Embedded Platform",
|
||||
"modelVersion" :
|
||||
{
|
||||
"major" : 1,
|
||||
"minor" : 0,
|
||||
"bugfix" : 99
|
||||
},
|
||||
"devicePrefix" : "DEP"
|
||||
},
|
||||
"trialmode" : true
|
||||
}
|
||||
177
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/RingBuffer.hpp
vendored
Normal file
177
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/RingBuffer.hpp
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright (c) 2014, The Cinder Project
|
||||
This code is intended to be used with the Cinder C++ library, http://libcinder.org
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
|
||||
the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and
|
||||
the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
|
||||
the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
//! \brief Ringbuffer (aka circular buffer) data structure for use in concurrent audio scenarios.
|
||||
//!
|
||||
//! Other than minor modifications, this ringbuffer is a copy of Tim Blechmann's fine work, found as the base
|
||||
//! structure of boost::lockfree::spsc_queue (ringbuffer_base). Whereas the boost::lockfree data structures
|
||||
//! are meant for a wide range of applications / archs, this version specifically caters to audio processing.
|
||||
//!
|
||||
//! The implementation remains lock-free and thread-safe within a single write thread / single read thread context.
|
||||
//!
|
||||
//! \note \a T must be POD.
|
||||
template <typename T>
|
||||
class RingBufferT {
|
||||
public:
|
||||
//! Constructs a RingBufferT with size = 0
|
||||
RingBufferT() : mData( nullptr ), mAllocatedSize( 0 ), mWriteIndex( 0 ), mReadIndex( 0 ) {}
|
||||
//! Constructs a RingBufferT with \a count maximum elements.
|
||||
RingBufferT( size_t count ) : mAllocatedSize( 0 )
|
||||
{
|
||||
resize( count );
|
||||
}
|
||||
|
||||
RingBufferT( RingBufferT &&other )
|
||||
: mData( other.mData ), mAllocatedSize( other.mAllocatedSize ), mWriteIndex( 0 ), mReadIndex( 0 )
|
||||
{
|
||||
other.mData = nullptr;
|
||||
other.mAllocatedSize = 0;
|
||||
}
|
||||
|
||||
~RingBufferT()
|
||||
{
|
||||
if( mData )
|
||||
free( mData );
|
||||
}
|
||||
//! Resizes the container to contain \a count maximum elements. Invalidates the internal buffer and resets read / write indices to 0. \note Must be synchronized with both read and write threads.
|
||||
void resize( size_t count )
|
||||
{
|
||||
size_t allocatedSize = count + 1; // one bin is used to distinguish between the read and write indices when full.
|
||||
|
||||
if( mAllocatedSize )
|
||||
mData = (T *)::realloc( mData, allocatedSize * sizeof( T ) );
|
||||
else
|
||||
mData = (T *)::calloc( allocatedSize, sizeof( T ) );
|
||||
|
||||
mAllocatedSize = allocatedSize;
|
||||
clear();
|
||||
}
|
||||
//! Invalidates the internal buffer and resets read / write indices to 0. \note Must be synchronized with both read and write threads.
|
||||
void clear()
|
||||
{
|
||||
mWriteIndex = 0;
|
||||
mReadIndex = 0;
|
||||
}
|
||||
//! Returns the maximum number of elements.
|
||||
size_t getSize() const
|
||||
{
|
||||
return mAllocatedSize - 1;
|
||||
}
|
||||
//! Returns the number of elements available for wrtiing. \note Only safe to call from the write thread.
|
||||
size_t getAvailableWrite() const
|
||||
{
|
||||
return getAvailableWrite( mWriteIndex, mReadIndex );
|
||||
}
|
||||
//! Returns the number of elements available for wrtiing. \note Only safe to call from the read thread.
|
||||
size_t getAvailableRead() const
|
||||
{
|
||||
return getAvailableRead( mWriteIndex, mReadIndex );
|
||||
}
|
||||
|
||||
//! \brief Writes \a count elements into the internal buffer from \a array. \return `true` if all elements were successfully written, or `false` otherwise.
|
||||
//!
|
||||
//! \note only safe to call from the write thread.
|
||||
//! TODO: consider renaming this to writeAll / readAll, and having generic read / write that just does as much as it can
|
||||
bool write( const T *array, size_t count )
|
||||
{
|
||||
const size_t writeIndex = mWriteIndex.load( std::memory_order_relaxed );
|
||||
const size_t readIndex = mReadIndex.load( std::memory_order_acquire );
|
||||
|
||||
if( count > getAvailableWrite( writeIndex, readIndex ) )
|
||||
return false;
|
||||
|
||||
size_t writeIndexAfter = writeIndex + count;
|
||||
|
||||
if( writeIndex + count > mAllocatedSize ) {
|
||||
size_t countA = mAllocatedSize - writeIndex;
|
||||
size_t countB = count - countA;
|
||||
|
||||
std::memcpy( mData + writeIndex, array, countA * sizeof( T ) );
|
||||
std::memcpy( mData, array + countA, countB * sizeof( T ) );
|
||||
writeIndexAfter -= mAllocatedSize;
|
||||
}
|
||||
else {
|
||||
std::memcpy( mData + writeIndex, array, count * sizeof( T ) );
|
||||
if( writeIndexAfter == mAllocatedSize )
|
||||
writeIndexAfter = 0;
|
||||
}
|
||||
|
||||
mWriteIndex.store( writeIndexAfter, std::memory_order_release );
|
||||
return true;
|
||||
}
|
||||
//! \brief Reads \a count elements from the internal buffer into \a array. \return `true` if all elements were successfully read, or `false` otherwise.
|
||||
//!
|
||||
//! \note only safe to call from the read thread.
|
||||
bool read( T *array, size_t count )
|
||||
{
|
||||
const size_t writeIndex = mWriteIndex.load( std::memory_order_acquire );
|
||||
const size_t readIndex = mReadIndex.load( std::memory_order_relaxed );
|
||||
|
||||
if( count > getAvailableRead( writeIndex, readIndex ) )
|
||||
return false;
|
||||
|
||||
size_t readIndexAfter = readIndex + count;
|
||||
|
||||
if( readIndex + count > mAllocatedSize ) {
|
||||
size_t countA = mAllocatedSize - readIndex;
|
||||
size_t countB = count - countA;
|
||||
|
||||
std::memcpy( array, mData + readIndex, countA * sizeof( T ) );
|
||||
std::memcpy( array + countA, mData, countB * sizeof( T ) );
|
||||
|
||||
readIndexAfter -= mAllocatedSize;
|
||||
}
|
||||
else {
|
||||
std::memcpy( array, mData + readIndex, count * sizeof( T ) );
|
||||
if( readIndexAfter == mAllocatedSize )
|
||||
readIndexAfter = 0;
|
||||
}
|
||||
|
||||
mReadIndex.store( readIndexAfter, std::memory_order_release );
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t getAvailableWrite( size_t writeIndex, size_t readIndex ) const
|
||||
{
|
||||
size_t result = readIndex - writeIndex - 1;
|
||||
if( writeIndex >= readIndex )
|
||||
result += mAllocatedSize;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t getAvailableRead( size_t writeIndex, size_t readIndex ) const
|
||||
{
|
||||
if( writeIndex >= readIndex )
|
||||
return writeIndex - readIndex;
|
||||
|
||||
return writeIndex + mAllocatedSize - readIndex;
|
||||
}
|
||||
|
||||
|
||||
T *mData;
|
||||
size_t mAllocatedSize;
|
||||
std::atomic<size_t> mWriteIndex, mReadIndex;
|
||||
};
|
||||
12188
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_flac.h
vendored
Normal file
12188
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_flac.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4749
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_mp3.h
vendored
Normal file
4749
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_mp3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6434
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_wav.h
vendored
Normal file
6434
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_wav.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
101
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/pink.hpp
vendored
Normal file
101
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/pink.hpp
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Original code source: https://www.firstpr.com.au/dsp/pink-noise/phil_burk_19990905_patest_pink.c
|
||||
/*
|
||||
patest_pink.c
|
||||
|
||||
Generate Pink Noise using Gardner method.
|
||||
Optimization suggested by James McCartney uses a tree
|
||||
to select which random value to replace.
|
||||
|
||||
x x x x x x x x x x x x x x x x
|
||||
x x x x x x x x
|
||||
x x x x
|
||||
x x
|
||||
x
|
||||
|
||||
Tree is generated by counting trailing zeros in an increasing index.
|
||||
When the index is zero, no random number is selected.
|
||||
|
||||
This program uses the Portable Audio library which is under development.
|
||||
For more information see: http://www.audiomulch.com/portaudio/
|
||||
|
||||
Author: Phil Burk, http://www.softsynth.com
|
||||
|
||||
Revision History:
|
||||
|
||||
Copyleft 1999 Phil Burk - No rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
static unsigned long generate_random_number(void)
|
||||
{
|
||||
static unsigned long rand_seed = 22222;
|
||||
rand_seed = (rand_seed * 196314165) + 907633515;
|
||||
return rand_seed;
|
||||
}
|
||||
|
||||
#define PINK_MAX_RANDOM_ROWS (30)
|
||||
#define PINK_RANDOM_BITS (24)
|
||||
#define PINK_RANDOM_SHIFT ((sizeof(long)*8)-PINK_RANDOM_BITS)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
long rows[PINK_MAX_RANDOM_ROWS];
|
||||
long running_sum;
|
||||
int index;
|
||||
int index_mask;
|
||||
float scalar;
|
||||
} PinkNoise;
|
||||
|
||||
PinkNoise pink;
|
||||
|
||||
void init_pink_noise()
|
||||
{
|
||||
pink.index = 0;
|
||||
pink.index_mask = (1<<PINK_MAX_RANDOM_ROWS) - 1;
|
||||
long pmax = (PINK_MAX_RANDOM_ROWS + 1) * (1<<(PINK_RANDOM_BITS-1));
|
||||
pink.scalar = 1.0f / pmax;
|
||||
for(int i=0; i < PINK_MAX_RANDOM_ROWS; i++ ) pink.rows[i] = 0;
|
||||
pink.running_sum = 0;
|
||||
}
|
||||
|
||||
float generate_pink_noise()
|
||||
{
|
||||
long new_random;
|
||||
long sum;
|
||||
float output;
|
||||
|
||||
pink.index = (pink.index + 1) & pink.index_mask;
|
||||
|
||||
if( pink.index != 0 )
|
||||
{
|
||||
|
||||
int num_zeros = 0;
|
||||
int n = pink.index;
|
||||
while( (n & 1) == 0 )
|
||||
{
|
||||
n = n >> 1;
|
||||
num_zeros++;
|
||||
}
|
||||
|
||||
pink.running_sum -= pink.rows[num_zeros];
|
||||
new_random = ((long)generate_random_number()) >> PINK_RANDOM_SHIFT;
|
||||
pink.running_sum += new_random;
|
||||
pink.rows[num_zeros] = new_random;
|
||||
}
|
||||
|
||||
new_random = ((long)generate_random_number()) >> PINK_RANDOM_SHIFT;
|
||||
sum = pink.running_sum + new_random;
|
||||
|
||||
output = pink.scalar * sum;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void get_pink_samples(double time, double timedelta, unsigned int num_samples, double frequency, double amplitude, int32_t* sampleBuffer, uint16_t bits_per_sample, double max_encoding_value)
|
||||
{
|
||||
for(unsigned int n=0; n < num_samples; n++)
|
||||
{
|
||||
sampleBuffer[n] = (int32_t)(max_encoding_value * generate_pink_noise() * amplitude);
|
||||
}
|
||||
}
|
||||
15
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/sinewave.hpp
vendored
Normal file
15
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/sinewave.hpp
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
//This code is based on examples from http://faculty.fiu.edu/~wgillam/wavfiles.html
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
void get_tone_samples(double time, double timedelta, unsigned int num_samples, double frequency, double amplitude, int32_t* sampleBuffer, uint16_t bits_per_sample, double max_encoding_val)
|
||||
{
|
||||
for(unsigned int n=0; n < num_samples; n++)
|
||||
{
|
||||
time += timedelta;
|
||||
double s = sin(frequency*time*2*M_PI);
|
||||
sampleBuffer[n] = (int32_t)(max_encoding_val * s * amplitude);
|
||||
}
|
||||
}
|
||||
115
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/wav.hpp
vendored
Normal file
115
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/wav.hpp
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
//This code was based on examples from http://faculty.fiu.edu/~wgillam/wavfiles.html
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define GET_BYTE(data, i) (((data) >> (8*(i))) & 0xFF)
|
||||
#define WAV_HEADER_SIZE 44
|
||||
|
||||
struct wav_info {
|
||||
uint16_t num_channels; /* 1 for mono, 2 for stereo, etc. */
|
||||
uint16_t bits_per_sample; /* 16 for CD, 24 for high-res, etc. */
|
||||
uint32_t sample_rate; /* 44100 for CD, 88200, 96000, 192000, etc. */
|
||||
uint32_t num_samples; /* total number of samples per channel */
|
||||
};
|
||||
|
||||
// open the output wav file and move the file pointer to the end of where the header will be, ready for writing samples
|
||||
FILE* open_wav_file(const char * filename) {
|
||||
FILE* fp = fopen(filename, "wb");
|
||||
if (fp != NULL && fseek(fp, WAV_HEADER_SIZE, SEEK_SET) == -1) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
void write_wav_hdr(const struct wav_info* w, FILE* fp)
|
||||
/* Write a standard 44-byte PCM format RIFF/WAVE header to the beginning of *fp.
|
||||
Again, the seek position of *fp will be left at the beginning of
|
||||
the data section, so one can immediately begin writing samples */
|
||||
{
|
||||
// We'll need the following:
|
||||
uint32_t data_size = (w->num_samples)*(w->num_channels)*(w->bits_per_sample)/8;
|
||||
uint32_t RCS = data_size+36;
|
||||
uint32_t byte_rate = (w->sample_rate)*(w->num_channels)*(w->bits_per_sample)/8;
|
||||
uint16_t block_align = (w->num_channels)*(w->bits_per_sample)/8;
|
||||
|
||||
// Prepare a standard 44 byte WAVE header from the info in w
|
||||
uint8_t h[WAV_HEADER_SIZE];
|
||||
// Bytes 1-4 are the ASCII codes for the four characters "RIFF"
|
||||
h[0]='R';
|
||||
h[1]='I';
|
||||
h[2]='F';
|
||||
h[3]='F';
|
||||
// Bytes 5-8 are RCS (i.e. data_size plus the remaining 36 bytes in the header)
|
||||
// in Little Endian format
|
||||
for(int i=0; i<4; i++) h[4+i] = GET_BYTE(RCS, i);
|
||||
// Bytes 9-12 are the ASCII codes for the four characters "WAVE"
|
||||
h[8]='W';
|
||||
h[9]='A';
|
||||
h[10]='V';
|
||||
h[11]='E';
|
||||
// Bytes 13-16 are the ASCII codes for the four characters "fmt "
|
||||
h[12]='f';
|
||||
h[13]='m';
|
||||
h[14]='t';
|
||||
h[15]= ' ';
|
||||
// Bytes 17-20 are the integer 16 (the size of the "fmt " subchunk
|
||||
// in the RIFF header we are writing) as a four-byte integer in
|
||||
// Little Endian format
|
||||
h[16]=0x10;
|
||||
h[17]=0x00;
|
||||
h[18]=0x00;
|
||||
h[19]=0x00;
|
||||
// Bytes 21-22 are the integer 1 (to indicate PCM format),
|
||||
// written as a two-byte Little Endian
|
||||
h[20]=0x01;
|
||||
h[21]=0x00;
|
||||
// Bytes 23-24 are num_channels as a two-byte Little Endian
|
||||
for(int j=0; j<2; j++) h[22+j] = GET_BYTE(w->num_channels, j);
|
||||
// Bytes 25-26 are sample_rate as a four-byte L.E.
|
||||
for(int i=0; i<4; i++) h[24+i] = GET_BYTE(w->sample_rate, i);
|
||||
// Bytes 27-30 are byte_rate as a four-byte L.E.
|
||||
for(int i=0; i<4; i++) h[28+i] = GET_BYTE(byte_rate, i);
|
||||
// Bytes 31-34 are block_align as a two-byte L.E.
|
||||
for(int j=0; j<2; j++) h[32+j] = GET_BYTE(block_align, j);
|
||||
// Bytes 35-36 are bits_per_sample as a two-byte L.E.
|
||||
for(int j=0; j<2; j++) h[34+j] = GET_BYTE(w->bits_per_sample, j);
|
||||
// Bytes 37-40 are the ASCII codes for the four characters "data"
|
||||
h[36]='d';
|
||||
h[37]='a';
|
||||
h[38]='t';
|
||||
h[39]='a';
|
||||
// Bytes 41-44 are data_size as a four-byte L.E.
|
||||
for(int i=0; i<4; i++) h[40+i] = GET_BYTE(data_size, i);
|
||||
|
||||
// Write the header to the beginning of *fp
|
||||
fseek(fp,0,SEEK_SET);
|
||||
fwrite(h,1,WAV_HEADER_SIZE,fp);
|
||||
}
|
||||
|
||||
|
||||
void write_sample(const struct wav_info* w, FILE* fp, const int32_t* sample)
|
||||
/* Write a sample to *fp in the correct Little Endian format.
|
||||
sample should be an array with w->num_channels elements.
|
||||
Note that we use the int_fast32_t datatype to hold samples, which should be
|
||||
changed if you want to use bits_per_sample > 32. Also, if you use, say,
|
||||
bits_per_sample=24, then you want to make sure that your actual samples
|
||||
are going to fit into a 3-byte Little Endian integer in twos-complement
|
||||
encoding. If you're only going to use bits_per_sample=16, you could
|
||||
use int_fast16_t instead of int_fast32_t. Note also that the WAVE
|
||||
file format expects 8-bit samples to be UNsigned, so if you're going
|
||||
to use bits_per_sample=8, then you should use uint_fast8_t to hold
|
||||
your samples. */
|
||||
|
||||
{
|
||||
// We'll assume w->bits_per_sample is divisible by 8, otherwise
|
||||
// one should do bytes_per_sample++ and make sure
|
||||
// the last (w->bits_per_sample % 8) bits of each sample[i] are zero
|
||||
int b = w->bits_per_sample/8; // bytes per sample
|
||||
uint8_t x[b];
|
||||
for(unsigned int i=0; i<w->num_channels; i++) {
|
||||
// populate x with sample[i] in Little Endian format, then write it
|
||||
for(int j=0; j<b; j++) x[j] = GET_BYTE(sample[i], j);
|
||||
fwrite(x,1,b,fp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// AlsaDevice.hpp
|
||||
// ALSA device handling
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "alsa/asoundlib.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#define PERIOD_FRAMES 96
|
||||
#define BUFFER_PERIODS 4
|
||||
#define DEFAULT_SAMPLERATE 48000
|
||||
#define DEFAULT_CHANNELS 2
|
||||
|
||||
// in order of prefence
|
||||
template <typename T>
|
||||
class RingBufferT;
|
||||
|
||||
struct AlsaTimestamps {
|
||||
uint64_t host; // Get "now" hi-res timestamp from a PCM status container.
|
||||
uint64_t audio; // Get "now" hi-res audio timestamp from a PCM status container.
|
||||
uint64_t trigger; // Time when stream was started or stopped
|
||||
uint32_t avail_frames;
|
||||
uint32_t delay_frames;
|
||||
};
|
||||
|
||||
struct CommonParam {
|
||||
int samplerate = DEFAULT_SAMPLERATE;
|
||||
snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
|
||||
snd_pcm_uframes_t framesPerPeriod = PERIOD_FRAMES;
|
||||
snd_pcm_uframes_t bufferPeriods = BUFFER_PERIODS;
|
||||
};
|
||||
|
||||
struct StreamParam {
|
||||
uint32_t channels = DEFAULT_CHANNELS;
|
||||
};
|
||||
|
||||
class AlsaDevice {
|
||||
public:
|
||||
AlsaDevice(std::string);
|
||||
~AlsaDevice();
|
||||
|
||||
int32_t getCaptureFramesReady();
|
||||
int32_t getPlaybackFramesReady();
|
||||
int32_t writeFrames(std::vector<RingBufferT<int32_t>> & ringRx);
|
||||
int32_t writeFrames(char* buf, uint32_t frames);
|
||||
int32_t readFrames(std::vector<RingBufferT<int32_t>> & ringTx);
|
||||
int32_t readFrames(char* buf, uint32_t frames);
|
||||
|
||||
int start(uint32_t maxPlaybackChannels, uint32_t maxCaptureChannels);
|
||||
void stop();
|
||||
|
||||
uint32_t getSamplerate();
|
||||
|
||||
void setSamplerate(uint32_t samplerate)
|
||||
{
|
||||
mCommonParam.samplerate = samplerate;
|
||||
}
|
||||
|
||||
uint32_t getChannels(snd_pcm_stream_t dir);
|
||||
uint32_t getBytesPerSample();
|
||||
|
||||
void setFramesPerPeriod(uint32_t frames)
|
||||
{
|
||||
mCommonParam.framesPerPeriod = (snd_pcm_uframes_t) frames;
|
||||
}
|
||||
|
||||
void setBufferPeriods(uint32_t periods)
|
||||
{
|
||||
mCommonParam.bufferPeriods = (snd_pcm_uframes_t) periods;
|
||||
}
|
||||
|
||||
void setDantePlaybackChannelStartIndex(uint32_t dantePlaybackChannelStartIndex)
|
||||
{
|
||||
mDantePlaybackChannelStartIndex = dantePlaybackChannelStartIndex;
|
||||
}
|
||||
|
||||
void setDanteCaptureChannelStartIndex(uint32_t danteCaptureChannelStartIndex)
|
||||
{
|
||||
mDanteCaptureChannelStartIndex = danteCaptureChannelStartIndex;
|
||||
}
|
||||
|
||||
void getTimestamps(AlsaTimestamps &ts, snd_pcm_stream_t dir);
|
||||
int32_t getTxChannels() { return mAlsaTxChannels; }
|
||||
int32_t getRxChannels() { return mAlsaRxChannels; }
|
||||
bool isHWTimeStampOn(snd_pcm_t *pcmHandle);
|
||||
|
||||
private:
|
||||
int updateHwParams();
|
||||
void setHwParams(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_param,
|
||||
StreamParam streamParam);
|
||||
void setSwParams(snd_pcm_t *handle);
|
||||
uint64_t alsaTimestamp2Ns(snd_htimestamp_t t);
|
||||
void setFormat(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_param);
|
||||
|
||||
snd_pcm_t *mPlaybackHandle = nullptr;
|
||||
snd_pcm_t *mCaptureHandle = nullptr;
|
||||
snd_pcm_hw_params_t *mPlaybackHwParams = nullptr;
|
||||
snd_pcm_hw_params_t *mCaptureHwParams = nullptr;
|
||||
|
||||
int mError = 0;
|
||||
std::string mAlsaDeviceName;
|
||||
|
||||
CommonParam mCommonParam;
|
||||
StreamParam mPlaybackParam;
|
||||
StreamParam mCaptureParam;
|
||||
uint32_t mAlsaRxChannels = 0;
|
||||
uint32_t mAlsaTxChannels = 0;
|
||||
uint32_t mAlsaBytesPerSample = 0;
|
||||
bool mFirstRead = true;
|
||||
bool mWriteStarted = false;
|
||||
uint32_t mDanteCaptureChannelStartIndex = 0;
|
||||
uint32_t mDantePlaybackChannelStartIndex = 0;
|
||||
};
|
||||
|
||||
class AlsaAggregatedDevice {
|
||||
public:
|
||||
AlsaAggregatedDevice(std::string);
|
||||
|
||||
int32_t writeFrames(std::vector<RingBufferT<int32_t>> & ringRx);
|
||||
int32_t readFrames(std::vector<RingBufferT<int32_t>> & ringTx);
|
||||
int start(uint32_t maxPlaybackChannels, uint32_t maxCaptureChannels);
|
||||
void stop();
|
||||
uint32_t getSamplerate();
|
||||
void setSamplerate(uint32_t samplerate);
|
||||
uint32_t getChannels(snd_pcm_stream_t dir);
|
||||
void setFramesPerPeriod(uint32_t frames);
|
||||
void setBufferPeriods(uint32_t periods);
|
||||
uint32_t getBytesPerSample();
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<AlsaDevice>> mAlsaDevices;
|
||||
std::vector<std::shared_ptr<AlsaDevice>> mAlsaStartedDevices;
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,828 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// AlsaDevice.cpp
|
||||
// ALSA device handling
|
||||
//
|
||||
#include "AlsaDevice.hpp"
|
||||
#include "3rd_party/RingBuffer.hpp"
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#undef NDEBUG
|
||||
#include <cassert>
|
||||
#define ALSA_FRAME_BUFFER_BYTES (4096 * 8)
|
||||
|
||||
extern uint32_t g_alsa_ring_size;
|
||||
static const std::array<snd_pcm_format_t, 3> kSupportedFormats
|
||||
{
|
||||
{SND_PCM_FORMAT_S32, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16}
|
||||
};
|
||||
|
||||
AlsaDevice::AlsaDevice(std::string deviceId) :
|
||||
mAlsaDeviceName(deviceId)
|
||||
{
|
||||
}
|
||||
|
||||
AlsaDevice::~AlsaDevice()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
int AlsaDevice::start
|
||||
(
|
||||
uint32_t dantePlaybackChannels,
|
||||
uint32_t danteCaptureChannels
|
||||
)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (dantePlaybackChannels > 0) {
|
||||
if ((err = snd_pcm_hw_params_malloc(&mPlaybackHwParams))) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_open(&mPlaybackHandle, mAlsaDeviceName.c_str(),
|
||||
SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE))) {
|
||||
fprintf(stderr, "Alsa playback open %s error: %s\n",
|
||||
mAlsaDeviceName.c_str(),
|
||||
snd_strerror(err));
|
||||
goto done;
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_any(mPlaybackHandle, mPlaybackHwParams);
|
||||
if ((err = snd_pcm_hw_params_get_channels_max(
|
||||
mPlaybackHwParams, &mPlaybackParam.channels))) {
|
||||
fprintf(stderr, "cannot get playback max channel count (%s)\n",
|
||||
snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
if (mPlaybackParam.channels > dantePlaybackChannels) {
|
||||
mPlaybackParam.channels = dantePlaybackChannels;
|
||||
}
|
||||
setHwParams(mPlaybackHandle, mPlaybackHwParams, mPlaybackParam);
|
||||
setSwParams(mPlaybackHandle);
|
||||
if (isHWTimeStampOn(mPlaybackHandle)) {
|
||||
printf("Play back sound device relies on audio wallclock timestamps\n");
|
||||
}
|
||||
else {
|
||||
printf("Play back sound device relies on audio sample counter timestamps\n");
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_prepare(mPlaybackHandle))) {
|
||||
fprintf(stderr, "cannot prepare playback audio interface for use (%s)\n",
|
||||
snd_strerror(err));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (danteCaptureChannels > 0) {
|
||||
if ((err = snd_pcm_hw_params_malloc(&mCaptureHwParams))) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_open(&mCaptureHandle, mAlsaDeviceName.c_str(),
|
||||
SND_PCM_STREAM_CAPTURE,
|
||||
SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NONBLOCK))) {
|
||||
fprintf(stderr, "Alsa capture open error: %s\n", snd_strerror(err));
|
||||
goto done;
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_any(mCaptureHandle, mCaptureHwParams);
|
||||
if ((err = snd_pcm_hw_params_get_channels_max(
|
||||
mCaptureHwParams, &mCaptureParam.channels))) {
|
||||
fprintf(stderr, "cannot get capture max channel count (%s)\n",
|
||||
snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
if (mCaptureParam.channels > danteCaptureChannels) {
|
||||
mCaptureParam.channels = danteCaptureChannels;
|
||||
}
|
||||
setHwParams(mCaptureHandle, mCaptureHwParams, mCaptureParam);
|
||||
setSwParams(mCaptureHandle);
|
||||
if (isHWTimeStampOn(mCaptureHandle)) {
|
||||
printf("Capture sound device relies on audio wallclock timestamps\n");
|
||||
}
|
||||
else {
|
||||
printf("Capture sound device relies on audio sample counter timestamps\n");
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_prepare(mCaptureHandle))) {
|
||||
fprintf(stderr, "cannot prepare capture audio interface for use (%s)\n",
|
||||
snd_strerror(err));
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if ( updateHwParams() >= 0) {
|
||||
if (mPlaybackHwParams) {
|
||||
snd_pcm_hw_params_get_channels(mPlaybackHwParams, &mAlsaTxChannels);
|
||||
}
|
||||
if (mCaptureHwParams) {
|
||||
snd_pcm_hw_params_get_channels(mCaptureHwParams, &mAlsaRxChannels);
|
||||
}
|
||||
}
|
||||
|
||||
mAlsaBytesPerSample = getBytesPerSample();
|
||||
mFirstRead = true;
|
||||
mWriteStarted = false;
|
||||
done:
|
||||
if (err) {
|
||||
stop();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void AlsaDevice::stop()
|
||||
{
|
||||
if (mPlaybackHwParams) {
|
||||
snd_pcm_hw_params_free(mPlaybackHwParams);
|
||||
mPlaybackHwParams = nullptr;
|
||||
}
|
||||
|
||||
if (mCaptureHwParams) {
|
||||
snd_pcm_hw_params_free(mCaptureHwParams);
|
||||
mCaptureHwParams = nullptr;
|
||||
}
|
||||
|
||||
if (mPlaybackHandle) {
|
||||
snd_pcm_close(mPlaybackHandle);
|
||||
mPlaybackHandle = nullptr;
|
||||
}
|
||||
|
||||
if (mCaptureHandle) {
|
||||
snd_pcm_close(mCaptureHandle);
|
||||
mCaptureHandle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AlsaDevice::getCaptureFramesReady()
|
||||
{
|
||||
if (mCaptureHandle == NULL) {
|
||||
fprintf(stderr, "%s: Not capture device handle\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t framesReady = 0;
|
||||
if ((framesReady = snd_pcm_avail_update(mCaptureHandle)) < 0) {
|
||||
return 0;
|
||||
}
|
||||
return framesReady;
|
||||
}
|
||||
|
||||
int32_t AlsaDevice::getPlaybackFramesReady()
|
||||
{
|
||||
if (mPlaybackHandle == NULL) {
|
||||
fprintf(stderr, "%s: Not playback device handle\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t framesReady = 0;
|
||||
|
||||
/* find out how much space is available for playback data */
|
||||
if ((framesReady = snd_pcm_avail_update(mPlaybackHandle)) < 0) {
|
||||
if (framesReady == -EPIPE) {
|
||||
snd_pcm_recover(mPlaybackHandle, framesReady, 0);
|
||||
return -1;
|
||||
} else {
|
||||
fprintf(stderr, "unknown ALSA avail update return value (%d)\n",
|
||||
framesReady);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Why large frames ready value at startup?
|
||||
framesReady = framesReady > 4096 ? 4096 : framesReady;
|
||||
|
||||
return framesReady;
|
||||
}
|
||||
|
||||
int32_t AlsaDevice::writeFrames(char* buf, uint32_t frames)
|
||||
{
|
||||
if (mPlaybackHandle == NULL) {
|
||||
fprintf(stderr, "%s: Not playback device handle\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t framesWritten = 0;
|
||||
if ((framesWritten = snd_pcm_writei(mPlaybackHandle, buf, frames)) < 0) {
|
||||
fprintf(stderr, "write failed (%s)\n", snd_strerror(framesWritten));
|
||||
}
|
||||
return framesWritten;
|
||||
}
|
||||
|
||||
int32_t AlsaDevice::readFrames(char *buf, uint32_t frames)
|
||||
{
|
||||
if (mCaptureHandle == NULL) {
|
||||
fprintf(stderr, "%s: Not capture device handle\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// non-blocking for now
|
||||
int32_t r = snd_pcm_readi(mCaptureHandle, buf, frames);
|
||||
|
||||
if (r >= 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r == -EAGAIN) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (r) {
|
||||
case -EBADFD:
|
||||
fprintf(stderr, "%s: PCM not in correct state\n", __func__);
|
||||
break;
|
||||
case -EPIPE:
|
||||
fprintf(stderr, "%s: PCM overrun\n", __func__);
|
||||
break;
|
||||
case -ESTRPIPE:
|
||||
fprintf(stderr, "%s: PCM suspend\n", __func__);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown error %s \n", strerror(r));
|
||||
break;
|
||||
}
|
||||
|
||||
snd_pcm_recover(mCaptureHandle, r, 0);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t AlsaDevice::writeFrames(std::vector<RingBufferT<int32_t>> & ringRx)
|
||||
{
|
||||
//TODO decouple ringbuffer from alsa device
|
||||
|
||||
// Convert from Dante audio format to ALSA format and send to
|
||||
// ALSA device
|
||||
int32_t txFramesReady = getPlaybackFramesReady();
|
||||
if (txFramesReady) {
|
||||
uint8_t frame_buf[ALSA_FRAME_BUFFER_BYTES] = {};
|
||||
if (txFramesReady > ALSA_FRAME_BUFFER_BYTES) {
|
||||
txFramesReady = ALSA_FRAME_BUFFER_BYTES;
|
||||
}
|
||||
|
||||
if (ringRx[mDantePlaybackChannelStartIndex].getAvailableRead() >= g_alsa_ring_size/2 &&
|
||||
mWriteStarted == false) {
|
||||
mWriteStarted = true;
|
||||
}
|
||||
|
||||
if (mWriteStarted) {
|
||||
for (int i = 0; i < txFramesReady; i++) {
|
||||
for (unsigned int alsaChannelIndex = 0, danteChannelIndex = mDantePlaybackChannelStartIndex; alsaChannelIndex < mAlsaTxChannels; alsaChannelIndex++, danteChannelIndex++) {
|
||||
int tmp;
|
||||
ringRx[danteChannelIndex].read(&tmp, 1);
|
||||
if (mAlsaBytesPerSample == 2) {
|
||||
((int16_t*) frame_buf)[i * mAlsaTxChannels + alsaChannelIndex] =
|
||||
tmp / 32768;
|
||||
} else if (mAlsaBytesPerSample == 4) {
|
||||
((int32_t*) frame_buf)[i * mAlsaTxChannels + alsaChannelIndex] =
|
||||
tmp;
|
||||
} else {
|
||||
std::cerr <<
|
||||
"ALSA bytes per sample not yet supported." <<
|
||||
std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mPlaybackHandle == NULL) {
|
||||
fprintf(stderr, "%s: Not playback device handle\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t framesWritten = 0;
|
||||
if ((framesWritten = snd_pcm_writei(mPlaybackHandle, frame_buf, txFramesReady)) < 0) {
|
||||
fprintf(stderr, "write failed (%s)\n", snd_strerror(framesWritten));
|
||||
}
|
||||
return framesWritten;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t AlsaDevice::readFrames(std::vector<RingBufferT<int32_t>> & ringTx)
|
||||
{
|
||||
int32_t r = 0;
|
||||
if (mFirstRead) {
|
||||
mFirstRead = false;
|
||||
// read required to trigger capture
|
||||
if (mCaptureHandle) {
|
||||
char tmp[32];
|
||||
// non-blocking for now
|
||||
r = snd_pcm_readi(mCaptureHandle, tmp, 1);
|
||||
|
||||
if (r < 0 && r != -EAGAIN) {
|
||||
fprintf(stderr, "%d\n", r);
|
||||
snd_pcm_recover(mCaptureHandle, r, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t rxFramesReady = getCaptureFramesReady();
|
||||
if (rxFramesReady) {
|
||||
uint8_t frame_buf[ALSA_FRAME_BUFFER_BYTES] = {};
|
||||
if (rxFramesReady > ALSA_FRAME_BUFFER_BYTES) {
|
||||
rxFramesReady = ALSA_FRAME_BUFFER_BYTES;
|
||||
}
|
||||
|
||||
if (mCaptureHandle) {
|
||||
// non-blocking for now
|
||||
r = snd_pcm_readi(mCaptureHandle, frame_buf, rxFramesReady);
|
||||
|
||||
if (r < 0 && r != -EAGAIN) {
|
||||
fprintf(stderr, "%d\n", r);
|
||||
snd_pcm_recover(mCaptureHandle, r, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t tmp = 0;
|
||||
for(int i = 0; i < rxFramesReady; i++) {
|
||||
for (unsigned int alsaChannelIndex = 0, danteChannelIndex = mDanteCaptureChannelStartIndex; alsaChannelIndex < mAlsaRxChannels; alsaChannelIndex++, danteChannelIndex++) {
|
||||
if (mAlsaBytesPerSample == 2) {
|
||||
tmp = ((int16_t*)
|
||||
frame_buf)[i * mAlsaRxChannels + alsaChannelIndex] * 32768;
|
||||
} else if (mAlsaBytesPerSample == 4) {
|
||||
tmp = ((int32_t*)
|
||||
frame_buf)[i * mAlsaRxChannels + alsaChannelIndex];
|
||||
} else {
|
||||
std::cerr <<
|
||||
"ALSA bytes per sample not yet supported." <<
|
||||
std::endl;
|
||||
}
|
||||
ringTx[danteChannelIndex].write(&tmp, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r>=0?r:-1;
|
||||
}
|
||||
|
||||
uint32_t AlsaDevice::getSamplerate()
|
||||
{
|
||||
snd_pcm_hw_params_t *hw_params =
|
||||
(mPlaybackHwParams) ? mPlaybackHwParams : mCaptureHwParams;
|
||||
|
||||
uint32_t fs = 0;
|
||||
int d;
|
||||
if (updateHwParams() >= 0) {
|
||||
snd_pcm_hw_params_get_rate(hw_params, &fs, &d);
|
||||
}
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
uint32_t AlsaDevice::getChannels(snd_pcm_stream_t dir)
|
||||
{
|
||||
return (dir == SND_PCM_STREAM_PLAYBACK) ? mAlsaTxChannels: mAlsaRxChannels;
|
||||
}
|
||||
|
||||
uint32_t AlsaDevice::getBytesPerSample()
|
||||
{
|
||||
uint32_t bytes_per_sample = 0;
|
||||
switch (mCommonParam.format) {
|
||||
case SND_PCM_FORMAT_S32:
|
||||
bytes_per_sample = 4;
|
||||
break;
|
||||
case SND_PCM_FORMAT_S24_3LE:
|
||||
bytes_per_sample = 3;
|
||||
break;
|
||||
case SND_PCM_FORMAT_S16:
|
||||
bytes_per_sample = 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
return bytes_per_sample;
|
||||
}
|
||||
|
||||
// Make encoding common for playback and capture
|
||||
int AlsaDevice::updateHwParams()
|
||||
{
|
||||
int err = 0;
|
||||
if (mPlaybackHandle) {
|
||||
err = snd_pcm_hw_params_current(mPlaybackHandle, mPlaybackHwParams);
|
||||
if (err < 0) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Broken configuration for stream: no configurations available: %s\n",
|
||||
snd_strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
if (mCaptureHandle) {
|
||||
err = snd_pcm_hw_params_current(mCaptureHandle, mCaptureHwParams);
|
||||
if (mError < 0) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Broken configuration for stream: no configurations available: %s\n",
|
||||
snd_strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void AlsaDevice::setFormat
|
||||
(
|
||||
snd_pcm_t *pcm_handle,
|
||||
snd_pcm_hw_params_t *hw_param
|
||||
)
|
||||
{
|
||||
if (mCommonParam.format != SND_PCM_FORMAT_UNKNOWN) {
|
||||
int err;
|
||||
// Format known
|
||||
if ((err = snd_pcm_hw_params_set_format(pcm_handle, hw_param,
|
||||
mCommonParam.format))) {
|
||||
fprintf(stderr, "cannot set common sample format %s (%s)\n",
|
||||
snd_pcm_format_name(mCommonParam.format), snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto format : kSupportedFormats) {
|
||||
int supported =
|
||||
snd_pcm_hw_params_set_format(pcm_handle, hw_param, format);
|
||||
if (supported >= 0) {
|
||||
// suitable format found
|
||||
mCommonParam.format = format;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unable to find suitable format\n");
|
||||
assert(0);
|
||||
}
|
||||
|
||||
void AlsaDevice::setHwParams
|
||||
(
|
||||
snd_pcm_t *pcm_handle,
|
||||
snd_pcm_hw_params_t *hw_param,
|
||||
StreamParam streamParam
|
||||
)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_access(
|
||||
pcm_handle, hw_param, SND_PCM_ACCESS_RW_INTERLEAVED))) {
|
||||
fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
setFormat(pcm_handle, hw_param);
|
||||
|
||||
uint32_t rate = mCommonParam.samplerate;
|
||||
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_param, &rate,
|
||||
0))) {
|
||||
fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hw_param,
|
||||
streamParam.channels))) {
|
||||
fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t bufSize = (snd_pcm_uframes_t)
|
||||
(mCommonParam.framesPerPeriod * mCommonParam.bufferPeriods);
|
||||
if ((err = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_param,
|
||||
&bufSize))) {
|
||||
fprintf(stderr, "cannot set buf size (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t periodSize = mCommonParam.framesPerPeriod;
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_param,
|
||||
&periodSize, 0))) {
|
||||
fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params(pcm_handle, hw_param)) < 0) {
|
||||
fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_buffer_size(hw_param,
|
||||
&bufSize))) {
|
||||
fprintf(stderr, "cannot get buf size (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
fprintf(stdout, "Actual buf size (%d)\n", bufSize);
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_period_size(hw_param,
|
||||
&periodSize, 0))) {
|
||||
fprintf(stderr, "cannot get period size (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
fprintf(stdout, "Actual period size (%d)\n", periodSize);
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_period_size_max(hw_param,
|
||||
&periodSize, 0))) {
|
||||
fprintf(stderr, "cannot get period size max(%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
fprintf(stdout, "Actual period max size (%d)\n", periodSize);
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_period_size_min(hw_param,
|
||||
&periodSize, 0))) {
|
||||
fprintf(stderr, "cannot get period size min(%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
fprintf(stdout, "Actual period size min(%d)\n", periodSize);
|
||||
|
||||
}
|
||||
|
||||
void AlsaDevice::setSwParams(snd_pcm_t *pcm_handle)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (pcm_handle == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
snd_pcm_sw_params_t *swParams;
|
||||
snd_pcm_sw_params_alloca(&swParams);
|
||||
|
||||
if ((err = snd_pcm_sw_params_current(pcm_handle, swParams))) {
|
||||
fprintf(stderr, "cannot initialize software parameters structure (%s)\n",
|
||||
snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_sw_params_set_avail_min(pcm_handle, swParams,
|
||||
mCommonParam.framesPerPeriod))) {
|
||||
fprintf(stderr, "cannot set minimum available count (%s)\n",
|
||||
snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_sw_params_set_start_threshold(pcm_handle, swParams,
|
||||
0U))) {
|
||||
fprintf(stderr, "cannot set start mode (%s)\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm_handle, swParams,
|
||||
SND_PCM_TSTAMP_ENABLE))) {
|
||||
fprintf(stderr, "Unable to set tstamp mode : %s\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
auto clock = SND_PCM_TSTAMP_TYPE_MONOTONIC;
|
||||
#ifdef DEP_TIMESTAMP
|
||||
// DEP times tamp is monotonic raw, Alsa time stamp needs to be set to the same type
|
||||
clock = SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW;
|
||||
#endif
|
||||
|
||||
if ((err = snd_pcm_sw_params_set_tstamp_type(
|
||||
pcm_handle, swParams, clock))) {
|
||||
fprintf(stderr, "Unable to set tstamp type : %s\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
// write sw params
|
||||
if ((mError = snd_pcm_sw_params(pcm_handle, swParams)) < 0) {
|
||||
fprintf(stderr, "cannot set software parameters (%s)\n",
|
||||
snd_strerror(mError));
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool AlsaDevice::isHWTimeStampOn(snd_pcm_t *pcmHandle)
|
||||
{
|
||||
snd_pcm_hw_params_t *hwParams;
|
||||
snd_pcm_hw_params_alloca(&hwParams);
|
||||
/* get the current hwparams */
|
||||
int err = snd_pcm_hw_params_current(pcmHandle, hwParams);
|
||||
if (err < 0) {
|
||||
printf("Unable to determine current hwparams_p: %s\n", snd_strerror(err));
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (snd_pcm_hw_params_supports_audio_wallclock_ts(hwParams)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t AlsaDevice::alsaTimestamp2Ns(snd_htimestamp_t ts)
|
||||
{
|
||||
uint64_t ns;
|
||||
|
||||
ns = ts.tv_sec * 1000000000ULL;
|
||||
ns += ts.tv_nsec;
|
||||
|
||||
return ns;
|
||||
}
|
||||
|
||||
void AlsaDevice::getTimestamps(AlsaTimestamps &ts, snd_pcm_stream_t dir)
|
||||
{
|
||||
snd_pcm_status_t *status;
|
||||
snd_pcm_status_alloca(&status);
|
||||
|
||||
snd_pcm_t *pcm_handle = (dir == SND_PCM_STREAM_PLAYBACK) ? mPlaybackHandle : mCaptureHandle;
|
||||
|
||||
if ((mError = snd_pcm_status(pcm_handle, status)) < 0) {
|
||||
fprintf(stderr, "Stream status error: %s\n", snd_strerror(mError));
|
||||
assert(0);
|
||||
}
|
||||
snd_htimestamp_t tmpTs;
|
||||
|
||||
snd_pcm_status_get_htstamp(status, &tmpTs);
|
||||
ts.host = alsaTimestamp2Ns(tmpTs);
|
||||
|
||||
snd_pcm_status_get_audio_htstamp(status, &tmpTs);
|
||||
ts.audio = alsaTimestamp2Ns(tmpTs);
|
||||
|
||||
snd_pcm_status_get_trigger_htstamp(status, &tmpTs);
|
||||
ts.trigger = alsaTimestamp2Ns(tmpTs);
|
||||
|
||||
ts.avail_frames = snd_pcm_status_get_avail(status);
|
||||
ts.delay_frames = snd_pcm_status_get_delay(status);
|
||||
}
|
||||
|
||||
AlsaAggregatedDevice::AlsaAggregatedDevice(std::string devices)
|
||||
{
|
||||
std::istringstream issDevices(devices);
|
||||
std::string device;
|
||||
while(getline(issDevices, device, ';')) {
|
||||
std::cout << "Binding " << device << std::endl;
|
||||
mAlsaDevices.push_back(std::shared_ptr<AlsaDevice>(new AlsaDevice(device)));
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AlsaAggregatedDevice::writeFrames(std::vector<RingBufferT<int32_t>> &ringRx)
|
||||
{
|
||||
int32_t framesWritten= 0;
|
||||
for(auto p : mAlsaStartedDevices) {
|
||||
framesWritten += p->writeFrames(ringRx);
|
||||
}
|
||||
return framesWritten;
|
||||
}
|
||||
|
||||
int32_t AlsaAggregatedDevice::readFrames(std::vector<RingBufferT<int32_t>> & ringTx)
|
||||
{
|
||||
int32_t framesRead= 0;
|
||||
for(auto p : mAlsaStartedDevices) {
|
||||
framesRead += p->readFrames(ringTx);
|
||||
}
|
||||
return framesRead;
|
||||
}
|
||||
|
||||
int AlsaAggregatedDevice::start(uint32_t dantePlaybackChannels, uint32_t danteCaptureChannels)
|
||||
{
|
||||
int err = 0;
|
||||
uint32_t danteCaptureChannelStartIndex = 0, dantePlaybackChannelStartIndex = 0, channels = 0;
|
||||
for(auto p : mAlsaDevices) {
|
||||
if (!dantePlaybackChannels && !danteCaptureChannels) {
|
||||
break;
|
||||
}
|
||||
|
||||
err = p->start(dantePlaybackChannels, danteCaptureChannels);
|
||||
|
||||
if (err) {
|
||||
break;
|
||||
}
|
||||
mAlsaStartedDevices.push_back(p);
|
||||
p->setDanteCaptureChannelStartIndex(danteCaptureChannelStartIndex);
|
||||
channels = p->getChannels(SND_PCM_STREAM_CAPTURE);
|
||||
if (danteCaptureChannels < channels) {
|
||||
channels = danteCaptureChannels;
|
||||
}
|
||||
danteCaptureChannels -= channels;
|
||||
danteCaptureChannelStartIndex += channels;
|
||||
|
||||
p->setDantePlaybackChannelStartIndex(dantePlaybackChannelStartIndex);
|
||||
channels = p->getChannels(SND_PCM_STREAM_PLAYBACK);
|
||||
if (dantePlaybackChannels < channels) {
|
||||
channels = dantePlaybackChannels;
|
||||
}
|
||||
dantePlaybackChannels -= channels;
|
||||
dantePlaybackChannelStartIndex += channels;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void AlsaAggregatedDevice::stop()
|
||||
{
|
||||
for(auto p : mAlsaStartedDevices) {
|
||||
p->stop();
|
||||
}
|
||||
mAlsaStartedDevices.clear();
|
||||
}
|
||||
|
||||
uint32_t AlsaAggregatedDevice::getSamplerate()
|
||||
{
|
||||
uint32_t sampleRate = 0;
|
||||
for(auto p : mAlsaDevices)
|
||||
{
|
||||
auto s = p->getSamplerate();
|
||||
if (sampleRate && sampleRate != s) {
|
||||
std::cerr << "Error, sample rate [" << s << "] is different from other's sample rate [" << sampleRate << "]" << std::endl;
|
||||
}
|
||||
sampleRate = s;
|
||||
}
|
||||
return sampleRate;
|
||||
}
|
||||
|
||||
void AlsaAggregatedDevice::setSamplerate(uint32_t samplerate)
|
||||
{
|
||||
for(auto p : mAlsaDevices)
|
||||
{
|
||||
p->setSamplerate(samplerate);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AlsaAggregatedDevice::getChannels(snd_pcm_stream_t dir)
|
||||
{
|
||||
int32_t channels= 0;
|
||||
for(auto p : mAlsaDevices)
|
||||
{
|
||||
channels += p->getChannels(dir);
|
||||
}
|
||||
return channels;
|
||||
}
|
||||
|
||||
void AlsaAggregatedDevice::setFramesPerPeriod(uint32_t frames)
|
||||
{
|
||||
for(auto p : mAlsaDevices)
|
||||
{
|
||||
p->setFramesPerPeriod(frames);
|
||||
}
|
||||
}
|
||||
|
||||
void AlsaAggregatedDevice::setBufferPeriods(uint32_t periods)
|
||||
{
|
||||
for(auto p : mAlsaDevices)
|
||||
{
|
||||
p->setBufferPeriods(periods);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AlsaAggregatedDevice::getBytesPerSample()
|
||||
{
|
||||
uint32_t bytesPerSample = 0;
|
||||
for(auto p : mAlsaStartedDevices){
|
||||
auto b = p->getBytesPerSample();
|
||||
if (bytesPerSample && bytesPerSample != b){
|
||||
std::cerr << "Error, bytes per sample [" << b << "] is different from other's [" << bytesPerSample << "]" << std::endl;
|
||||
}
|
||||
bytesPerSample = b;
|
||||
}
|
||||
return bytesPerSample;
|
||||
}
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,158 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(dep_example_apps)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
# 3rd party libraries
|
||||
add_library(dep_example_third_party_includes INTERFACE)
|
||||
target_include_directories(dep_example_third_party_includes INTERFACE 3p_include/)
|
||||
|
||||
# versions
|
||||
add_library(dep_example_version STATIC
|
||||
version/versions.c)
|
||||
target_include_directories(dep_example_version PUBLIC version/)
|
||||
|
||||
############################################################
|
||||
# Loopback tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_loopback
|
||||
dep_loopback/DanteLoopback.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dep_example_loopback
|
||||
dep_audio
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_loopback PROPERTIES OUTPUT_NAME DepLoopback)
|
||||
|
||||
############################################################
|
||||
# Timing diagnostic tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_timing
|
||||
dep_timing_trace/DanteTimingTrace.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_timing
|
||||
dep_audio
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_timing PROPERTIES OUTPUT_NAME DepTiming)
|
||||
|
||||
# Linux examples
|
||||
if(DANTE_LINUX_EXAMPLES)
|
||||
|
||||
############################################################
|
||||
# Alsa Device Library
|
||||
############################################################
|
||||
|
||||
add_library(dep_example_alsa_device STATIC
|
||||
AlsaDevice/src/AlsaDevice.cpp
|
||||
)
|
||||
target_include_directories(dep_example_alsa_device PUBLIC
|
||||
AlsaDevice/include
|
||||
)
|
||||
target_link_libraries(dep_example_alsa_device PUBLIC
|
||||
alsalib
|
||||
dep_example_third_party_includes
|
||||
)
|
||||
|
||||
############################################################
|
||||
# Metadata Header Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_info
|
||||
dep_info/dinfo.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dep_example_info
|
||||
dep_audio
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_info PROPERTIES OUTPUT_NAME dinfo)
|
||||
|
||||
############################################################
|
||||
# Playback Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_playback
|
||||
dep_play/dplay.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_playback
|
||||
dep_audio
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_playback PROPERTIES OUTPUT_NAME dplay)
|
||||
|
||||
############################################################
|
||||
# Recording Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_record
|
||||
dep_record/drecord.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_record
|
||||
dep_audio
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_record PROPERTIES OUTPUT_NAME drecord)
|
||||
|
||||
############################################################
|
||||
# Tone/Noise Generation Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_soundtest
|
||||
dep_test/dtest.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_soundtest
|
||||
dep_audio
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_soundtest PROPERTIES OUTPUT_NAME dtest)
|
||||
|
||||
############################################################
|
||||
# Alsa Audio Input/Output Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_alsa_bridge
|
||||
dep_alsa_bridge/DanteAlsaBridge.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_alsa_bridge
|
||||
dep_audio
|
||||
dep_example_alsa_device
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_alsa_bridge PROPERTIES OUTPUT_NAME DepAlsaBridge)
|
||||
|
||||
############################################################
|
||||
# Dante Soundcard Tool
|
||||
############################################################
|
||||
|
||||
set(SOUNDCARD_SHARED_OBJECT_NAME "dsoundcard.so")
|
||||
set(SOUNDCARD_SHARED_OBJECT_NAME ${SOUNDCARD_SHARED_OBJECT_NAME} PARENT_SCOPE)
|
||||
add_executable(dep_example_soundcard
|
||||
dep_soundcard/src/dsoundcard.cpp
|
||||
dep_soundcard/src/dsoundcard_intermediatebuffer.cpp
|
||||
dep_soundcard/src/dsoundcard_logging.cpp
|
||||
dep_soundcard/src/alsa_plugin.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_soundcard
|
||||
dep_audio
|
||||
dep_example_alsa_device
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
target_include_directories(dep_example_soundcard PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/dep_soundcard/include
|
||||
)
|
||||
set_target_properties(dep_example_soundcard PROPERTIES OUTPUT_NAME ${SOUNDCARD_SHARED_OBJECT_NAME})
|
||||
target_compile_options(dep_example_soundcard PUBLIC -fPIC -DPIC)
|
||||
target_link_options(dep_example_soundcard PUBLIC -shared -fPIC -DPIC)
|
||||
|
||||
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1,52 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include(ExternalProject)
|
||||
include(FetchContent)
|
||||
|
||||
set(TARGET alsalib)
|
||||
|
||||
# If ALSA_INSTALL_PATH is set it should point to an externally provided ALSA installation.
|
||||
# Otherwise a default version will be built.
|
||||
if (ALSA_INSTALL_PATH)
|
||||
add_library(alsalib SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(alsalib PROPERTIES
|
||||
IMPORTED_LOCATION ${ALSA_INSTALL_PATH}/lib/libasound.so
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${ALSA_INSTALL_PATH}/include
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(EP_TARGET ${TARGET}_source)
|
||||
|
||||
FetchContent_Declare(alsalib_download
|
||||
URL https://www.alsa-project.org/files/pub/lib/alsa-lib-1.0.29.tar.bz2
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(alsalib_download)
|
||||
if (NOT alsalib_download_POPULATED)
|
||||
FetchContent_Populate(alsalib_download)
|
||||
endif()
|
||||
|
||||
set(EP_INSTALL_DIR ${alsalib_download_SOURCE_DIR}/staging)
|
||||
|
||||
ExternalProject_Add(alsalib_build
|
||||
SOURCE_DIR ${alsalib_download_SOURCE_DIR}
|
||||
BINARY_DIR ${alsalib_download_BINARY_DIR}
|
||||
|
||||
CONFIGURE_COMMAND ""
|
||||
|
||||
BUILD_COMMAND CC=${CMAKE_C_COMPILER} ${alsalib_download_SOURCE_DIR}/configure --prefix=${EP_INSTALL_DIR} --host=${TOOLCHAIN_TARGET} --disable-python
|
||||
|
||||
COMMAND make
|
||||
)
|
||||
|
||||
# CMake known bug - INTERFACE_INCLUDE_DIRECTORIES requires dir to exist at config stage
|
||||
file(MAKE_DIRECTORY ${EP_INSTALL_DIR}/include)
|
||||
|
||||
add_library(${TARGET} SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(${TARGET} PROPERTIES
|
||||
IMPORTED_LOCATION ${EP_INSTALL_DIR}/lib/libasound.so
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${EP_INSTALL_DIR}/include
|
||||
)
|
||||
add_dependencies(${TARGET} ${EP_TARGET})
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteAlsa.cpp
|
||||
// Dante-ALSA bridge
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "AlsaDevice.hpp"
|
||||
#include "3rd_party/RingBuffer.hpp"
|
||||
#include "versions.h"
|
||||
#include <queue>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static std::string g_soundcard = "hw:1,0";
|
||||
static int32_t g_starting_dante_rx = 0;
|
||||
static int32_t g_starting_dante_tx = 0;
|
||||
uint32_t g_alsa_ring_size = 96;
|
||||
|
||||
#define ALSA_DEFAULT_FRAMES_PER_PERIOD 24
|
||||
#define ALSA_DEFAULT_BUFFER_PERIODS 4
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
class DanteAlsaBridge {
|
||||
public:
|
||||
DanteAlsaBridge(Dante::Buffers &buffers, int txLatencySamples) :
|
||||
mBuffers(buffers), mTxLatencySamples(txLatencySamples),
|
||||
mSamplesPerPeriod(), mSamplesPerChannel(), mDanteTxHeadSamples(),
|
||||
mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(),
|
||||
mAlsaTxChannels(0), mAlsaRxChannels(0),
|
||||
mAlsaFramesPerPeriod(ALSA_DEFAULT_FRAMES_PER_PERIOD),
|
||||
mAlsaBufferPeriods(ALSA_DEFAULT_BUFFER_PERIODS)
|
||||
{
|
||||
mAlsaDevice = std::unique_ptr<AlsaAggregatedDevice>(new AlsaAggregatedDevice(g_soundcard));
|
||||
}
|
||||
|
||||
~DanteAlsaBridge() {}
|
||||
|
||||
void setAlsaFramesPerPeriod(uint32_t frames)
|
||||
{
|
||||
mAlsaFramesPerPeriod = frames;
|
||||
}
|
||||
|
||||
void setAlsaBufferPeriods(uint32_t periods)
|
||||
{
|
||||
mAlsaBufferPeriods = periods;
|
||||
}
|
||||
|
||||
int start()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mAlsaDevice->setFramesPerPeriod(mAlsaFramesPerPeriod);
|
||||
mAlsaDevice->setBufferPeriods(mAlsaBufferPeriods);
|
||||
mAlsaDevice->setSamplerate(metadata->audio.sample_rate);
|
||||
|
||||
int result =
|
||||
mAlsaDevice->start
|
||||
(
|
||||
metadata->audio.num_rx_channels,
|
||||
metadata->audio.num_tx_channels
|
||||
);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
mAlsaTxChannels = mAlsaDevice->getChannels(SND_PCM_STREAM_PLAYBACK);
|
||||
mAlsaRxChannels = mAlsaDevice->getChannels(SND_PCM_STREAM_CAPTURE);
|
||||
mAlsaBytesPerSample = mAlsaDevice->getBytesPerSample();
|
||||
|
||||
if (mTxLatencySamples == 0) {
|
||||
// Not set explicitly on command line. Default is approx 1ms
|
||||
// at the current sample rate.
|
||||
mTxLatencySamples = metadata->audio.sample_rate / 1000;
|
||||
}
|
||||
|
||||
std::cout << "Started soundcard: " << std::endl;
|
||||
std::cout << "\tChannels: " << mAlsaRxChannels << " RX, " <<
|
||||
mAlsaTxChannels << " TX" << std::endl;
|
||||
std::cout << "\tSample rate: " << metadata->audio.sample_rate <<
|
||||
std::endl;
|
||||
std::cout << "\tFrames per period: " << mAlsaFramesPerPeriod <<
|
||||
std::endl;
|
||||
std::cout << "\tPeriods per buffer: " << mAlsaBufferPeriods <<
|
||||
std::endl;
|
||||
std::cout << "DEP interface:" << std::endl;
|
||||
std::cout << "\tTx Latency Samples: " << mTxLatencySamples <<
|
||||
std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
mAlsaDevice->stop();
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
alsaPlayback(numPeriods);
|
||||
alsaCapture(numPeriods);
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mRingTx.resize(mAlsaRxChannels);
|
||||
mDanteTxChannels.resize(mAlsaRxChannels);
|
||||
for (unsigned int i = 0; i < mAlsaRxChannels; i++) {
|
||||
mRingTx[i].resize(g_alsa_ring_size);
|
||||
mDanteTxChannels[i] =
|
||||
(int32_t *)mBuffers.getDanteTxChannel(i + g_starting_dante_tx);
|
||||
}
|
||||
|
||||
mRingRx.resize(mAlsaTxChannels);
|
||||
mDanteRxChannels.resize(mAlsaTxChannels);
|
||||
for (unsigned int i = 0; i < mAlsaTxChannels; i++) {
|
||||
mRingRx[i].resize(g_alsa_ring_size);
|
||||
mDanteRxChannels[i] = (const int32_t *)
|
||||
mBuffers.getDanteRxChannel(i + g_starting_dante_rx );
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int)
|
||||
((metadata->time.period_count * mSamplesPerPeriod) %
|
||||
mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int)
|
||||
((metadata->time.period_count * mSamplesPerPeriod +
|
||||
mTxLatencySamples) % mSamplesPerChannel);
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers &mBuffers;
|
||||
unsigned int mTxLatencySamples;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
std::vector<RingBufferT<int32_t>> mRingRx;
|
||||
std::vector<RingBufferT<int32_t>> mRingTx;
|
||||
|
||||
// ALSA specific state
|
||||
std::unique_ptr<AlsaAggregatedDevice> mAlsaDevice;
|
||||
uint32_t mAlsaTxChannels;
|
||||
uint32_t mAlsaRxChannels;
|
||||
uint32_t mAlsaBytesPerSample;
|
||||
uint32_t mAlsaFramesPerPeriod;
|
||||
uint32_t mAlsaBufferPeriods;
|
||||
std::queue<int32_t> fifo_errors;
|
||||
|
||||
void alsaPlayback(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extrema stalls (eg breakpoints while debugging) just
|
||||
// jump to catch up
|
||||
if (numSamples > mSamplesPerChannel) {
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante RX loop and copy data into local ring buffer.
|
||||
if (mDanteRxHeadSamples + numSamples > mSamplesPerChannel) {
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteRxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
for (unsigned int c = 0; c < mAlsaTxChannels; c++) {
|
||||
mRingRx[c].write(mDanteRxChannels[c] + mDanteRxHeadSamples, n1);
|
||||
}
|
||||
mDanteRxHeadSamples = 0;
|
||||
for (unsigned int c = 0; c < mAlsaTxChannels; c++) {
|
||||
mRingRx[c].write(mDanteRxChannels[c] + mDanteRxHeadSamples, n2);
|
||||
}
|
||||
mDanteRxHeadSamples += n2;
|
||||
} else {
|
||||
for (unsigned int c = 0; c < mAlsaTxChannels; c++) {
|
||||
mRingRx[c].write
|
||||
(
|
||||
mDanteRxChannels[c] + mDanteRxHeadSamples, numSamples
|
||||
);
|
||||
}
|
||||
mDanteRxHeadSamples += numSamples;
|
||||
}
|
||||
|
||||
mAlsaDevice->writeFrames(mRingRx);
|
||||
}
|
||||
|
||||
void alsaCapture(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
mAlsaDevice->readFrames(mRingTx);
|
||||
|
||||
static bool started = false;
|
||||
if (mRingTx[0].getAvailableRead() > g_alsa_ring_size/2 &&
|
||||
started == false) {
|
||||
started = true;
|
||||
}
|
||||
|
||||
// Dante
|
||||
// For extrama stalls (eg breakpoints while debugging)
|
||||
// just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel) {
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel) {
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
for (unsigned int c = 0; c < mAlsaRxChannels; c++) {
|
||||
mRingTx[c].read(mDanteTxChannels[c] + mDanteTxHeadSamples, n1);
|
||||
}
|
||||
mDanteTxHeadSamples = 0;
|
||||
for (unsigned int c = 0; c < mAlsaRxChannels; c++) {
|
||||
mRingTx[c].read(mDanteTxChannels[c] + mDanteTxHeadSamples, n2);
|
||||
}
|
||||
mDanteTxHeadSamples += n2;
|
||||
} else {
|
||||
for (unsigned int c = 0; c < mAlsaRxChannels; c++) {
|
||||
mRingTx[c].read((mDanteTxChannels[c] + mDanteTxHeadSamples),
|
||||
numSamples);
|
||||
}
|
||||
mDanteTxHeadSamples += numSamples;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int err;
|
||||
int opt;
|
||||
uint32_t framesPerPeriod = 0;
|
||||
uint32_t bufferPeriods = 0;
|
||||
int txLatencySamples = 0;
|
||||
|
||||
while ((opt = getopt(argc, argv, "b:d:p:r:t:l:c:v")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
g_soundcard = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
g_starting_dante_rx = atoi(optarg);
|
||||
break;
|
||||
case 't':
|
||||
g_starting_dante_tx = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
framesPerPeriod = atoi(optarg);
|
||||
break;
|
||||
case 'b':
|
||||
bufferPeriods = atoi(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
g_alsa_ring_size = atoi(optarg);
|
||||
std::cout << "Using ring buffer size of " << g_alsa_ring_size << std::endl;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "\t-d ALSA Device(s) e.g. 'hw:1,0' or multi devices 'hw:1,0;hw:1,1'" << std::endl;
|
||||
std::cout <<
|
||||
"\t-r Starting Dante RX channel to map - default is 0" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-t Starting Dante TX channel to map - default is 0" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-p ALSA frames per period - default is " <<
|
||||
ALSA_DEFAULT_FRAMES_PER_PERIOD <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-b ALSA periods per buffer - default is " <<
|
||||
ALSA_DEFAULT_BUFFER_PERIODS <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Tx Latency write-back samples - default will be " <<
|
||||
"calculated to correspond to 1ms at the configured Dante " <<
|
||||
"sample rate" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-c Ring buffer size - default is " << g_alsa_ring_size <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
DanteAlsaBridge bridge(buffers, txLatencySamples);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
if (framesPerPeriod > 0) {
|
||||
bridge.setAlsaFramesPerPeriod(framesPerPeriod);
|
||||
}
|
||||
if (bufferPeriods > 0) {
|
||||
bridge.setAlsaBufferPeriods(bufferPeriods);
|
||||
}
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running) {
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect("DanteEP", false);
|
||||
if (result) {
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr <<
|
||||
"Error connecting to shared memory: " <<
|
||||
Dante::SharedMemory::getErrorMessage(result) <<
|
||||
std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
err = bridge.start();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
auto _work = [&bridge](unsigned int numPeriods) {
|
||||
bridge.work(numPeriods);
|
||||
};
|
||||
auto _reset = [&bridge]() { bridge.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
bridge.stop();
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,44 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(DEP_audio)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
# Single library for now
|
||||
add_library(dep_audio STATIC
|
||||
src/DanteBuffers.cpp
|
||||
src/DantePriority.cpp
|
||||
src/DanteRunner.cpp
|
||||
src/versions.c
|
||||
src/posix/DanteSharedMemory.cpp
|
||||
src/posix/DanteTiming.cpp
|
||||
)
|
||||
|
||||
target_sources(dep_audio
|
||||
PRIVATE include_private/versions.h
|
||||
)
|
||||
|
||||
target_include_directories(dep_audio PUBLIC
|
||||
include
|
||||
)
|
||||
|
||||
target_include_directories(dep_audio
|
||||
PRIVATE
|
||||
include_private
|
||||
)
|
||||
set_property(TARGET dep_audio PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_compile_options(dep_audio
|
||||
PRIVATE
|
||||
-Wno-error=nonnull
|
||||
-Wall
|
||||
-Werror
|
||||
-Wno-error=unknown-pragmas
|
||||
-Wno-error=unused-function
|
||||
-Wno-error=strict-aliasing
|
||||
)
|
||||
|
||||
target_compile_definitions(dep_audio
|
||||
PRIVATE NDEBUG
|
||||
)
|
||||
target_link_libraries(dep_audio
|
||||
PUBLIC pthread rt
|
||||
)
|
||||
@@ -0,0 +1,415 @@
|
||||
//
|
||||
// Copyright © 2020-2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
|
||||
/// @file Buffers.hpp
|
||||
/// Audio buffers header file
|
||||
#pragma once
|
||||
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#define DANTE_ENCODING__PCM32 32
|
||||
#define DANTE_ENCODING__FLOAT32 65
|
||||
|
||||
/// If set, the meta-data header, TX and RX channel buffers are in seperate memory segments.
|
||||
/// If not set, the meta-data header and channel buffers are in a single continuous memory segment.
|
||||
#define DANTE_BUFFERS_FLAG__SEPARATE_CHANNEL_MEMORY 0x1
|
||||
|
||||
/// Set by DEP when updating multiple fields in the time struct that need to be read atomically
|
||||
#define DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC 0x10000
|
||||
|
||||
/// Special non-zero value used for header magic
|
||||
#define DANTE_BUFFERS_HDR_MAGIC 0x50525354
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
///
|
||||
/// Overlay structure for the shared memory buffer header block
|
||||
///
|
||||
typedef struct buffer_header
|
||||
{
|
||||
/// Describes the shared memory layout and state
|
||||
struct
|
||||
{
|
||||
/// Set to zero when the shared memory buffer is not yet configured or becomes invalid.
|
||||
/// Set to DANTE_BUFFERS_HDR_MAGIC when the shared memory buffer has been fully set
|
||||
/// up and is valid for use.
|
||||
uint32_t magic_marker;
|
||||
|
||||
/// Total length in bytes of the shared memory. Includes headers and audio channel buffers.
|
||||
uint32_t buffer_length;
|
||||
|
||||
/// Total length in bytes of the metadata header
|
||||
uint32_t metadata_header_length;
|
||||
|
||||
/// Flags used for various buffer operations. See DANTE_BUFFERS_FLAG defines.
|
||||
uint32_t flags;
|
||||
|
||||
/// Offset in bytes to the firxt TX channel buffer from the start of this header
|
||||
uint32_t first_tx_channel_offset_bytes;
|
||||
|
||||
/// Offset in bytes to the firxt RX channel buffer from the start of this header
|
||||
uint32_t first_rx_channel_offset_bytes;
|
||||
|
||||
/// Offset in bytes to the timing object subheader from the start of this header
|
||||
uint32_t timing_object_subheader_offset_bytes;
|
||||
|
||||
/// Incremented at the start and end of a buffer reset operation
|
||||
uint32_t reset_count;
|
||||
} metadata;
|
||||
|
||||
/// Describes the audio data stored in the channel buffers
|
||||
struct
|
||||
{
|
||||
/// When non-zero, this value is the currently configured device sample rate.
|
||||
/// When zero, it means the sample rate is in the process of being changed.
|
||||
uint32_t sample_rate;
|
||||
|
||||
/// Encoding of the audio data in the channel buffers. Currently always DANTE_ENCODING__PCM32.
|
||||
uint32_t encoding;
|
||||
|
||||
/// The total number of samples that can be stored in the buffers for each channel
|
||||
uint32_t samples_per_channel;
|
||||
|
||||
/// Total size in bytes for each channel buffer
|
||||
uint32_t bytes_per_channel;
|
||||
|
||||
/// Total number of available TX channels (minimum of configured and activated number of channels)
|
||||
uint32_t num_tx_channels;
|
||||
|
||||
/// Total number of available RX channels (minimum of configured and activated number of channels)
|
||||
uint32_t num_rx_channels;
|
||||
uint32_t pad7;
|
||||
uint32_t pad8;
|
||||
} audio;
|
||||
|
||||
/// time fields
|
||||
struct
|
||||
{
|
||||
/// The epoch represents the PTP time at which the device last achieved PTP synchronisation.
|
||||
/// To get the complete epoch time, convert @ref epoch_seconds and @ref epoch_samples into the same units
|
||||
/// and add them together. For example, to get epoch time in samples:
|
||||
/// (epoch_seconds * sample_rate) + epoch_samples
|
||||
/// @note The epoch is initially zero when the device first starts up before it has achieved
|
||||
/// PTP sync for the first time. Subsequently the epoch values are only updated upon each sync
|
||||
/// event but not on sync loss. That is, this value cannot be used to determine the
|
||||
/// device's current PTP sync state apart from the initial state (yet to sync for the first time).
|
||||
uint32_t epoch_seconds;
|
||||
|
||||
/// The sub-second portion of the epoch in unit of samples. See @ref epoch_seconds for more details.
|
||||
uint32_t epoch_samples;
|
||||
|
||||
/// This value gives the number of samples that will elapse before DEP signals to the application
|
||||
/// that there is audio data available. A lower value can allow the application to wake up
|
||||
/// more often to process audio data but with the tradeoff that CPU load will increase.
|
||||
uint32_t samples_per_period;
|
||||
|
||||
/// This value is incremented every time a period has elapsed.
|
||||
/// Applications can use this value to determine how many periods of audio data can be read/written
|
||||
/// since the last time it woke up.
|
||||
uint64_t period_count;
|
||||
|
||||
/// Currently unused
|
||||
uint32_t clock_drift_ppb;
|
||||
|
||||
/// The local monotonic time at the current period_count value.
|
||||
/// The montonic clock source is CLOCK_MONOTONIC_RAW.
|
||||
/// (monotonic, period_count) timing pair values can be used to track the rate change
|
||||
/// of the local time domain against the PTP time domain.
|
||||
/// @note The (monotonic, period_count) values are not updated atomically. But such updates are
|
||||
/// bracketed by setting DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC prior to starting an update and cleared
|
||||
/// when both fields have been updated. See the DanteTiming example code for a guide to how
|
||||
/// these two timing fields can be read to ensure correctness and consistency.
|
||||
uint64_t monotonic;
|
||||
} time;
|
||||
|
||||
} buffer_header_t;
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Timing object subheader
|
||||
//----------------------------------------------------------
|
||||
|
||||
#define TIMING_OBJECT_NAME_LENGTH 256
|
||||
|
||||
enum
|
||||
{
|
||||
/// No timing synchronisation
|
||||
TIMING_OBJECT_TYPE__NONE = 0,
|
||||
|
||||
/// Posix semaphore or Windows Event.
|
||||
/// DEP signals the semaphore/event after each period. See buffer_header_t.time.samples_per_period for more details.
|
||||
TIMING_OBJECT_TYPE__SIGNAL_EVENT = 1
|
||||
};
|
||||
|
||||
/// Timing object subheader
|
||||
///
|
||||
/// Used to describe the timing synchronisation mechanism used between DEP and the application
|
||||
typedef struct timing_object_subheader
|
||||
{
|
||||
/// Length in bytes of this subheader
|
||||
uint32_t subheader_length_bytes;
|
||||
|
||||
/// The timing object type. Currently only TIMING_OBJECT_TYPE__SIGNAL_EVENT is used.
|
||||
uint32_t object_type;
|
||||
|
||||
/// Name of the timing object. The meaning of this name is dependent on the object type.
|
||||
/// If TIMING_OBJECT_TYPE__SIGNAL_EVENT is in use, this gives the name of the Posix semaphore
|
||||
/// or Windows signal event.
|
||||
char object_name[TIMING_OBJECT_NAME_LENGTH];
|
||||
} timing_object_subheader_t;
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Platform-specific shared-memory implementation
|
||||
//----------------------------------------------------------
|
||||
|
||||
class SharedMemory
|
||||
{
|
||||
public:
|
||||
SharedMemory();
|
||||
~SharedMemory();
|
||||
|
||||
int connect(const std::string & name, bool globalNamespace);
|
||||
void disconnect();
|
||||
|
||||
void * get() const;
|
||||
|
||||
static std::string getErrorMessage(int err);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl * mImpl;
|
||||
};
|
||||
|
||||
///
|
||||
// The main buffer handler class
|
||||
///
|
||||
class Buffers
|
||||
{
|
||||
public:
|
||||
Buffers();
|
||||
~Buffers();
|
||||
|
||||
/// Attempt to connect to the underlying shared memory buffer(s)
|
||||
///
|
||||
/// @param name Name of the shared memory buffer to connect to
|
||||
/// @param globalNamespace true if the name is in a global namespace and false otherwise.
|
||||
/// Only relevant for Windows platforms.
|
||||
/// @return 0 on success, otherwise a platform-specific error code
|
||||
int connect(const std::string & name, bool globalNamespace);
|
||||
|
||||
/// Disconnect from the underlying shared memory buffer(s)
|
||||
void disconnect();
|
||||
|
||||
/// Check whether currently connected to underlying shared memory buffer(s)
|
||||
/// @return true if connected and false otherwise
|
||||
bool isConnected() const { return mIsConnected; }
|
||||
|
||||
/// Check whether shared memory is within a global name space.
|
||||
///
|
||||
/// @note Only relevant for Windows platforms.
|
||||
/// @return true if a global name space is in use and false otherwise.
|
||||
bool isGlobalNamespace() const;
|
||||
|
||||
/// Get a pointer to the shared memory metadata block
|
||||
/// @return pointer to the start of the metadata block
|
||||
const volatile buffer_header_t * getHeader() const;
|
||||
|
||||
/// Get a pointer to the shared memory timing object subheader
|
||||
/// @return pointer to the start of the timing object subheader
|
||||
const timing_object_subheader_t * getTimingObjectSubheader() const;
|
||||
|
||||
/// Get audio buffer for the given Dante TX channel
|
||||
///
|
||||
/// Non-interleaved samples for the given TX channel can be written into the audio buffer
|
||||
/// @param index Zero based index of given Dante TX channel
|
||||
/// @return pointer to start of audio buffer for given index
|
||||
void * getDanteTxChannel(unsigned int index) const;
|
||||
|
||||
/// Get audio buffer for the given Dante RX channel
|
||||
///
|
||||
/// Non-interleaved samples for the given RX channel can be read from the audio buffer
|
||||
/// @param index Zero based index of given Dante RX channel
|
||||
/// @return pointer to start of audio buffer for given index
|
||||
void * getDanteRxChannel(unsigned int index) const;
|
||||
|
||||
private:
|
||||
bool mGlobalNamespace;
|
||||
SharedMemory mSharedMemory;
|
||||
SharedMemory mDanteTxSharedMemory;
|
||||
SharedMemory mDanteRxSharedMemory;
|
||||
|
||||
// cached data
|
||||
const volatile buffer_header_t * mHeader;
|
||||
const timing_object_subheader_t * mTimingObjectSubheader;
|
||||
std::vector<void *> mDanteTxChannels;
|
||||
std::vector<void *> mDanteRxChannels;
|
||||
|
||||
bool mIsConnected;
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Platform-specific helper class that encapsulates run loop functionality
|
||||
//----------------------------------------------------------
|
||||
|
||||
class Timing
|
||||
{
|
||||
public:
|
||||
Timing();
|
||||
~Timing();
|
||||
|
||||
int open(const timing_object_subheader_t * subheader, bool globalNamespace);
|
||||
void close();
|
||||
|
||||
int wait();
|
||||
|
||||
static std::string getErrorMessage(int err);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl * mImpl;
|
||||
};
|
||||
|
||||
typedef std::function<void (bool)> RunnerActiveChangedFn;
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Helper class that encapsulates run loop functionality
|
||||
//----------------------------------------------------------
|
||||
|
||||
class Runner
|
||||
{
|
||||
public:
|
||||
Runner(Buffers & buffers);
|
||||
~Runner();
|
||||
|
||||
// run the main processing loop.
|
||||
// buffers must be connected.
|
||||
// Runs until 'running' is set to false or the buffer magic marker is cleared,
|
||||
// indicating that a disconnection is required.
|
||||
int run(bool & running, std::function<void(unsigned int n)> transferFn, std::function<void()> epochResetFn);
|
||||
|
||||
bool isActive() const;
|
||||
void setActiveChangedFn(RunnerActiveChangedFn fn) { mActiveChangedFn = fn; }
|
||||
|
||||
private:
|
||||
void setActive(bool active);
|
||||
|
||||
Buffers & mBuffers;
|
||||
const volatile buffer_header_t * mHeader;
|
||||
const timing_object_subheader_t * mTimingObjectSubheader;
|
||||
uint16_t mResetCount;
|
||||
uint64_t mPeriodCount;
|
||||
uint64_t mLastActiveMonotonicValue;
|
||||
bool mIsActive;
|
||||
RunnerActiveChangedFn mActiveChangedFn;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Memory Barrier helper function
|
||||
//----------------------------------------------------------
|
||||
|
||||
/*
|
||||
* The cpu and the compiler can reorder memory operations for the sake of optimisation.
|
||||
* However, this can break the synchronisation between DEP and the external application,
|
||||
* where we expect reads and writes to the shared memory to happen in a certain order.
|
||||
*
|
||||
* An acquire barrier ensures that any reads or writes sequenced after the barrier
|
||||
* are ordered after any reads before the barrier. For example, DEP writes to the RX
|
||||
* buffers and then updates the header.audio.period_count. Consider this pseudocode for
|
||||
* reading from the buffer:
|
||||
* ```
|
||||
* while(period_count == previous_period_count);
|
||||
* memory_barrier_acquire();
|
||||
* read_audio_data();
|
||||
* ```
|
||||
* The memory barrier here guarantees that the data read in read_audio_data() will be
|
||||
* at least as recent as the write to the period_count.
|
||||
*
|
||||
* This method is one way to implement an acquire memory barrier in a compiler and
|
||||
* architecture agnostic way.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4834) // ignore NODISCARD on dummyvar.load
|
||||
#endif
|
||||
static inline void memory_barrier_acquire() {
|
||||
volatile std::atomic<int> dummy_var;
|
||||
dummy_var.store(1);
|
||||
dummy_var.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Platform-specific helper functions
|
||||
//----------------------------------------------------------
|
||||
|
||||
uint64_t getMonotonicRate();
|
||||
|
||||
uint64_t getMonotonicValue();
|
||||
|
||||
void sleepMs(unsigned int ms);
|
||||
|
||||
} // namespace Dante
|
||||
//
|
||||
// Copyright © 2020-2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// Priority.hpp
|
||||
// Priority modification handling
|
||||
//
|
||||
#pragma once
|
||||
|
||||
void setDantePriority(const char* progName);
|
||||
void cleanupDantePriority();
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
#pragma once
|
||||
#ifndef DEP_AUDIO_VERSIONS_H
|
||||
#define DEP_AUDIO_VERSIONS_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
extern const char * DEP_AUDIO_HASH_FULL ;
|
||||
extern const char * DEP_AUDIO_HASH_SHORT ;
|
||||
extern const bool DEP_AUDIO_STATE_CHANGES ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH_SHORT ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION ;
|
||||
extern const bool DEP_AUDIO_COMMITS_SINCE_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_TYPE ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_FULL ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MAJOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MINOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_PATCH ;
|
||||
extern const char * DEP_AUDIO_VERSION_SUFFIX ;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,61 @@
|
||||
CXX ?= g++
|
||||
ALSA_SRC_NAME ?= AlsaDevice
|
||||
|
||||
# path #
|
||||
SRC_PATH = src
|
||||
BUILD_PATH = build
|
||||
|
||||
# executable #
|
||||
LIB_NAME = libdep_audio.a
|
||||
|
||||
# extensions #
|
||||
SRC_EXT = cpp
|
||||
|
||||
SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-)
|
||||
OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o)
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
|
||||
# flags #
|
||||
COMPILE_FLAGS = -Wno-error=nonnull -fPIC -Wall -Werror -Wno-error=unknown-pragmas -Wno-error=unused-function -Wno-error=strict-aliasing -O2 -g -DNDEBUG -fPIC -std=gnu++11
|
||||
INCLUDES = -I include
|
||||
|
||||
ifneq (on, $(ALSA))
|
||||
SOURCES := $(filter-out src/$(ALSA_SRC_NAME).cpp,$(SOURCES))
|
||||
OBJECTS := $(filter-out build/$(ALSA_SRC_NAME).o,$(OBJECTS))
|
||||
DEP := $(filter-out build/$(ALSA_SRC_NAME).d,$(DEPS))
|
||||
else
|
||||
INCLUDES := $(INCLUDES) -I$(ALSA_INSTALL_PATH)/include
|
||||
endif
|
||||
|
||||
.PHONY: default_target
|
||||
default_target: release
|
||||
|
||||
.PHONY: release
|
||||
release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS)
|
||||
release: dirs
|
||||
@$(MAKE) all
|
||||
|
||||
.PHONY: dirs
|
||||
dirs:
|
||||
@echo "Creating directories"
|
||||
@mkdir -p $(dir $(OBJECTS))
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@$(RM) -r $(BUILD_PATH)
|
||||
@$(RM) -r $(LIB_NAME)
|
||||
|
||||
# checks the executable and symlinks to the output
|
||||
.PHONY: all
|
||||
all: $(LIB_NAME)
|
||||
|
||||
# Creation of the executable
|
||||
$(LIB_NAME): $(OBJECTS)
|
||||
ar -r $(LIB_NAME) $(OBJECTS)
|
||||
|
||||
# Source file rules
|
||||
# After the first compilation they will be joined with the rules from the
|
||||
# dependency files to provide header dependencies
|
||||
$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT)
|
||||
@echo "Compiling: $< -> $@"
|
||||
$(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@
|
||||
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteBuffers.cpp
|
||||
// Audio buffers implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
Buffers::Buffers() : mGlobalNamespace(), mSharedMemory(), mDanteTxSharedMemory(), mDanteRxSharedMemory(), mHeader(), mTimingObjectSubheader(), mDanteTxChannels(), mDanteRxChannels(), mIsConnected(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Buffers::~Buffers()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// Attempt to connect to the underlying shared memory buffer(s)
|
||||
// returns 0 on success, otherwise a platform-specific error code
|
||||
int Buffers::connect(const std::string & name, bool globalNamespace)
|
||||
{
|
||||
if (mHeader)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return ERROR_INVALID_STATE;
|
||||
#else
|
||||
return EALREADY;
|
||||
#endif
|
||||
}
|
||||
mGlobalNamespace = globalNamespace;
|
||||
int result = mSharedMemory.connect(name, false);
|
||||
if (result)
|
||||
{
|
||||
disconnect();
|
||||
return result;
|
||||
}
|
||||
uint8_t * buf8 = (uint8_t *) mSharedMemory.get();
|
||||
mHeader = (const volatile buffer_header_t *) mSharedMemory.get();
|
||||
if (mHeader->metadata.timing_object_subheader_offset_bytes)
|
||||
{
|
||||
mTimingObjectSubheader = (timing_object_subheader_t *) (buf8 + mHeader->metadata.timing_object_subheader_offset_bytes);
|
||||
}
|
||||
mDanteTxChannels.resize(mHeader->audio.num_tx_channels);
|
||||
mDanteRxChannels.resize(mHeader->audio.num_rx_channels);
|
||||
uint8_t * tx0 = nullptr;
|
||||
uint8_t * rx0 = nullptr;
|
||||
if (mHeader->metadata.flags & DANTE_BUFFERS_FLAG__SEPARATE_CHANNEL_MEMORY)
|
||||
{
|
||||
result = mDanteTxSharedMemory.connect(name + "Tx", false);
|
||||
if (result)
|
||||
{
|
||||
disconnect();
|
||||
return result;
|
||||
}
|
||||
result = mDanteRxSharedMemory.connect(name + "Rx", false);
|
||||
if (result)
|
||||
{
|
||||
disconnect();
|
||||
return result;
|
||||
}
|
||||
tx0 = ((uint8_t *) mDanteTxSharedMemory.get()) + mHeader->metadata.first_tx_channel_offset_bytes;
|
||||
rx0 = ((uint8_t *) mDanteRxSharedMemory.get()) + mHeader->metadata.first_rx_channel_offset_bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
tx0 = buf8 + mHeader->metadata.first_tx_channel_offset_bytes;
|
||||
rx0 = buf8 + mHeader->metadata.first_rx_channel_offset_bytes;
|
||||
}
|
||||
for (unsigned int c = 0; c < mHeader->audio.num_tx_channels; c++)
|
||||
{
|
||||
mDanteTxChannels[c] = (void *) (tx0 + c * mHeader->audio.bytes_per_channel);
|
||||
}
|
||||
for (unsigned int c = 0; c < mHeader->audio.num_rx_channels; c++)
|
||||
{
|
||||
mDanteRxChannels[c] = (void *) (rx0 + c * mHeader->audio.bytes_per_channel);
|
||||
}
|
||||
mIsConnected = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Disconnect from the underlying shared memory buffer(s)
|
||||
void Buffers::disconnect()
|
||||
{
|
||||
mSharedMemory.disconnect();
|
||||
mDanteTxSharedMemory.disconnect();
|
||||
mDanteRxSharedMemory.disconnect();
|
||||
|
||||
mHeader = nullptr;
|
||||
mTimingObjectSubheader = nullptr;
|
||||
mDanteTxChannels.clear();
|
||||
mDanteRxChannels.clear();
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
bool Buffers::isGlobalNamespace() const
|
||||
{
|
||||
return mGlobalNamespace;
|
||||
}
|
||||
|
||||
// Get a pointer to the shared memory metadata block
|
||||
const volatile buffer_header_t * Buffers::getHeader() const
|
||||
{
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
const timing_object_subheader_t * Buffers::getTimingObjectSubheader() const
|
||||
{
|
||||
return mTimingObjectSubheader;
|
||||
}
|
||||
|
||||
// Get a pointer to the start of the audio buffer for the given TX channel
|
||||
void * Buffers::getDanteTxChannel(unsigned int index) const
|
||||
{
|
||||
return mDanteTxChannels[index];
|
||||
}
|
||||
|
||||
// Get a pointer to the start of the audio buffer for the given RX channel
|
||||
void * Buffers::getDanteRxChannel(unsigned int index) const
|
||||
{
|
||||
return mDanteRxChannels[index];
|
||||
}
|
||||
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright © 2021-2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DantePriority.cpp
|
||||
// Priority modification implementation
|
||||
//
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "dante/Priority.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void setDantePriority(const char* progName)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
(void)(progName);
|
||||
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
timeBeginPeriod(1);
|
||||
#else
|
||||
// check if effective id is root user
|
||||
if (geteuid() == 0)
|
||||
{
|
||||
int policy;
|
||||
struct sched_param param;
|
||||
pthread_getschedparam(pthread_self(), &policy, ¶m);
|
||||
|
||||
// This thread should have a priority that is higher than the parent thread. Otherwise there is a chance it won't
|
||||
// be allowed to service the Dante buffers often enough, and this can cause a deadlock.
|
||||
// If the parent has a realtime scheduling policy, increase the priority if possible.
|
||||
// If the parent doesn't have realtime scheduling, give this thread the lowest realtime priority.
|
||||
if ((policy == SCHED_FIFO) || (policy == SCHED_RR))
|
||||
{
|
||||
if (param.sched_priority < sched_get_priority_max(policy))
|
||||
{
|
||||
param.sched_priority++;
|
||||
int ret = pthread_setschedparam(pthread_self(), policy, ¶m);
|
||||
if (ret != 0) std::cerr << "ERROR: increasing Dante thread priority failed. Error number: " << ret << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
policy = SCHED_FIFO;
|
||||
param.sched_priority = sched_get_priority_min(policy);
|
||||
|
||||
int ret = pthread_setschedparam(pthread_self(), policy, ¶m);
|
||||
if (ret != 0) std::cerr << "ERROR: setting Dante thread scheduling policy failed. Error number: " << ret << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
progName = progName != NULL ? progName : "The application";
|
||||
std::cerr << "WARNING: " << progName << " has not been run as root so thread priority will not be changed to realtime." << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cleanupDantePriority()
|
||||
{
|
||||
#if defined(WIN32)
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
}
|
||||
//
|
||||
// Copyright © 2021-2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteRunner.cpp
|
||||
// Audio runner implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
#define RUNNER_INACTIVE_MAX_TIME_IN_SECS 1
|
||||
|
||||
Runner::Runner(Buffers & buffers) :
|
||||
mBuffers(buffers),
|
||||
mHeader(),
|
||||
mTimingObjectSubheader(),
|
||||
mResetCount(),
|
||||
mPeriodCount(),
|
||||
mLastActiveMonotonicValue(),
|
||||
mIsActive(),
|
||||
mActiveChangedFn()
|
||||
{
|
||||
}
|
||||
|
||||
Runner::~Runner()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool Runner::isActive() const
|
||||
{
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
void Runner::setActive(bool active)
|
||||
{
|
||||
if (mIsActive != active)
|
||||
{
|
||||
mIsActive = active;
|
||||
if (mActiveChangedFn)
|
||||
{
|
||||
mActiveChangedFn(active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Runner::run(bool & running, std::function<void(unsigned int n)> transferFn, std::function<void()> resetFn)
|
||||
{
|
||||
uint64_t monotonicRate = getMonotonicRate();
|
||||
uint64_t monotonicValue = getMonotonicValue();
|
||||
uint64_t lastMonotonicValue = 0;
|
||||
uint64_t monotonicSeconds = 0;
|
||||
uint64_t lastMonotonicSeconds = 0;
|
||||
uint64_t count = 0;
|
||||
uint64_t max = 0;
|
||||
|
||||
mHeader = mBuffers.getHeader();
|
||||
if (!mHeader)
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
mTimingObjectSubheader = mBuffers.getTimingObjectSubheader();
|
||||
|
||||
Timing timing;
|
||||
int result = timing.open(mTimingObjectSubheader, mBuffers.isGlobalNamespace());
|
||||
if (result)
|
||||
{
|
||||
std::cerr << "Error opening timing object: " << Timing::getErrorMessage(result) << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool resetNeeded = true;
|
||||
|
||||
mResetCount = mHeader->metadata.reset_count;
|
||||
mPeriodCount = mHeader->time.period_count;
|
||||
|
||||
while (running && mHeader->metadata.magic_marker)
|
||||
{
|
||||
if (mResetCount != mHeader->metadata.reset_count || resetNeeded)
|
||||
{
|
||||
resetNeeded = false;
|
||||
|
||||
mResetCount = mHeader->metadata.reset_count;
|
||||
while (mResetCount & 0x1)
|
||||
{
|
||||
timing.wait();
|
||||
mResetCount = mHeader->metadata.reset_count;
|
||||
}
|
||||
// Ensure the read to period count is after the read from reset_count
|
||||
memory_barrier_acquire();
|
||||
|
||||
mPeriodCount = mHeader->time.period_count;
|
||||
if (resetFn) resetFn();
|
||||
|
||||
memory_barrier_acquire();
|
||||
// Ensure any reads from the header in resetFn come before checking reset_count again.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mPeriodCount == mHeader->time.period_count)
|
||||
{
|
||||
timing.wait();
|
||||
}
|
||||
|
||||
monotonicValue = getMonotonicValue();
|
||||
|
||||
if (mPeriodCount < mHeader->time.period_count)
|
||||
{
|
||||
uint64_t n = mHeader->time.period_count - mPeriodCount;
|
||||
|
||||
// Ensure period count is read before reading audio data
|
||||
memory_barrier_acquire();
|
||||
|
||||
if (transferFn) transferFn(static_cast<unsigned int>(n));
|
||||
mPeriodCount += n;
|
||||
|
||||
mLastActiveMonotonicValue = monotonicValue;
|
||||
setActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((monotonicValue - mLastActiveMonotonicValue) > (getMonotonicRate() * RUNNER_INACTIVE_MAX_TIME_IN_SECS))
|
||||
{
|
||||
setActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
uint64_t delta = monotonicValue - lastMonotonicValue;
|
||||
if (max < delta) max = delta;
|
||||
monotonicSeconds = monotonicValue / monotonicRate;
|
||||
|
||||
if (monotonicSeconds != lastMonotonicSeconds)
|
||||
{
|
||||
if (lastMonotonicSeconds)
|
||||
{
|
||||
//uint64_t maxUs = (max * 1000000) / monotonicRate;
|
||||
//std::cerr << lastMonotonicSeconds << ": count=" << count << " max=" << max << " (" << maxUs << "us)" << std::endl;
|
||||
}
|
||||
lastMonotonicSeconds = monotonicSeconds;
|
||||
count = 0;
|
||||
max = 0;
|
||||
}
|
||||
lastMonotonicValue = monotonicValue;
|
||||
}
|
||||
timing.close();
|
||||
mHeader = nullptr;
|
||||
mTimingObjectSubheader = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteSharedMemory.cpp
|
||||
// Audio shared memory implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
static const size_t kNameLength = 31; // PSHMNAMLEN
|
||||
static const mode_t kMode = 0666; // S_IRUSR | S_IWUSR | S_IRGRP | S_IRGRP;
|
||||
|
||||
|
||||
class SharedMemory::Impl
|
||||
{
|
||||
public:
|
||||
Impl() : mName(), mFileDesc(), mSize(), mData() {}
|
||||
~Impl() { disconnect(); }
|
||||
|
||||
int connect(const std::string & name, bool globalNamespace)
|
||||
{
|
||||
if (name == "" || name.size() > kNameLength)
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (mData)
|
||||
{
|
||||
return EALREADY;
|
||||
}
|
||||
|
||||
// Get ID for shared mem config block
|
||||
int fileDesc = shm_open(name.c_str(), O_RDWR, kMode);
|
||||
if (fileDesc < 0)
|
||||
{
|
||||
int err = errno;
|
||||
return err;
|
||||
}
|
||||
mFileDesc = fileDesc;
|
||||
mName = name;
|
||||
|
||||
struct stat s;
|
||||
if (fstat(mFileDesc, &s) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
disconnect();
|
||||
return err;
|
||||
}
|
||||
if (s.st_size == 0)
|
||||
{
|
||||
// Creator has not yet resized the memory, try again later
|
||||
disconnect();
|
||||
return EAGAIN;
|
||||
}
|
||||
mSize = s.st_size;
|
||||
|
||||
// Map the memory
|
||||
void * raw = mmap(NULL, mSize, (PROT_READ | PROT_WRITE), MAP_SHARED, mFileDesc, 0);
|
||||
if (raw == MAP_FAILED)
|
||||
{
|
||||
// Creator has not yet resized, try again later
|
||||
int err = errno;
|
||||
disconnect();
|
||||
return err;
|
||||
}
|
||||
mData = raw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
if (mData)
|
||||
{
|
||||
if (mSize)
|
||||
{
|
||||
munmap(mData, mSize);
|
||||
}
|
||||
mData = NULL;
|
||||
}
|
||||
|
||||
mSize = 0;
|
||||
|
||||
if (mFileDesc)
|
||||
{
|
||||
close(mFileDesc);
|
||||
mFileDesc = -1;
|
||||
}
|
||||
|
||||
if (mName.size())
|
||||
{
|
||||
mName = "";
|
||||
}
|
||||
}
|
||||
|
||||
void * get() const { return mData; }
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
int mFileDesc;
|
||||
size_t mSize;
|
||||
void * mData;
|
||||
};
|
||||
|
||||
SharedMemory::SharedMemory()
|
||||
{
|
||||
mImpl = new Impl();
|
||||
}
|
||||
|
||||
SharedMemory::~SharedMemory()
|
||||
{
|
||||
delete mImpl;
|
||||
}
|
||||
|
||||
int SharedMemory::connect(const std::string & name, bool globalNamespace)
|
||||
{
|
||||
return mImpl->connect(name, globalNamespace);
|
||||
}
|
||||
|
||||
void SharedMemory::disconnect()
|
||||
{
|
||||
mImpl->disconnect();
|
||||
}
|
||||
|
||||
void * SharedMemory::get() const
|
||||
{
|
||||
return mImpl->get();
|
||||
}
|
||||
|
||||
std::string SharedMemory::getErrorMessage(int err)
|
||||
{
|
||||
std::cerr << "ERROR is " << err << std::endl;
|
||||
if (!err) return std::string();
|
||||
|
||||
char tmp[1024];
|
||||
|
||||
// POSIX and GNU versions have different return types so we have to deal with both
|
||||
#if (__APPLE__ || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE))
|
||||
int result = strerror_r(err, tmp, sizeof(tmp));
|
||||
if (result == 0)
|
||||
return std::string();
|
||||
|
||||
return std::string(tmp);
|
||||
#else
|
||||
char * result = strerror_r(err, tmp, sizeof(tmp));
|
||||
return std::string(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
}; // namespace Dante
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteTiming.cpp
|
||||
// Audio timing implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <semaphore.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
// Helper functions
|
||||
uint64_t getMonotonicRate()
|
||||
{
|
||||
return 1000000000ULL;
|
||||
}
|
||||
|
||||
uint64_t getMonotonicValue()
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||
uint64_t x;
|
||||
x = ts.tv_sec;
|
||||
x *= 1000000000ULL;
|
||||
x += ts.tv_nsec;
|
||||
return x;
|
||||
}
|
||||
|
||||
void sleepMs(unsigned int ms)
|
||||
{
|
||||
usleep(ms * 1000);
|
||||
}
|
||||
|
||||
class Timing::Impl
|
||||
{
|
||||
public:
|
||||
Impl() : mType(TIMING_OBJECT_TYPE__NONE), mSemaphore(SEM_FAILED) {}
|
||||
~Impl() { close(); }
|
||||
|
||||
|
||||
int open(const timing_object_subheader_t * subheader, bool globalNamespace)
|
||||
{
|
||||
mType = (subheader ? subheader->object_type : TIMING_OBJECT_TYPE__NONE);
|
||||
std::string name = (subheader ? subheader->object_name : "");
|
||||
|
||||
if (mType == TIMING_OBJECT_TYPE__SIGNAL_EVENT)
|
||||
{
|
||||
mSemaphore = sem_open(name.c_str(), 0);
|
||||
if (mSemaphore == SEM_FAILED)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (mSemaphore != SEM_FAILED)
|
||||
{
|
||||
sem_close(mSemaphore);
|
||||
mSemaphore = SEM_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int wait()
|
||||
{
|
||||
switch (mType)
|
||||
{
|
||||
case TIMING_OBJECT_TYPE__SIGNAL_EVENT:
|
||||
{
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
ts.tv_sec++;
|
||||
if (sem_timedwait(mSemaphore, &ts) == -1)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
sleepMs(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mType;
|
||||
sem_t * mSemaphore;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
Timing::Timing()
|
||||
{
|
||||
mImpl = new Impl();
|
||||
}
|
||||
|
||||
Timing::~Timing()
|
||||
{
|
||||
if (mImpl)
|
||||
{
|
||||
delete mImpl;
|
||||
mImpl = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
int Timing::open(const timing_object_subheader_t * subheader, bool globalNamespace)
|
||||
{
|
||||
return mImpl->open(subheader, globalNamespace);
|
||||
}
|
||||
|
||||
void Timing::close()
|
||||
{
|
||||
mImpl->close();
|
||||
}
|
||||
|
||||
int Timing::wait()
|
||||
{
|
||||
return mImpl->wait();
|
||||
}
|
||||
|
||||
std::string Timing::getErrorMessage(int err)
|
||||
{
|
||||
if (!err) return std::string();
|
||||
|
||||
char tmp[1024];
|
||||
|
||||
// POSIX and GNU versions have different return types so we have to deal with both
|
||||
#if (__APPLE__ || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE))
|
||||
int result = strerror_r(err, tmp, sizeof(tmp));
|
||||
if (result == 0)
|
||||
return std::string();
|
||||
|
||||
return std::string(tmp);
|
||||
#else
|
||||
char * result = strerror_r(err, tmp, sizeof(tmp));
|
||||
return std::string(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "versions.h"
|
||||
|
||||
const char * DEP_AUDIO_HASH_FULL = "2cc23e7373f9c2bd0b126013f6361482d18e963e";
|
||||
const char * DEP_AUDIO_HASH_SHORT = "2cc23e73";
|
||||
const bool DEP_AUDIO_STATE_CHANGES = 0 ? true : false;
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_HASH = "0e39aa90f4730d09407991125de47b946b9d788b";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_HASH_SHORT = "0e39aa90";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG = "dep_audio_buffers_v1.0.0";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_VERSION = "1.0.0";
|
||||
const bool DEP_AUDIO_COMMITS_SINCE_COMPONENT_TAG = 1 ? true : false;
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_VERSION_TYPE = "dev";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_VERSION_FULL = "1.0.0-dev";
|
||||
const unsigned int DEP_AUDIO_VERSION_MAJOR = 1;
|
||||
const unsigned int DEP_AUDIO_VERSION_MINOR = 0;
|
||||
const unsigned int DEP_AUDIO_VERSION_PATCH = 0;
|
||||
const char * DEP_AUDIO_VERSION_SUFFIX = "";
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dinfo.cpp
|
||||
// Buffer information dump example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "versions.h"
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#define PRINTOUT_WIDTH 46
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signalHandler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> void printPair(T1 t1, T2 t2)
|
||||
{
|
||||
std::cout << " " << std::left << std::setw(30) << std::setfill(' ') << t1 << t2 << std::endl;
|
||||
}
|
||||
|
||||
class DanteInformation
|
||||
{
|
||||
public:
|
||||
DanteInformation(Dante::Buffers & buffers) : mBuffers(buffers) {}
|
||||
|
||||
void work(unsigned int numPeriods) {}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto header = mBuffers.getHeader();
|
||||
|
||||
std::cout << std::string(PRINTOUT_WIDTH, '#') << std::endl;
|
||||
printPair("Reset count:", header->metadata.reset_count);
|
||||
printPair("", "");
|
||||
printPair("Audio Settings", "");
|
||||
printPair("Sample rate:", header->audio.sample_rate);
|
||||
printPair("Bits per sample:", header->audio.encoding);
|
||||
printPair("Samples per channel:", header->audio.samples_per_channel);
|
||||
printPair("Receiver channels:", header->audio.num_rx_channels);
|
||||
printPair("Transmitter channels:", header->audio.num_tx_channels);
|
||||
printPair("Bytes per channel:", header->audio.bytes_per_channel);
|
||||
printPair("", "");
|
||||
printPair("Timing Settings", "");
|
||||
printPair("Epoch seconds:", header->time.epoch_seconds);
|
||||
printPair("Epoch samples:", header->time.epoch_samples);
|
||||
printPair("Samples per period:", header->time.samples_per_period);
|
||||
printPair("Period count:", header->time.period_count);
|
||||
printPair("Clock drift ppb:", header->time.clock_drift_ppb);
|
||||
std::cout << std::string(PRINTOUT_WIDTH, '#') << std::endl;
|
||||
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
};
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "s:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
|
||||
#endif
|
||||
|
||||
DanteInformation danteInformation(buffers);
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&danteInformation](unsigned int numPeriods) { danteInformation.work(numPeriods); };
|
||||
auto _reset = [&danteInformation]() { danteInformation.reset(); };
|
||||
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteLoopback.cpp
|
||||
// Audio loopback example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
class Loopback
|
||||
{
|
||||
public:
|
||||
Loopback(Dante::Buffers & buffers, int txLatencySamples)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), mSamplesPerPeriod(), mSamplesPerChannel(),
|
||||
mDanteTxHeadSamples(), mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mNumLoopbackChannels()
|
||||
{}
|
||||
~Loopback() {}
|
||||
|
||||
// Unrolled RX and TX
|
||||
void workUnwrappedDanteRxDanteTx(unsigned int numSamples)
|
||||
{
|
||||
assert(mDanteTxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
assert(mDanteRxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
//std::cerr << "Transferring " << numSamples << " from " << mReadHead << " to " << mWriteHead << std::endl;
|
||||
unsigned int numBytes = numSamples * sizeof(int32_t);
|
||||
for (unsigned int c = 0; c < mNumLoopbackChannels; c++)
|
||||
{
|
||||
// We copy Dante RX -> Dante RX
|
||||
memcpy(mDanteTxChannels[c] + mDanteTxHeadSamples, mDanteRxChannels[c] + mDanteRxHeadSamples, numBytes);
|
||||
}
|
||||
mDanteTxHeadSamples += numSamples;
|
||||
mDanteRxHeadSamples += numSamples;
|
||||
}
|
||||
|
||||
void workUnwrappedDanteRx(unsigned int numSamples)
|
||||
{
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
workUnwrappedDanteRxDanteTx(n1);
|
||||
assert(mDanteTxHeadSamples == mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = 0;
|
||||
workUnwrappedDanteRxDanteTx(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
workUnwrappedDanteRxDanteTx(numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante RX loop
|
||||
if (mDanteRxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteRxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
workUnwrappedDanteRx(n1);
|
||||
assert(mDanteRxHeadSamples == mSamplesPerChannel);
|
||||
mDanteRxHeadSamples = 0;
|
||||
workUnwrappedDanteRx(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
workUnwrappedDanteRx(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(loopback, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
if (mDanteTxChannels.size() < mDanteRxChannels.size())
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteTxChannels.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteRxChannels.size();
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
unsigned int mTxLatencySamples;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
unsigned int mNumLoopbackChannels;
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 48 // 1ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "l:s:v")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-l Loopback Tx Latency (# samples) - default is " << LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
|
||||
Loopback loopback(buffers, txLatencySamples);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&loopback](unsigned int numPeriods) { loopback.work(numPeriods); };
|
||||
auto _reset = [&loopback]() { loopback.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,610 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dplay.cpp
|
||||
// Audio play example
|
||||
//
|
||||
// prevent compiler warning
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
#define DR_FLAC_IMPLEMENTATION
|
||||
#include "3rd_party/dr_flac.h"
|
||||
|
||||
#define DR_MP3_IMPLEMENTATION
|
||||
#include "3rd_party/dr_mp3.h"
|
||||
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
#include "3rd_party/dr_wav.h"
|
||||
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define START_CHANNEL_DEFAULT 1
|
||||
#define NUM_CHANNELS_DEFAULT 0
|
||||
|
||||
#define MIN(a, b) ( (a) < (b) ? (a) : (b) )
|
||||
|
||||
#define ZERO_INDEXED(n) ( (n) - 1)
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SAMPLE_PROCESS_BLOCK 4096
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signalHandler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
virtual bool open(const char* filename) = 0;
|
||||
virtual uint32_t getSampleRate() = 0;
|
||||
virtual uint32_t getChannelCount() = 0;
|
||||
virtual unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[]) = 0;
|
||||
virtual void seekStart() = 0;
|
||||
virtual void close() = 0;
|
||||
};
|
||||
|
||||
class FlacDecoder: public Decoder
|
||||
{
|
||||
public:
|
||||
bool open(const char* filename)
|
||||
{
|
||||
decoder = drflac_open_file(filename, NULL);
|
||||
if (decoder == NULL)
|
||||
{
|
||||
std::cerr << "ERROR: opening flac file " << std::endl;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Decoder opened successfully" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
uint32_t getSampleRate()
|
||||
{
|
||||
return decoder->sampleRate;
|
||||
}
|
||||
uint32_t getChannelCount()
|
||||
{
|
||||
return decoder->channels;
|
||||
}
|
||||
unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[])
|
||||
{
|
||||
return drflac_read_pcm_frames_s32(decoder, samplesPerChannel, sampleDecBuffers);
|
||||
}
|
||||
void seekStart()
|
||||
{
|
||||
drflac_seek_to_pcm_frame(decoder, 0);
|
||||
}
|
||||
void close()
|
||||
{
|
||||
drflac_close(decoder);
|
||||
std::cout << "Decoder closed successfully" << std::endl;
|
||||
}
|
||||
private:
|
||||
drflac* decoder;
|
||||
};
|
||||
|
||||
class MP3Decoder: public Decoder
|
||||
{
|
||||
public:
|
||||
bool open(const char* filename)
|
||||
{
|
||||
if (!drmp3_init_file(&decoder, filename, NULL))
|
||||
{
|
||||
std::cerr << "ERROR: opening mp3 file " << std::endl;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Decoder opened successfully" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
uint32_t getSampleRate()
|
||||
{
|
||||
return decoder.sampleRate;
|
||||
}
|
||||
uint32_t getChannelCount()
|
||||
{
|
||||
return decoder.channels;
|
||||
}
|
||||
// the library presents 16-bit samples from the mp3 file into the buffer, we need to space these out to 32-bit samples
|
||||
unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[])
|
||||
{
|
||||
if (samplesPerChannel == 0) return -1;
|
||||
unsigned int framesRead = drmp3_read_pcm_frames_s16(&decoder, samplesPerChannel, (drmp3_int16 *) sampleDecBuffers);
|
||||
// do an in-place copy of the 16-bit samples into the expected 32-bit format
|
||||
// the calling function takes into account the number of channels when asking for samples, so the buffer should be large enough
|
||||
for (int s = decoder.channels * framesRead - 1; s >= 0; s--)
|
||||
{
|
||||
sampleDecBuffers[s] = ((drmp3_int16 *)sampleDecBuffers)[s] << 16;
|
||||
}
|
||||
return framesRead;
|
||||
}
|
||||
void seekStart()
|
||||
{
|
||||
drmp3_seek_to_pcm_frame(&decoder, 0);
|
||||
}
|
||||
void close()
|
||||
{
|
||||
drmp3_uninit(&decoder);
|
||||
std::cout << "Decoder closed successfully" << std::endl;
|
||||
}
|
||||
private:
|
||||
drmp3 decoder;
|
||||
};
|
||||
|
||||
class WavDecoder: public Decoder
|
||||
{
|
||||
public:
|
||||
bool open(const char* filename)
|
||||
{
|
||||
if (!drwav_init_file(&decoder, filename, NULL))
|
||||
{
|
||||
std::cerr << "ERROR: opening wav file " << std::endl;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Decoder opened successfully" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
uint32_t getSampleRate()
|
||||
{
|
||||
return decoder.sampleRate;
|
||||
}
|
||||
uint32_t getChannelCount()
|
||||
{
|
||||
return decoder.channels;
|
||||
}
|
||||
unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[])
|
||||
{
|
||||
unsigned int framesRead = drwav_read_pcm_frames_s32(&decoder, samplesPerChannel, sampleDecBuffers);
|
||||
return framesRead;
|
||||
}
|
||||
void seekStart()
|
||||
{
|
||||
drwav_seek_to_pcm_frame(&decoder, 0);
|
||||
}
|
||||
void close()
|
||||
{
|
||||
drwav_uninit(&decoder);
|
||||
std::cout << "Decoder closed successfully" << std::endl;
|
||||
}
|
||||
private:
|
||||
drwav decoder;
|
||||
};
|
||||
|
||||
class Playback
|
||||
{
|
||||
public:
|
||||
Playback(Dante::Buffers & buffers, int txLatencySamples, Decoder * & decoder,
|
||||
unsigned int startChannel,int numChannels, bool repeat)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), decoder(decoder), startChannel(startChannel),
|
||||
numChannels(numChannels), repeat(repeat), mSamplesPerPeriod(), mSamplesPerChannel(), mDanteTxHeadSamples(),
|
||||
mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mNumPlaybackChannels()
|
||||
{
|
||||
mDecoderChannelCount = decoder->getChannelCount();
|
||||
}
|
||||
~Playback()
|
||||
{
|
||||
decoder->close();
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
pullSamplesFromDecoder(n1);
|
||||
assert(mDanteTxHeadSamples == mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = 0;
|
||||
pullSamplesFromDecoder(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
pullSamplesFromDecoder(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(playback, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
if (mDanteTxChannels.size() < mDanteRxChannels.size())
|
||||
{
|
||||
mNumPlaybackChannels = (unsigned int) mDanteTxChannels.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
mNumPlaybackChannels = (unsigned int) mDanteRxChannels.size();
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
|
||||
std::cout << "Config changed" << std::endl;
|
||||
if (numChannels == 0)
|
||||
{
|
||||
mActiveChannels = mDecoderChannelCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
mActiveChannels = MIN((unsigned int) numChannels, mDecoderChannelCount);
|
||||
}
|
||||
if (!sampleRatesMatch() || !enoughDEPChannels()) g_running = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
|
||||
unsigned int mTxLatencySamples;
|
||||
Decoder * decoder;
|
||||
unsigned int startChannel;
|
||||
int numChannels;
|
||||
bool repeat;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
unsigned int mNumPlaybackChannels;
|
||||
|
||||
int32_t sampleDecBuffers[MAX_SAMPLE_PROCESS_BLOCK];
|
||||
unsigned int mDecoderChannelCount;
|
||||
unsigned int mActiveChannels;
|
||||
|
||||
bool sampleRatesMatch()
|
||||
{
|
||||
uint32_t fileSampleRate = decoder->getSampleRate();
|
||||
uint32_t depSampleRate = mBuffers.getHeader()->audio.sample_rate;
|
||||
std::cerr << "file sample rate: " << fileSampleRate << " dep sample rate: " << depSampleRate << std::endl;
|
||||
if (fileSampleRate == depSampleRate)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: Sample rate mismatch, exiting..." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool enoughDEPChannels()
|
||||
{
|
||||
uint32_t fileChannelCount = decoder->getChannelCount();
|
||||
uint32_t depChannelCount = mBuffers.getHeader()->audio.num_tx_channels;
|
||||
std::cerr << "starting channel: " << startChannel + 1 << " | file channel count: " << fileChannelCount <<
|
||||
" | total dep channels: " << depChannelCount << " | user-defined channel count: " << numChannels;
|
||||
if (numChannels == 0) std::cerr << " -> " << fileChannelCount;
|
||||
std::cerr << std::endl;
|
||||
|
||||
if (startChannel + mActiveChannels <= depChannelCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: User asked for more channels than exist in dep, exiting..." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void pullSamplesFromDecoder(unsigned int numSamples)
|
||||
{
|
||||
assert(mDanteTxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
|
||||
unsigned int samplesPerChannel;
|
||||
|
||||
unsigned int maxSamplesPerChannel = MAX_SAMPLE_PROCESS_BLOCK / mDecoderChannelCount;
|
||||
|
||||
do {
|
||||
samplesPerChannel = numSamples > maxSamplesPerChannel ? maxSamplesPerChannel : numSamples;
|
||||
|
||||
unsigned int samplesRead = decoder->getSamples(samplesPerChannel, sampleDecBuffers);
|
||||
if (samplesRead < samplesPerChannel)
|
||||
{
|
||||
if (repeat) decoder->seekStart();
|
||||
else g_running = false;
|
||||
}
|
||||
int32_t* sampleDecBuffers_ptr = sampleDecBuffers;
|
||||
// samples come out from the decoders in an interleaved fashion, ie
|
||||
// sample 1 channel 1, sample 1 channel 2, sample 2 channel 1, sample 2 channel 2... etc
|
||||
// we need to 'de-interleave' these samples and put each in the correct buffer
|
||||
for (unsigned int sample = 0; sample < samplesPerChannel; sample++)
|
||||
{
|
||||
for (unsigned int c = startChannel; c < startChannel + mActiveChannels; c++)
|
||||
{
|
||||
*(mDanteTxChannels[c] + mDanteTxHeadSamples + sample) = *sampleDecBuffers_ptr;
|
||||
sampleDecBuffers_ptr++;
|
||||
}
|
||||
if (mActiveChannels < mDecoderChannelCount)
|
||||
{
|
||||
sampleDecBuffers_ptr += (mDecoderChannelCount - mActiveChannels);
|
||||
}
|
||||
}
|
||||
mDanteTxHeadSamples += samplesRead;
|
||||
numSamples -= samplesRead;
|
||||
|
||||
} while (numSamples > 0 && g_running);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 48 // 1ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
unsigned int startChannel = START_CHANNEL_DEFAULT;
|
||||
int numChannels = NUM_CHANNELS_DEFAULT;
|
||||
bool repeat = false;
|
||||
|
||||
MP3Decoder mp3Decoder;
|
||||
WavDecoder wavDecoder;
|
||||
FlacDecoder flacDecoder;
|
||||
Decoder *decoder;
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "l:s:r::c:n:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
startChannel = atoi(optarg);
|
||||
if (startChannel <= 0)
|
||||
{
|
||||
std::cout <<
|
||||
"Starting channel '-c' should be positive." <<
|
||||
std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
numChannels = atoi(optarg);
|
||||
if (numChannels < 0)
|
||||
{
|
||||
std::cout <<
|
||||
"Number of channels '-n' should be non-negative" <<
|
||||
std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
repeat = true;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Playback Tx Latency (# samples) - default is " << PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-c The first contiguous channel that the file will be played over - default is " << START_CHANNEL_DEFAULT <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-n The number of contiguous channels that will be used - default is " << NUM_CHANNELS_DEFAULT << std::endl <<
|
||||
"\t\t If this value is set to '0', the number of channels will be determined by the file" << std::endl <<
|
||||
"\t\t Specifying a number of outputs greater than the number of file channels will cause nothing to be send to the excess channels." <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-r Repeat forever" << std::endl <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
std::cout << "Starting channel: " << startChannel << std::endl;
|
||||
std::cout << "Number of channels: " << numChannels << std::endl;
|
||||
std::cout << "Repeat: " << (repeat ? "yes" : "no") << std::endl;
|
||||
|
||||
if (argv[optind] == NULL)
|
||||
{
|
||||
std::cerr << "Error: Input filename not provided." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* filename = argv[optind];
|
||||
const char* extension = strrchr(filename, '.');
|
||||
|
||||
if (strcmp(extension, ".mp3") == 0)
|
||||
{
|
||||
decoder = &mp3Decoder;
|
||||
}
|
||||
else if (strcmp(extension, ".wav") == 0)
|
||||
{
|
||||
decoder = &wavDecoder;
|
||||
}
|
||||
else if (strcmp(extension, ".flac") == 0)
|
||||
{
|
||||
decoder = &flacDecoder;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Format should be one of 'mp3', 'wav', 'flac'." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (!decoder->open(filename))
|
||||
{
|
||||
std::cerr << "There was an issue opening the file. Exiting..." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Playback playback(buffers, txLatencySamples, decoder, ZERO_INDEXED(startChannel), numChannels, repeat);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&playback](unsigned int numPeriods) { playback.work(numPeriods); };
|
||||
auto _reset = [&playback]() { playback.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// drecord.cpp
|
||||
// Audio recording example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
/* WAV library */
|
||||
#include "3rd_party/wav.hpp"
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static char *outFileName;
|
||||
|
||||
#define MAX_ENCODER_NUM 8
|
||||
#define MAX_DECODER_NUM 0
|
||||
#define OUT_MAX_FILE_NAME_LENGTH 256
|
||||
#define MAX_SAMPLE_PROCESS_BLOCK 4096
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
class Recorder
|
||||
{
|
||||
public:
|
||||
Recorder(Dante::Buffers & buffers, int txLatencySamples)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), mSamplesPerPeriod(), mSamplesPerChannel(),
|
||||
mDanteTxHeadSamples(), mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mNumLoopbackChannels()
|
||||
{}
|
||||
~Recorder()
|
||||
{
|
||||
close_encoders();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante RX loop
|
||||
if (mDanteRxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteRxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
push_samples_to_encoder(n1);
|
||||
assert(mDanteRxHeadSamples == mSamplesPerChannel);
|
||||
mDanteRxHeadSamples = 0;
|
||||
push_samples_to_encoder(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
push_samples_to_encoder(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(loopback, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
if (mDanteTxChannels.size() < mDanteRxChannels.size())
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteTxChannels.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteRxChannels.size();
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
|
||||
//first time reset is called dep is initialising
|
||||
if (mOutputFilePointer == NULL) {
|
||||
mWavInfo.num_channels = metadata->audio.num_rx_channels;
|
||||
mWavInfo.bits_per_sample = metadata->audio.encoding;
|
||||
mWavInfo.sample_rate = metadata->audio.sample_rate;
|
||||
mSampleBuffer = new int32_t[mWavInfo.num_channels];
|
||||
start_encoders();
|
||||
} else {
|
||||
std::cout << "Stopping recording because changes were made to dep config" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
if (mSampleBuffer) delete mSampleBuffer;
|
||||
mSampleBuffer = NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
unsigned int mTxLatencySamples;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
unsigned int mNumLoopbackChannels;
|
||||
|
||||
struct wav_info mWavInfo;
|
||||
FILE* mOutputFilePointer = NULL;
|
||||
uint32_t mTotalSamples = 0;
|
||||
int32_t* mSampleBuffer;
|
||||
|
||||
void start_encoders()
|
||||
{
|
||||
mOutputFilePointer = open_wav_file(outFileName);
|
||||
if(mOutputFilePointer == NULL)
|
||||
{
|
||||
std::cerr << "Error opening for output!" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void close_encoders()
|
||||
{
|
||||
if (mOutputFilePointer != NULL)
|
||||
{
|
||||
mWavInfo.num_samples = mTotalSamples;
|
||||
write_wav_hdr(&mWavInfo, mOutputFilePointer);
|
||||
fclose(mOutputFilePointer);
|
||||
mOutputFilePointer = NULL;
|
||||
std::cout << "Encoder closed. Audio saved to '" << outFileName << "'." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void push_samples_to_encoder(unsigned int numSamples)
|
||||
{
|
||||
assert(mDanteRxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
|
||||
for (unsigned int i = 0; i < numSamples; i++)
|
||||
{
|
||||
for (unsigned int j = 0; j < mWavInfo.num_channels; j++)
|
||||
{
|
||||
mSampleBuffer[j] = *(mDanteRxChannels[j] + mDanteRxHeadSamples + i);
|
||||
}
|
||||
write_sample(&mWavInfo, mOutputFilePointer, mSampleBuffer);
|
||||
}
|
||||
|
||||
mTotalSamples += numSamples;
|
||||
mDanteRxHeadSamples += numSamples;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
#define OUT_FILE_NAME_DEFAULT "sound.wav"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
outFileName = (char*)OUT_FILE_NAME_DEFAULT;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "l:s:o:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
outFileName = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Loopback Tx Latency (# samples) - default is " << LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-o output file name prefix - default is " << OUT_FILE_NAME_DEFAULT <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
|
||||
Recorder recorder(buffers, txLatencySamples);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&recorder](unsigned int numPeriods) { recorder.work(numPeriods); };
|
||||
auto _reset = [&recorder]() { recorder.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
recorder.cleanup();
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,11 @@
|
||||
pcm_type.dsoundcard {
|
||||
lib "/opt/dep/dsoundcard.so"
|
||||
}
|
||||
|
||||
pcm.dsoundcard {
|
||||
type dsoundcard
|
||||
hint {
|
||||
show on
|
||||
description "DEP Example Sound Card"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dsoundcard.hpp
|
||||
// Priority modification handling
|
||||
//
|
||||
#pragma once
|
||||
#include <semaphore.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/pcm_external.h>
|
||||
#include <sys/socket.h>
|
||||
#include <bits/stdc++.h>
|
||||
#include <mutex>
|
||||
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dsoundcard_logging.hpp"
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_MS 5
|
||||
|
||||
void startDEPSoundcard(snd_pcm_ioplug_t * io);
|
||||
void depSoundcardThread(snd_pcm_ioplug_t * io);
|
||||
void stopDEPSoundcard(snd_pcm_ioplug_t * io);
|
||||
|
||||
class IntermediateBuffer;
|
||||
class DEPSoundcard
|
||||
{
|
||||
public:
|
||||
DEPSoundcard(snd_pcm_ioplug_t * io, uint64_t & hwPtr, int pollSocket, sem_t* semaphore, uint32_t sampleRate);
|
||||
~DEPSoundcard();
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
bool pollDataAvailable();
|
||||
snd_pcm_sframes_t numFramesInIntermediateBuffer();
|
||||
|
||||
snd_pcm_sframes_t alsaToBuffer(void* buffer, snd_pcm_uframes_t frames, unsigned int channels);
|
||||
snd_pcm_sframes_t bufferToAlsa(void* buffer, snd_pcm_uframes_t frames, unsigned int channels);
|
||||
|
||||
private :
|
||||
void work(unsigned int numPeriods);
|
||||
void reset();
|
||||
|
||||
snd_pcm_uframes_t depRxToBuffer(unsigned int numFrames);
|
||||
snd_pcm_uframes_t bufferToDepTx(unsigned int numFrames);
|
||||
|
||||
void setThreadPriority();
|
||||
|
||||
private:
|
||||
Dante::Buffers mBuffers;
|
||||
snd_pcm_ioplug_t *mIo;
|
||||
ssize_t mBytesPerSample;
|
||||
uint64_t & mHwPtr;
|
||||
int mPollSocket;
|
||||
sem_t* mSemaphore;
|
||||
unsigned int mPeriodSizeInFrames;
|
||||
unsigned int mBufferSizeInFrames;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
bool mRunning;
|
||||
IntermediateBuffer* intermediateBuffer;
|
||||
uint32_t mSampleRate;
|
||||
bool mDataAvailable;
|
||||
bool mXrunOccurred;
|
||||
};
|
||||
|
||||
class IntermediateBuffer
|
||||
{
|
||||
public:
|
||||
IntermediateBuffer(unsigned int channels, unsigned int bufferSizeInFrames, snd_pcm_stream_t mode, unsigned int samplerate);
|
||||
~IntermediateBuffer();
|
||||
|
||||
void reset(unsigned int channels, unsigned int bufferSizeInFrames);
|
||||
uint32_t getNumSamplesInBuffer();
|
||||
uint32_t getAvailableSpaceInSamplesInBuffer();
|
||||
|
||||
snd_pcm_uframes_t writeAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
snd_pcm_uframes_t readAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
|
||||
snd_pcm_uframes_t writeDepSamples(snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<const int32_t *> & danteRxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteRxHeadSamples,
|
||||
uint32_t bufferSizeInFrames);
|
||||
|
||||
snd_pcm_uframes_t readDepSamples(snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<int32_t *> & danteTxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteTxHeadSamples,
|
||||
uint32_t bufferSizeInFrames);
|
||||
|
||||
std::mutex mBufferMutex;
|
||||
|
||||
private:
|
||||
void resetBufferParams(unsigned int channels, unsigned int bufferSizeInFrames);
|
||||
|
||||
void writeAlsaSamplesToBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
void readAlsaSamplesFromBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
|
||||
uint32_t writeDepSamplesS16LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t writeDepSamplesS32LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t writeDepSamplesFLOATLE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
|
||||
uint32_t readDepSamples16LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t readDepSamplesS32LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t readDepSamplesFLOATLE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
|
||||
int32_t* mBuffer;
|
||||
unsigned int mBufferSizeInSamples;
|
||||
unsigned int mWriteHead;
|
||||
unsigned int mReadHead;
|
||||
DEPSoundcardBufferLog *mBufferLog;
|
||||
uint32_t mBufferLevelInSamples; // N.B. having an explicit value for this lets us see that the buffer is full when the write pointer catches up with the read pointer
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
snd_pcm_ioplug_t io;
|
||||
int fd;
|
||||
uint64_t hw_ptr;
|
||||
unsigned int channels;
|
||||
sem_t semaphore;
|
||||
DEPSoundcard* dsoundcard;
|
||||
uint32_t dep_sample_rate;
|
||||
uint32_t dep_frames_per_period;
|
||||
} snd_pcm_dante_t;
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dsoundcard_logging.hpp
|
||||
// Generates timestamped stats log in csv format which can be imported into a spreadsheet to examine buffer levels and thread timings.
|
||||
//
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
// Enable logging of intermediate buffer levels
|
||||
#define __ENABLE_BUFFER_LOGGING 0
|
||||
|
||||
// Enabling function trace logging logs calls to a CSV file, enabling just function trace outputs on stderr
|
||||
#define __ENABLE_FUNCTION_TRACE_LOGGING 0
|
||||
#define __ENABLE_FUNCTION_TRACE 0
|
||||
|
||||
// You can't log to both csv and stderr
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
#include <string.h>
|
||||
#undef __ENABLE_FUNCTION_TRACE
|
||||
#define __ENABLE_FUNCTION_TRACE 0
|
||||
#endif
|
||||
|
||||
class DEPSoundcardBufferLog
|
||||
{
|
||||
#if (__ENABLE_BUFFER_LOGGING == 1)
|
||||
|
||||
struct dsoundcardBufferLogEntry
|
||||
{
|
||||
uint64_t timestamp;
|
||||
uint16_t threadId;
|
||||
uint32_t samplesWritten;
|
||||
bool writeOperation;
|
||||
uint32_t newBufferLevel;
|
||||
};
|
||||
|
||||
public:
|
||||
DEPSoundcardBufferLog(const char *logFileName);
|
||||
~DEPSoundcardBufferLog();
|
||||
|
||||
void open();
|
||||
|
||||
void logWriteEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel);
|
||||
void logReadEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel);
|
||||
|
||||
std::ofstream mOutputFile;
|
||||
std::vector<struct dsoundcardBufferLogEntry> mLogEntries;
|
||||
|
||||
private:
|
||||
const uint32_t MaxNumLogEntries = 30000;
|
||||
|
||||
void displayWriteSampleRates(uint64_t buffTime);
|
||||
void displayReadSampleRates(uint64_t buffTime);
|
||||
void logEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel, bool write);
|
||||
|
||||
uint64_t mLogStartTime;
|
||||
pthread_t mFirstPthreadId;
|
||||
std::string mLogFileName;
|
||||
uint64_t mSamplesWritten;
|
||||
uint64_t mSamplesRead;
|
||||
uint64_t mPrevWriteSampleRateLogTime;
|
||||
uint64_t mPrevReadSampleRateLogTime;
|
||||
|
||||
#else
|
||||
public:
|
||||
DEPSoundcardBufferLog(const char *logFileName){};
|
||||
void logWriteEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel){};
|
||||
void logReadEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel){};
|
||||
#endif
|
||||
};
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
class DEPSoundcardFunctionLog
|
||||
{
|
||||
static const uint32_t MaxFunctionNameLength = 32;
|
||||
struct dsoundcardFunctionLogEntry
|
||||
{
|
||||
uint64_t timestamp;
|
||||
uint64_t duration;
|
||||
char functionName[MaxFunctionNameLength + 1];
|
||||
};
|
||||
|
||||
public:
|
||||
DEPSoundcardFunctionLog(const char *logFileName);
|
||||
~DEPSoundcardFunctionLog();
|
||||
|
||||
void open();
|
||||
|
||||
uint64_t logFunctionStart(const char *functionName);
|
||||
void logFunctionEnd(const char *functionName, uint64_t startTime);
|
||||
|
||||
const uint32_t MaxNumLogEntries = 50000;
|
||||
|
||||
std::ofstream mOutputFile;
|
||||
std::vector<struct dsoundcardFunctionLogEntry> mLogEntries;
|
||||
|
||||
private:
|
||||
uint64_t mLogStartTime;
|
||||
std::string mLogFileName;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
extern DEPSoundcardFunctionLog functionLog;
|
||||
#define FUNCTION_TRACE_START() uint64_t startTime = functionLog.logFunctionStart(__FUNCTION__); (void)startTime
|
||||
#define FUNCTION_TRACE_END() functionLog.logFunctionEnd(__FUNCTION__, startTime)
|
||||
#else
|
||||
#if (__ENABLE_FUNCTION_TRACE == 1)
|
||||
#define FUNCTION_TRACE_START() fprintf(stderr, "%s @ %s:%d\n", __FUNCTION__, __FILE__, __LINE__); fflush(stderr)
|
||||
#define FUNCTION_TRACE_END() fprintf(stderr, "%s @ %s:%d END\n", __FUNCTION__, __FILE__, __LINE__); fflush(stderr)
|
||||
#else
|
||||
#define FUNCTION_TRACE_START() ;
|
||||
#define FUNCTION_TRACE_END() ;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
OUTPUT=$(install dsoundcard.so /opt/dep -C -v)
|
||||
|
||||
if [[ $OUTPUT ]]
|
||||
then
|
||||
echo $OUTPUT
|
||||
else
|
||||
echo "Shared library not modified"
|
||||
fi
|
||||
@@ -0,0 +1,420 @@
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// alsa_plugin.cpp
|
||||
// ALSA plugin example
|
||||
//
|
||||
#include "dsoundcard.hpp"
|
||||
|
||||
// ALSA buffer size expressed in periods.
|
||||
#define REQUESTED_PERIODS_IN_ALSA_BUFFER 16
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define UNUSED_ARGS(args) (void)(args)
|
||||
|
||||
extern "C" {
|
||||
|
||||
static int depSetHwConstraint(snd_pcm_dante_t * dante, unsigned int maxChannels)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
unsigned int accessList[] =
|
||||
{
|
||||
//SND_PCM_ACCESS_MMAP_INTERLEAVED,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED
|
||||
//SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
|
||||
//SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
};
|
||||
|
||||
// Only support a few audio formats for now
|
||||
const unsigned int formats[] = {SND_PCM_FORMAT_S32, SND_PCM_FORMAT_S16, SND_PCM_FORMAT_FLOAT};
|
||||
|
||||
const unsigned int rates[] = { dante->dep_sample_rate };
|
||||
|
||||
snd_pcm_ioplug_t * io = &dante->io;
|
||||
|
||||
int result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, ARRAY_SIZE(accessList), accessList);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, ARRAY_SIZE(formats), formats);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, 1, maxChannels);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, ARRAY_SIZE(rates), rates);
|
||||
if (result < 0) return result;
|
||||
|
||||
// TODO - These values are not still correct, as we ideally want the ALSA period size for a
|
||||
// stream to be the same as the DEP period (in frames). With this method this is only true
|
||||
// for maxChannels (2) data of S32_LE samples. With, for example, 1 channel of S16_LE
|
||||
// the ALSA period is 4 times the DEP period - in other words the same number of bytes.
|
||||
// This does at least mean that the poll frequency expected is always a multiple of the DEP
|
||||
// frequency so we will live with it for now.
|
||||
// N.B. at this point we don't know the sample format or channels that will be used for the stream.
|
||||
unsigned int maxBytesPerPeriod = maxChannels * sizeof(uint32_t) * dante->dep_frames_per_period;
|
||||
unsigned int minBytesPerPeriod = 1 * sizeof(uint16_t) * dante->dep_frames_per_period;
|
||||
result = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, minBytesPerPeriod, maxBytesPerPeriod);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, REQUESTED_PERIODS_IN_ALSA_BUFFER, REQUESTED_PERIODS_IN_ALSA_BUFFER);
|
||||
if (result < 0) return result;
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this callback is called before the audio app playing into the soundcard exits
|
||||
static int depSoundcardClose(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
stopDEPSoundcard(io);
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
delete dante->dsoundcard;
|
||||
dante->dsoundcard = NULL;
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The return value of this function is used to set io->hw_ptr.
|
||||
// io->hw_ptr tells alsa how many samples have been consumed and it is used to determine whether
|
||||
// more samples should be read into the alsa buffer.
|
||||
static snd_pcm_sframes_t depSoundcardPointer(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
return dante->hw_ptr;
|
||||
}
|
||||
|
||||
static int depSoundcardStart(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
startDEPSoundcard(io);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this callback is called before the audio app playing into the soundcard pauses or scrubs/seeks
|
||||
static int depSoundcardStop(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
stopDEPSoundcard(io);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// When alsa reads some more samples in from the audio app, this function gets called.
|
||||
// We simply read the samples into the intermediate buffers which we have already asserted
|
||||
// are large enough so as to not cause overruns.
|
||||
static snd_pcm_sframes_t depSoundcardPlaybackTransfer(snd_pcm_ioplug_t * io, const snd_pcm_channel_area_t * areas,
|
||||
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
|
||||
char* buf = ((char*)areas->addr + (areas->first + areas->step * offset) / 8);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return dante->dsoundcard->alsaToBuffer(buf, size, io->channels);
|
||||
}
|
||||
|
||||
// This function is called when an audio app recording from the soundcard requests some more samples.
|
||||
// We simply copy out whatever is currently in the intermediate buffers.
|
||||
// The timing of the request for samples is controlled by io->hw_ptr which in turn is controlled by the
|
||||
// work function in the dep soundcard, so everything keeps in sync.
|
||||
static snd_pcm_sframes_t depSoundcardCaptureTransfer(snd_pcm_ioplug_t * io, const snd_pcm_channel_area_t * areas,
|
||||
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
char* buf = ((char*)areas->addr + (areas->first + areas->step * offset) / 8);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return dante->dsoundcard->bufferToAlsa(buf, size, io->channels);
|
||||
}
|
||||
|
||||
static int depHwParams(snd_pcm_ioplug_t * io, snd_pcm_hw_params_t * params)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
|
||||
dante->channels = io->channels;
|
||||
unsigned int bytesPerSample = snd_pcm_format_physical_width(io->format) / 8;
|
||||
unsigned int bytesPerFrame = bytesPerSample * io->channels;
|
||||
|
||||
fprintf(stderr, "DSOUNDCARD HARDWARE PARAMETERS\n");
|
||||
fprintf(stderr, "Sample format: %s\n", snd_pcm_format_name(io->format));
|
||||
fprintf(stderr, "Channels: %u\n", dante->channels);
|
||||
fprintf(stderr, "Bytes per sample: %u\n", bytesPerSample);
|
||||
fprintf(stderr, "Bytes per frame: %u\n", bytesPerFrame);
|
||||
fprintf(stderr, "Stream direction: %s\n", (io->stream == SND_PCM_STREAM_PLAYBACK) ? "Playback" : "Capture");
|
||||
fprintf(stderr, "IO period size (frames) %lu\n", io->period_size);
|
||||
fprintf(stderr, "IO buffer size (frames) %lu\n", io->buffer_size);
|
||||
|
||||
if (!dante->dsoundcard)
|
||||
{
|
||||
// Now that we know the buffer sizes ALSA has set, we can create the intermediate buffer.
|
||||
dante->dsoundcard = new DEPSoundcard(io, dante->hw_ptr, dante->fd, &dante->semaphore, dante->dep_sample_rate);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int depPollRevents(snd_pcm_ioplug_t * io, struct pollfd * pfds, unsigned int nfds, unsigned short * revents)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
static char buf[1];
|
||||
|
||||
*revents = 0;
|
||||
|
||||
if (pfds == NULL || nfds != 1 || revents == NULL) return -EINVAL;
|
||||
|
||||
read(pfds[0].fd, buf, sizeof(buf));
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
if ((dante) && (dante->dsoundcard) && (dante->dsoundcard->pollDataAvailable()))
|
||||
{
|
||||
*revents = (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If you see the error message "DEP timing - semaphore wait: Connection timed out" appearing on the console,
|
||||
// try uncommenting this sleep statement. This sleep prevents alsaloop spinning round tightly on this poll
|
||||
// which can cause livelock on some low-powered systems
|
||||
// Be careful not to set this value higher than the period time.
|
||||
//usleep(500);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the delay in frames between a frame being captured in the hardware being pulled out of the
|
||||
// driver, or the delay between a frame being written to the device and being played out.
|
||||
// This function is optional, but is needed to work around a bug in some versions of alsalib -
|
||||
// see https://git.alsa-project.org/?p=alsa-lib.git;a=commitdiff;h=6cee452eabc5cfdf0a6955033b8ac8f6e12ea883
|
||||
static int depSoundcardDelay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
*delayp = 0;
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
if ((dante) && (dante->dsoundcard))
|
||||
{
|
||||
*delayp = dante->dsoundcard->numFramesInIntermediateBuffer();
|
||||
}
|
||||
FUNCTION_TRACE_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_ioplug_callback_t depCapturePcmCallback =
|
||||
{
|
||||
.start = depSoundcardStart,
|
||||
.stop = depSoundcardStop,
|
||||
.pointer = depSoundcardPointer,
|
||||
.transfer = depSoundcardCaptureTransfer,
|
||||
.close = depSoundcardClose,
|
||||
.hw_params = depHwParams,
|
||||
.hw_free = NULL,
|
||||
.sw_params = NULL,
|
||||
.prepare = NULL,
|
||||
.drain = NULL,
|
||||
.pause = NULL,
|
||||
.resume = NULL,
|
||||
.poll_descriptors_count = NULL,
|
||||
.poll_descriptors = NULL,
|
||||
.poll_revents = depPollRevents,
|
||||
.dump = NULL,
|
||||
.delay = depSoundcardDelay,
|
||||
.query_chmaps = NULL,
|
||||
.get_chmap = NULL,
|
||||
.set_chmap = NULL
|
||||
};
|
||||
|
||||
static snd_pcm_ioplug_callback_t depPlaybackPcmCallback =
|
||||
{
|
||||
.start = depSoundcardStart,
|
||||
.stop = depSoundcardStop,
|
||||
.pointer = depSoundcardPointer,
|
||||
.transfer = depSoundcardPlaybackTransfer,
|
||||
.close = depSoundcardClose,
|
||||
.hw_params = depHwParams,
|
||||
.hw_free = NULL,
|
||||
.sw_params = NULL,
|
||||
.prepare = NULL,
|
||||
.drain = NULL,
|
||||
.pause = NULL,
|
||||
.resume = NULL,
|
||||
.poll_descriptors_count = NULL,
|
||||
.poll_descriptors = NULL,
|
||||
.poll_revents = depPollRevents,
|
||||
.dump = NULL,
|
||||
.delay = depSoundcardDelay,
|
||||
.query_chmaps = NULL,
|
||||
.get_chmap = NULL,
|
||||
.set_chmap = NULL
|
||||
};
|
||||
|
||||
// the poll fds have to be nonblocking if we are to use them for polling
|
||||
static int makeNonblock(int fd)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
int fl;
|
||||
|
||||
if ((fl = fcntl(fd, F_GETFL)) < 0) return fl;
|
||||
|
||||
if (fl & O_NONBLOCK) return 0;
|
||||
|
||||
return fcntl(fd, F_SETFL, fl | O_NONBLOCK);
|
||||
}
|
||||
|
||||
// first time setup tasks
|
||||
static int depSoundcardOpen(snd_pcm_t ** pcmp, const char * name, snd_pcm_stream_t stream, int mode)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
int err;
|
||||
int fd[2];
|
||||
snd_pcm_dante_t * dante;
|
||||
|
||||
dante = (snd_pcm_dante_t *)calloc(1, sizeof(*dante));
|
||||
if (!dante) return -ENOMEM;
|
||||
|
||||
dante->io.version = SND_PCM_IOPLUG_VERSION;
|
||||
dante->io.name = "ALSA <-> Dante PCM I/O Plugin";
|
||||
dante->io.callback = (stream == SND_PCM_STREAM_PLAYBACK) ? &depPlaybackPcmCallback : &depCapturePcmCallback;
|
||||
dante->io.private_data = dante;
|
||||
dante->io.mmap_rw = 0;
|
||||
|
||||
socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
|
||||
makeNonblock(fd[0]);
|
||||
makeNonblock(fd[1]);
|
||||
dante->fd = fd[0]; // File descriptor that DepSoundcard object writes to once per DEP period
|
||||
dante->io.poll_fd = fd[1]; // File descriptor that ALSA monitors / polls
|
||||
dante->io.poll_events = POLLIN;
|
||||
|
||||
sem_init(&dante->semaphore, 0, 0);
|
||||
|
||||
err = snd_pcm_ioplug_create(&dante->io, name, stream, mode);
|
||||
if (err < 0)
|
||||
{
|
||||
perror("depSoundcardOpen - call to snd_pcm_ioplug_create failed");
|
||||
free(dante);
|
||||
return err;
|
||||
}
|
||||
|
||||
// we connect to the dante buffers and get hw param information
|
||||
Dante::Buffers buffer;
|
||||
err = buffer.connect(SHM_DEFAULT_NAME, false);
|
||||
if (err) return -1;
|
||||
auto metadata = buffer.getHeader();
|
||||
unsigned int maxChannels = (stream == SND_PCM_STREAM_PLAYBACK ? metadata->audio.num_tx_channels : metadata->audio.num_rx_channels);
|
||||
dante->dep_sample_rate = metadata->audio.sample_rate;
|
||||
dante->dep_frames_per_period = metadata->time.samples_per_period;
|
||||
|
||||
buffer.disconnect();
|
||||
|
||||
err = depSetHwConstraint(dante, maxChannels);
|
||||
if (err < 0)
|
||||
{
|
||||
perror("depSoundcardOpen - call to depSetHwConstraint failed");
|
||||
snd_pcm_ioplug_delete(&dante->io);
|
||||
free(dante);
|
||||
return err;
|
||||
}
|
||||
|
||||
*pcmp = dante->io.pcm;
|
||||
snd_pcm_info_t * info = NULL;
|
||||
snd_pcm_info_alloca(&info);
|
||||
snd_pcm_info(*pcmp, info);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
SND_PCM_PLUGIN_DEFINE_FUNC(dsoundcard)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
UNUSED_ARGS(root && conf);
|
||||
|
||||
int err = depSoundcardOpen(pcmp, name, stream, mode);
|
||||
return err;
|
||||
}
|
||||
|
||||
SND_PCM_PLUGIN_SYMBOL(dsoundcard);
|
||||
|
||||
} // extern "C"
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,331 @@
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dsoundcard.cpp
|
||||
// DEP Soundcard example
|
||||
//
|
||||
#include "dsoundcard.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
|
||||
void startDEPSoundcard(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
// Occasionally "start" is called without "stop" being called first, e.g. when there is an xrun.
|
||||
// We need to reset the buffers when this happens.
|
||||
// There are checks in "stop" to make sure it does nothing if the worker thread is not running.
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
dante->dsoundcard->stop();
|
||||
|
||||
std::thread depSoundcard(depSoundcardThread, io);
|
||||
depSoundcard.detach();
|
||||
sem_wait(&dante->semaphore);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
void depSoundcardThread(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
dante->dsoundcard->run();
|
||||
}
|
||||
|
||||
void stopDEPSoundcard(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
if (dante->dsoundcard != NULL)
|
||||
{
|
||||
dante->dsoundcard->stop();
|
||||
}
|
||||
}
|
||||
|
||||
DEPSoundcard::DEPSoundcard(snd_pcm_ioplug_t * io, uint64_t & hwPtr, int pollSocket, sem_t* semaphore, uint32_t sampleRate)
|
||||
: mIo(io), mBytesPerSample(snd_pcm_format_width(io->format) / 8), mHwPtr(hwPtr),
|
||||
mPollSocket(pollSocket), mSemaphore(semaphore), mBufferSizeInFrames(), mDanteTxHeadSamples(),
|
||||
mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mRunning(false), intermediateBuffer(nullptr), mSampleRate(sampleRate),
|
||||
mDataAvailable(false), mXrunOccurred(false)
|
||||
{
|
||||
intermediateBuffer = new IntermediateBuffer(mIo->channels, mIo->buffer_size, mIo->stream, mSampleRate);
|
||||
}
|
||||
|
||||
DEPSoundcard::~DEPSoundcard()
|
||||
{
|
||||
delete intermediateBuffer;
|
||||
}
|
||||
|
||||
void DEPSoundcard::run()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
setThreadPriority();
|
||||
|
||||
mHwPtr = 0;
|
||||
mRunning = true;
|
||||
|
||||
Dante::Runner runner(mBuffers);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (mRunning)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = mBuffers.connect(SHM_DEFAULT_NAME, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [this](unsigned int numPeriods) { work(numPeriods); };
|
||||
auto _reset = [this]() { reset(); };
|
||||
runner.setActiveChangedFn([this](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
if (active)
|
||||
{
|
||||
sem_post(mSemaphore); // Unblock startDEPSoundcard
|
||||
}
|
||||
});
|
||||
runner.run(mRunning, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
mBuffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
|
||||
usleep(10000); // Make sure we allow things time to recover
|
||||
} // mRunning is false, therefore we are exiting
|
||||
cleanupDantePriority();
|
||||
// notify exit completion
|
||||
sem_post(mSemaphore); // Unblock DEPSoundcard::stop
|
||||
}
|
||||
|
||||
void DEPSoundcard::stop()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
if (mRunning)
|
||||
{
|
||||
mRunning = false;
|
||||
|
||||
// Wait for Runner thread to exit.
|
||||
sem_wait(mSemaphore);
|
||||
|
||||
delete intermediateBuffer;
|
||||
intermediateBuffer = new IntermediateBuffer(mIo->channels, mIo->buffer_size, mIo->stream, mSampleRate);
|
||||
}
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
void DEPSoundcard::work(unsigned int numPeriods)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
unsigned int numFrames = numPeriods * mPeriodSizeInFrames;
|
||||
|
||||
const std::lock_guard<std::mutex> lock(intermediateBuffer->mBufferMutex);
|
||||
|
||||
// depending on whether the soundcard is in capture or playback mode, we will be writing to or reading from some intermediate buffers
|
||||
if (mIo->stream == SND_PCM_STREAM_CAPTURE)
|
||||
{
|
||||
if (depRxToBuffer(numFrames) < numFrames)
|
||||
{
|
||||
mXrunOccurred = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bufferToDepTx(numFrames) < numFrames)
|
||||
{
|
||||
mXrunOccurred = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the poll function return true now.
|
||||
mDataAvailable = true;
|
||||
|
||||
// Hardware pointer represents the position of the Dante shared memory read/write pointers
|
||||
mHwPtr += numFrames;
|
||||
mHwPtr %= mIo->buffer_size;
|
||||
|
||||
// Write some data into the poll socket. The data itself is not important, but the act of
|
||||
// writing will wake up the polling client.
|
||||
char dummyByte;
|
||||
if (write(mPollSocket, &dummyByte, 1) < 0)
|
||||
{
|
||||
perror("DEPsoundcard Failed to write poll file descriptor");
|
||||
}
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values and on startup
|
||||
void DEPSoundcard::reset()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
auto metadata = mBuffers.getHeader();
|
||||
|
||||
uint32_t prevSampleRate = mSampleRate;
|
||||
|
||||
unsigned int newNumChannels =
|
||||
(mIo->stream == SND_PCM_STREAM_CAPTURE) ?
|
||||
mBuffers.getHeader()->audio.num_rx_channels :
|
||||
mBuffers.getHeader()->audio.num_tx_channels;
|
||||
|
||||
mPeriodSizeInFrames = metadata->time.samples_per_period;
|
||||
mBufferSizeInFrames = metadata->audio.samples_per_channel;
|
||||
mSampleRate = metadata->audio.sample_rate;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mPeriodSizeInFrames) % mBufferSizeInFrames);
|
||||
unsigned int playbackLatency = PLAYBACK_DEFAULT_TX_LATENCY_MS * mSampleRate / 1000;
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mPeriodSizeInFrames + playbackLatency) % mBufferSizeInFrames);
|
||||
|
||||
if ((mSampleRate) && (mSampleRate != prevSampleRate))
|
||||
{
|
||||
std::cerr << "Detected change to sample rate from " << prevSampleRate << " to " << mSampleRate << " - exiting." << std::endl;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (newNumChannels < mIo->channels)
|
||||
{
|
||||
std::cerr << "Detected change to number of channels from " << mIo->channels << " to " << newNumChannels << " - exiting." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
bool DEPSoundcard::pollDataAvailable()
|
||||
{
|
||||
bool retval = mDataAvailable;
|
||||
mDataAvailable = false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t DEPSoundcard::numFramesInIntermediateBuffer()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(intermediateBuffer->mBufferMutex);
|
||||
|
||||
return intermediateBuffer->getNumSamplesInBuffer() / mIo->channels;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t DEPSoundcard::depRxToBuffer(unsigned int numFrames)
|
||||
{
|
||||
return intermediateBuffer->writeDepSamples(mIo->format, numFrames, mDanteRxChannels, mIo->channels, mDanteRxHeadSamples, mBufferSizeInFrames);
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t DEPSoundcard::bufferToDepTx(unsigned int numFrames)
|
||||
{
|
||||
return intermediateBuffer->readDepSamples(mIo->format, numFrames, mDanteTxChannels, mIo->channels, mDanteTxHeadSamples, mBufferSizeInFrames);
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t DEPSoundcard::alsaToBuffer(void* buffer, snd_pcm_uframes_t frames, unsigned int channels)
|
||||
{
|
||||
// Return number of frames written
|
||||
assert(channels == mIo->channels);
|
||||
|
||||
if (mXrunOccurred)
|
||||
{
|
||||
mXrunOccurred = false;
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
return (intermediateBuffer->writeAlsaSamples(buffer, frames * channels, mBytesPerSample) / channels);
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t DEPSoundcard::bufferToAlsa(void* buffer, snd_pcm_uframes_t frames, unsigned int channels)
|
||||
{
|
||||
assert(channels == mIo->channels);
|
||||
|
||||
if (mXrunOccurred)
|
||||
{
|
||||
mXrunOccurred = false;
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
// Return number of frames read
|
||||
return (intermediateBuffer->readAlsaSamples(buffer, frames * channels, mBytesPerSample) / channels);
|
||||
}
|
||||
|
||||
void DEPSoundcard::setThreadPriority()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
setDantePriority("The soundcard");
|
||||
}
|
||||
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,438 @@
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dsoundcard_intermediatebuffer.cpp
|
||||
// DEP Soundcard example
|
||||
//
|
||||
#include "dsoundcard.hpp"
|
||||
|
||||
IntermediateBuffer::IntermediateBuffer(unsigned int channels, unsigned int bufferSizeInFrames, snd_pcm_stream_t mode, unsigned int samplerate)
|
||||
: mBufferLevelInSamples(0)
|
||||
{
|
||||
mBuffer = (int32_t*) calloc(channels * bufferSizeInFrames, sizeof(int32_t));
|
||||
|
||||
resetBufferParams(channels, bufferSizeInFrames);
|
||||
|
||||
if (mode == SND_PCM_STREAM_CAPTURE)
|
||||
{
|
||||
mBufferLog = new DEPSoundcardBufferLog("/tmp/dep_rx_soundcard_log.csv");
|
||||
}
|
||||
else
|
||||
{
|
||||
mBufferLog = new DEPSoundcardBufferLog("/tmp/dep_tx_soundcard_log.csv");
|
||||
}
|
||||
}
|
||||
|
||||
IntermediateBuffer::~IntermediateBuffer()
|
||||
{
|
||||
delete(mBufferLog);
|
||||
free(mBuffer);
|
||||
}
|
||||
|
||||
void IntermediateBuffer::reset(unsigned int channels, unsigned int bufferSizeInFrames)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mBufferMutex);
|
||||
unsigned int newBufferSizeInSamples = channels * bufferSizeInFrames;
|
||||
|
||||
if ((mBuffer == nullptr) || (newBufferSizeInSamples != mBufferSizeInSamples))
|
||||
{
|
||||
int32_t *oldBuffer = mBuffer;
|
||||
mBuffer = (int32_t*) calloc(channels * bufferSizeInFrames, sizeof(int32_t));
|
||||
|
||||
if (oldBuffer)
|
||||
{
|
||||
free(oldBuffer);
|
||||
}
|
||||
}
|
||||
resetBufferParams(channels, bufferSizeInFrames);
|
||||
}
|
||||
|
||||
void IntermediateBuffer::resetBufferParams(unsigned int channels, unsigned int bufferSizeInFrames)
|
||||
{
|
||||
mBufferSizeInSamples = channels * bufferSizeInFrames;
|
||||
mWriteHead = 0;
|
||||
mReadHead = 0;
|
||||
mBufferLevelInSamples = 0;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::getNumSamplesInBuffer()
|
||||
{
|
||||
#if 0
|
||||
// Check that we are recording the buffer level correctly.
|
||||
// The second part of this clause excludes the case where the buffer is full -
|
||||
// here the calculated buffer level will be 0 (because read ptr == write ptr) but the actual buffer level will be mBufferSizeInSamples
|
||||
uint32_t calcBufferLevel = (mBufferSizeInSamples + mWriteHead - mReadHead) % mBufferSizeInSamples;
|
||||
if ((calcBufferLevel != mBufferLevelInSamples) && (!((calcBufferLevel == 0)&&(mBufferLevelInSamples == mBufferSizeInSamples))))
|
||||
{
|
||||
fprintf(stderr, "ERROR - Diff between intermediate buffer read/write pointers %d Buffer level recorded %d (diff %i)\n",
|
||||
calcBufferLevel, mBufferLevelInSamples, mBufferLevelInSamples - calcBufferLevel);
|
||||
}
|
||||
#endif
|
||||
|
||||
return mBufferLevelInSamples;
|
||||
}
|
||||
|
||||
|
||||
uint32_t IntermediateBuffer::getAvailableSpaceInSamplesInBuffer()
|
||||
{
|
||||
return mBufferSizeInSamples - getNumSamplesInBuffer();
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::writeAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mBufferMutex);
|
||||
|
||||
// Overrun protection
|
||||
int32_t samplesToWrite = numSamples;
|
||||
|
||||
if ((getNumSamplesInBuffer() + numSamples) > mBufferSizeInSamples)
|
||||
{
|
||||
samplesToWrite = mBufferSizeInSamples - getNumSamplesInBuffer();
|
||||
}
|
||||
|
||||
uint8_t *bufPtr = (uint8_t *)alsaBuffer;
|
||||
if (mWriteHead + samplesToWrite > mBufferSizeInSamples)
|
||||
{
|
||||
unsigned int firstBatchSize = mBufferSizeInSamples - mWriteHead;
|
||||
writeAlsaSamplesToBuffer(bufPtr, firstBatchSize, bytesPerSample);
|
||||
assert(mWriteHead == 0);
|
||||
writeAlsaSamplesToBuffer(bufPtr + (firstBatchSize * bytesPerSample), samplesToWrite - firstBatchSize, bytesPerSample);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeAlsaSamplesToBuffer(bufPtr, samplesToWrite, bytesPerSample);
|
||||
}
|
||||
|
||||
mBufferLevelInSamples += samplesToWrite;
|
||||
mBufferLog->logWriteEvent(samplesToWrite, getNumSamplesInBuffer());
|
||||
|
||||
return samplesToWrite;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::readAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mBufferMutex);
|
||||
|
||||
// Underrun protection
|
||||
if (numSamples > getNumSamplesInBuffer())
|
||||
{
|
||||
numSamples = getNumSamplesInBuffer();
|
||||
}
|
||||
|
||||
uint8_t *bufPtr = (uint8_t *)alsaBuffer;
|
||||
|
||||
if (mReadHead + numSamples > mBufferSizeInSamples)
|
||||
{
|
||||
unsigned int firstBatchSize = mBufferSizeInSamples - mReadHead;
|
||||
readAlsaSamplesFromBuffer(bufPtr, firstBatchSize, bytesPerSample);
|
||||
assert(mReadHead == 0);
|
||||
readAlsaSamplesFromBuffer(bufPtr + (firstBatchSize * bytesPerSample), numSamples - firstBatchSize, bytesPerSample);
|
||||
}
|
||||
else
|
||||
{
|
||||
readAlsaSamplesFromBuffer(bufPtr, numSamples, bytesPerSample);
|
||||
}
|
||||
mBufferLevelInSamples -= numSamples;
|
||||
mBufferLog->logReadEvent(numSamples, getNumSamplesInBuffer());
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
void IntermediateBuffer::writeAlsaSamplesToBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
memcpy((uint8_t *)(mBuffer) + (mWriteHead * bytesPerSample), buffer, bytesPerSample * numSamples);
|
||||
mWriteHead = (mWriteHead + numSamples) % mBufferSizeInSamples;
|
||||
}
|
||||
|
||||
void IntermediateBuffer::readAlsaSamplesFromBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
memcpy(buffer, (uint8_t *)mBuffer + (mReadHead * bytesPerSample), bytesPerSample * numSamples);
|
||||
mReadHead = (mReadHead + numSamples) % mBufferSizeInSamples;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::writeDepSamples(
|
||||
snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<const int32_t *> & danteRxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteRxHeadSamples,
|
||||
uint32_t samplesPerChannel)
|
||||
{
|
||||
uint32_t framesToWrite = numFrames;
|
||||
uint32_t framesToSkip = 0;
|
||||
static bool bufferFullLogState = false;
|
||||
|
||||
// Check for overrun
|
||||
uint32_t numSamples = numFrames * numAlsaChannels;
|
||||
if ((getNumSamplesInBuffer() + numSamples) > mBufferSizeInSamples)
|
||||
{
|
||||
framesToWrite = (mBufferSizeInSamples - getNumSamplesInBuffer()) / numAlsaChannels;
|
||||
framesToSkip = numFrames - framesToWrite;
|
||||
|
||||
if (framesToWrite == 0)
|
||||
{
|
||||
if (bufferFullLogState == false) // Limit the number of times that this message is displayed.
|
||||
{
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer overrun (ALSA hasn't read enough). Buffer full." << std::endl;
|
||||
bufferFullLogState = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer overrun (ALSA hasn't read enough). " <<
|
||||
"Skipping " << framesToSkip << " frames." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (framesToWrite != 0)
|
||||
{
|
||||
bufferFullLogState = false;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SND_PCM_FORMAT_S16_LE:
|
||||
writeDepSamplesS16LE(framesToWrite, danteRxChannels, numAlsaChannels, danteRxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_S32_LE:
|
||||
writeDepSamplesS32LE(framesToWrite, danteRxChannels, numAlsaChannels, danteRxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_FLOAT_LE:
|
||||
writeDepSamplesFLOATLE(framesToWrite, danteRxChannels, numAlsaChannels, danteRxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "unexpected format" << std::endl;
|
||||
}
|
||||
mBufferLevelInSamples += framesToWrite * numAlsaChannels;
|
||||
mBufferLog->logWriteEvent(framesToWrite * numAlsaChannels, getNumSamplesInBuffer());
|
||||
|
||||
if (framesToSkip)
|
||||
{
|
||||
// Skip the shared memory pointer past the samples we aren't going to use
|
||||
danteRxHeadSamples = (danteRxHeadSamples + framesToSkip) % samplesPerChannel;
|
||||
}
|
||||
|
||||
return framesToWrite;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::readDepSamples(
|
||||
snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<int32_t *> & danteTxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteTxHeadSamples,
|
||||
uint32_t samplesPerChannel)
|
||||
{
|
||||
uint32_t framesToRead = numFrames;
|
||||
uint32_t framesToSkip = 0;
|
||||
static bool bufferEmptyLogState = false;
|
||||
|
||||
// Underrun protection
|
||||
uint32_t numSamples = numFrames * numAlsaChannels;
|
||||
if (getNumSamplesInBuffer() < numSamples)
|
||||
{
|
||||
framesToRead = getNumSamplesInBuffer() / numAlsaChannels;
|
||||
framesToSkip = numFrames - framesToRead;
|
||||
|
||||
if (framesToRead == 0)
|
||||
{
|
||||
if (bufferEmptyLogState == false)
|
||||
{
|
||||
bufferEmptyLogState = true;
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer underrun (ALSA hasn't written enough). No frames in buffer." << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer underrun (ALSA hasn't written enough). " <<
|
||||
"Skipping " << framesToSkip << " frames." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (framesToRead != 0)
|
||||
{
|
||||
bufferEmptyLogState = false;
|
||||
}
|
||||
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SND_PCM_FORMAT_S16_LE:
|
||||
readDepSamples16LE(framesToRead, danteTxChannels, numAlsaChannels, danteTxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_S32_LE:
|
||||
readDepSamplesS32LE(framesToRead, danteTxChannels, numAlsaChannels, danteTxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_FLOAT_LE:
|
||||
readDepSamplesFLOATLE(framesToRead, danteTxChannels, numAlsaChannels, danteTxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "unexpected format" << std::endl;
|
||||
}
|
||||
mBufferLevelInSamples -= framesToRead * numAlsaChannels;
|
||||
mBufferLog->logReadEvent(framesToRead * numAlsaChannels, getNumSamplesInBuffer());
|
||||
|
||||
if (framesToSkip)
|
||||
{
|
||||
// Skip the shared memory pointer to provide zeroes for samples that we can't read
|
||||
// from the intermediate buffer
|
||||
danteTxHeadSamples = (danteTxHeadSamples + framesToSkip) % samplesPerChannel;
|
||||
}
|
||||
|
||||
return framesToRead;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::writeDepSamplesS16LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
int32_t sample32 = *(danteRxChannels[channel] + danteRxHeadSamples);
|
||||
*((int16_t *)(mBuffer) + mWriteHead) = (int16_t)(sample32 >> 16);
|
||||
mWriteHead++;
|
||||
}
|
||||
mWriteHead %= mBufferSizeInSamples;
|
||||
danteRxHeadSamples = (danteRxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::writeDepSamplesS32LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
*(mBuffer + mWriteHead) = *(danteRxChannels[channel] + danteRxHeadSamples);
|
||||
mWriteHead++;
|
||||
}
|
||||
mWriteHead %= mBufferSizeInSamples;
|
||||
danteRxHeadSamples = (danteRxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::writeDepSamplesFLOATLE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlasChannels, uint32_t & danteRxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
float* floatBuffer = (float*)mBuffer;
|
||||
double doubleTemp;
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlasChannels; channel++)
|
||||
{
|
||||
doubleTemp = *(danteRxChannels[channel] + danteRxHeadSamples);
|
||||
doubleTemp /= (double) INT32_MAX;
|
||||
*(floatBuffer + mWriteHead) = (float) doubleTemp;
|
||||
mWriteHead++;
|
||||
}
|
||||
mWriteHead %= mBufferSizeInSamples;
|
||||
danteRxHeadSamples = (danteRxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::readDepSamples16LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
int16_t sample16 = *((int16_t *)(mBuffer) + mReadHead);
|
||||
int32_t sample32 = sample16 << 16;
|
||||
*(danteTxChannels[channel] + danteTxHeadSamples) = sample32;
|
||||
// Write silence to prevent audio looping if input goes away
|
||||
*((int16_t *)(mBuffer) + mReadHead) = 0;
|
||||
mReadHead++;
|
||||
}
|
||||
mReadHead %= mBufferSizeInSamples;
|
||||
danteTxHeadSamples = (danteTxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::readDepSamplesS32LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
*(danteTxChannels[channel] + danteTxHeadSamples) = *(mBuffer + mReadHead);
|
||||
// Write silence to prevent audio looping if input goes away
|
||||
*(mBuffer + mReadHead) = 0;
|
||||
mReadHead++;
|
||||
}
|
||||
mReadHead %= mBufferSizeInSamples;
|
||||
danteTxHeadSamples = (danteTxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::readDepSamplesFLOATLE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
float* floatBuffer = (float*) mBuffer;
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; (channel < numAlsaChannels) && (mReadHead != mWriteHead); channel++)
|
||||
{
|
||||
*(danteTxChannels[channel] + danteTxHeadSamples) = (uint32_t) ( *(floatBuffer + mReadHead) * INT32_MAX );
|
||||
// Write silence to prevent audio looping if input goes away
|
||||
*(mBuffer + mReadHead) = 0;
|
||||
mReadHead++;
|
||||
}
|
||||
mReadHead %= mBufferSizeInSamples;
|
||||
danteTxHeadSamples = (danteTxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,316 @@
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dsoundcard_logging.hpp
|
||||
// Generates timestamped stats log in csv format which can be imported into a spreadsheet to examine buffer levels and thread timings.
|
||||
//
|
||||
#include <pthread.h>
|
||||
|
||||
#include "dsoundcard_logging.hpp"
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#if ((__ENABLE_BUFFER_LOGGING == 1) || (__ENABLE_FUNCTION_TRACE_LOGGING == 1))
|
||||
static uint64_t getCurrTimeInMicroseconds() {
|
||||
return Dante::getMonotonicValue() / 1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
DEPSoundcardFunctionLog functionLog("/tmp/dep_soundcard_function_log.csv");
|
||||
#endif
|
||||
|
||||
|
||||
#if (__ENABLE_BUFFER_LOGGING == 1)
|
||||
|
||||
#define SAMPLE_RATE_LOGGING_PERIOD_US (60 * 1000 * 1000)
|
||||
|
||||
void* bufferLogDumpThreadFunction(void* arg) {
|
||||
|
||||
DEPSoundcardBufferLog *bufferLog = (DEPSoundcardBufferLog *)arg;
|
||||
|
||||
if(bufferLog->mOutputFile.tellp() > (25 * 1024 * 1024)) // Limit file size to 25M
|
||||
{
|
||||
bufferLog->mOutputFile.close();
|
||||
bufferLog->open();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < bufferLog->mLogEntries.size(); i++)
|
||||
{
|
||||
bufferLog->mOutputFile
|
||||
<< bufferLog->mLogEntries[i].timestamp << ","
|
||||
<< bufferLog->mLogEntries[i].threadId << ","
|
||||
<< bufferLog->mLogEntries[i].samplesWritten << ","
|
||||
<< bufferLog->mLogEntries[i].writeOperation << ","
|
||||
<< bufferLog->mLogEntries[i].newBufferLevel << ","
|
||||
<< (bufferLog->mLogEntries[i].newBufferLevel * 1000) / 96 // ms - 48 kHz, 2 channels
|
||||
<< std::endl;
|
||||
}
|
||||
bufferLog->mLogEntries.clear();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEPSoundcardBufferLog::DEPSoundcardBufferLog(const char *logFileName)
|
||||
: mOutputFile(), mLogStartTime(0), mFirstPthreadId(0), mLogFileName(logFileName),
|
||||
mSamplesWritten(0), mSamplesRead(0),
|
||||
mPrevWriteSampleRateLogTime(0), mPrevReadSampleRateLogTime(0)
|
||||
|
||||
{
|
||||
mLogStartTime = getCurrTimeInMicroseconds();
|
||||
|
||||
open();
|
||||
mOutputFile << "0, 0, 0, 0" << std::endl;
|
||||
}
|
||||
|
||||
DEPSoundcardBufferLog::~DEPSoundcardBufferLog()
|
||||
{
|
||||
mOutputFile.close();
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::open()
|
||||
{
|
||||
mOutputFile.open(mLogFileName);
|
||||
mOutputFile << "Timestamp (us), Thread, Samples, Write operation, Buffer level (samples), Buffer level (us)" << std::endl;
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::logWriteEvent(const uint32_t bufferLevelChange, const uint32_t newBufferLevel)
|
||||
{
|
||||
mSamplesWritten += bufferLevelChange;
|
||||
logEvent(bufferLevelChange, newBufferLevel, true);
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::logReadEvent(const uint32_t bufferLevelChange, const uint32_t newBufferLevel)
|
||||
{
|
||||
mSamplesRead += bufferLevelChange;
|
||||
logEvent(bufferLevelChange, newBufferLevel, false);
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::logEvent(const uint32_t bufferLevelChange, const uint32_t newBufferLevel, bool write)
|
||||
{
|
||||
uint64_t buffTime = getCurrTimeInMicroseconds() - mLogStartTime;
|
||||
pthread_t threadId = pthread_self();
|
||||
|
||||
if (mFirstPthreadId == 0)
|
||||
{
|
||||
mFirstPthreadId = threadId;
|
||||
}
|
||||
|
||||
struct dsoundcardBufferLogEntry logEntry;
|
||||
|
||||
logEntry.timestamp = buffTime;
|
||||
logEntry.threadId = (threadId == mFirstPthreadId);
|
||||
logEntry.writeOperation = write;
|
||||
logEntry.samplesWritten = bufferLevelChange;
|
||||
logEntry.newBufferLevel = newBufferLevel;
|
||||
|
||||
mLogEntries.push_back(logEntry);
|
||||
|
||||
if (mLogEntries.size() == (MaxNumLogEntries - 1000)) // Kick off a low priority thread to print out the log entries
|
||||
{
|
||||
pthread_t thread;
|
||||
struct sched_param params;
|
||||
params.sched_priority = sched_get_priority_min(SCHED_OTHER); // Set the priority value here
|
||||
pthread_create(&thread, NULL, bufferLogDumpThreadFunction, this);
|
||||
pthread_setschedparam(thread, SCHED_OTHER, ¶ms);
|
||||
}
|
||||
|
||||
// Note that the sample rates displayed will be the total for all channels - i.e.
|
||||
// stereo 48kHz = 96000
|
||||
if (write)
|
||||
{
|
||||
displayWriteSampleRates(buffTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
displayReadSampleRates(buffTime);
|
||||
}
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::displayWriteSampleRates(uint64_t buffTime)
|
||||
{
|
||||
if (mPrevWriteSampleRateLogTime == 0)
|
||||
{
|
||||
mPrevWriteSampleRateLogTime = buffTime;
|
||||
}
|
||||
else if ((buffTime - mPrevWriteSampleRateLogTime) >= SAMPLE_RATE_LOGGING_PERIOD_US)
|
||||
{
|
||||
double measurementTime = (double)(buffTime - mPrevWriteSampleRateLogTime);
|
||||
double sampleRate = ((double)mSamplesWritten * 1000000.0) / measurementTime;
|
||||
mPrevWriteSampleRateLogTime = buffTime;
|
||||
mSamplesWritten = 0;
|
||||
|
||||
std::cerr << buffTime / (1000 * 1000) << "s Write samplerate " << sampleRate << " samples/sec (all channs)" << std::endl;
|
||||
}
|
||||
}
|
||||
void DEPSoundcardBufferLog::displayReadSampleRates(uint64_t buffTime)
|
||||
{
|
||||
if (mPrevReadSampleRateLogTime == 0)
|
||||
{
|
||||
mPrevReadSampleRateLogTime = buffTime;
|
||||
}
|
||||
else if ((buffTime - mPrevReadSampleRateLogTime) >= SAMPLE_RATE_LOGGING_PERIOD_US)
|
||||
{
|
||||
double measurementTime = (double)(buffTime - mPrevReadSampleRateLogTime);
|
||||
double sampleRate = ((double)mSamplesRead * 1000000.0) / measurementTime;
|
||||
mPrevReadSampleRateLogTime = buffTime;
|
||||
mSamplesRead = 0;
|
||||
|
||||
std::cerr << buffTime / (1000 * 1000) << "s Read samplerate " << sampleRate << " samples/sec (all channs)" << std::endl;
|
||||
}
|
||||
}
|
||||
#endif // __ENABLE_BUFFER_LOGGING == 1
|
||||
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
|
||||
void* functionLogDumpThreadFunction(void* arg) {
|
||||
|
||||
DEPSoundcardFunctionLog *functionLog = (DEPSoundcardFunctionLog *)arg;
|
||||
|
||||
if (functionLog->mLogEntries.size() >= functionLog->MaxNumLogEntries)
|
||||
{
|
||||
std::cerr << "Potential function log overflow" << std::endl;
|
||||
}
|
||||
|
||||
if(functionLog->mOutputFile.tellp() > (25 * 1024 * 1024)) // Limit file size to 25M
|
||||
{
|
||||
functionLog->mOutputFile.close();
|
||||
functionLog->open();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < functionLog->mLogEntries.size(); i++)
|
||||
{
|
||||
bool functionStartMarker = functionLog->mLogEntries[i].duration == (uint64_t)(-1);
|
||||
|
||||
if(functionStartMarker)
|
||||
{
|
||||
functionLog->mLogEntries[i].duration = 0;
|
||||
}
|
||||
|
||||
functionLog->mOutputFile
|
||||
<< functionLog->mLogEntries[i].timestamp << ","
|
||||
<< functionLog->mLogEntries[i].functionName << ","
|
||||
<< functionStartMarker << ","
|
||||
<< functionLog->mLogEntries[i].duration
|
||||
<< std::endl;
|
||||
}
|
||||
functionLog->mLogEntries.clear();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEPSoundcardFunctionLog::DEPSoundcardFunctionLog(const char *logFileName)
|
||||
: mOutputFile(), mLogStartTime(0), mLogFileName(logFileName)
|
||||
{
|
||||
mLogStartTime = getCurrTimeInMicroseconds();
|
||||
|
||||
open();
|
||||
mOutputFile << "0, null" << std::endl;
|
||||
}
|
||||
|
||||
DEPSoundcardFunctionLog::~DEPSoundcardFunctionLog()
|
||||
{
|
||||
// Dump the remaining contents of the log
|
||||
functionLogDumpThreadFunction(this);
|
||||
mOutputFile.close();
|
||||
}
|
||||
|
||||
void DEPSoundcardFunctionLog::open()
|
||||
{
|
||||
mOutputFile.open(mLogFileName);
|
||||
mOutputFile << "Timestamp (us), Function, Start / End, Duration (us)" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
uint64_t DEPSoundcardFunctionLog::logFunctionStart(const char *functionName)
|
||||
{
|
||||
uint64_t buffTime = getCurrTimeInMicroseconds() - mLogStartTime;
|
||||
|
||||
if(mLogEntries.size() < MaxNumLogEntries)
|
||||
{
|
||||
struct dsoundcardFunctionLogEntry logEntry;
|
||||
|
||||
logEntry.timestamp = buffTime;
|
||||
strncpy(logEntry.functionName, functionName, MaxFunctionNameLength);
|
||||
logEntry.duration = (uint64_t)(-1);
|
||||
mLogEntries.push_back(logEntry);
|
||||
}
|
||||
|
||||
if (mLogEntries.size() == (MaxNumLogEntries / 2)) // Kick off a low priority thread to print out the log entries
|
||||
{
|
||||
pthread_t thread;
|
||||
struct sched_param params;
|
||||
params.sched_priority = sched_get_priority_min(SCHED_OTHER); // Set the priority value here
|
||||
pthread_create(&thread, NULL, functionLogDumpThreadFunction, this);
|
||||
pthread_setschedparam(thread, SCHED_OTHER, ¶ms);
|
||||
}
|
||||
return buffTime;
|
||||
}
|
||||
|
||||
void DEPSoundcardFunctionLog::logFunctionEnd(const char *functionName, uint64_t startTime)
|
||||
{
|
||||
uint64_t buffTime = getCurrTimeInMicroseconds() - mLogStartTime;
|
||||
uint64_t duration = buffTime - startTime;
|
||||
|
||||
if(mLogEntries.size() < MaxNumLogEntries)
|
||||
{
|
||||
struct dsoundcardFunctionLogEntry logEntry;
|
||||
|
||||
logEntry.timestamp = buffTime;
|
||||
strncpy(logEntry.functionName, functionName, MaxFunctionNameLength);
|
||||
logEntry.duration = duration;
|
||||
mLogEntries.push_back(logEntry);
|
||||
}
|
||||
}
|
||||
#endif // __ENABLE_FUNCTION_TRACE_LOGGING == 1
|
||||
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,431 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// dtest.cpp
|
||||
// Audio test example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "3rd_party/sinewave.hpp"
|
||||
#include "3rd_party/pink.hpp"
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
#define ZERO_INDEXED(n) ( (n) - 1)
|
||||
#define ONE_INDEXED(n) ( (n) + 1)
|
||||
|
||||
#define MAX_SAMPLE_PROCESS_BLOCK 4096
|
||||
|
||||
#define SIGNAL_GENERATOR_DEFAULT 'n'
|
||||
#define OUTPUT_CHANNEL_DEFAULT 0
|
||||
#define FREQUENCY_DEFAULT 440.0
|
||||
#define SECONDS_PER_CHANNEL_DEFAULT 1.0
|
||||
#define AMPLITUDE_DEFAULT 0.5
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signalHandler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
class AudioTest
|
||||
{
|
||||
public:
|
||||
AudioTest(Dante::Buffers & buffers, int txLatencySamples, char signalGenerator, int outputChannel,
|
||||
double frequency, double secondsPerChannel, double amplitude)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), mOutputChannel(outputChannel), mFrequency(frequency),
|
||||
mSecondsPerChannel(secondsPerChannel), mAmplitude(amplitude), mCurrentChannel(), mSamplesPerPeriod(),
|
||||
mSamplesPerChannel(), mDanteTxHeadSamples(), mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels()
|
||||
{
|
||||
if (signalGenerator =='n')
|
||||
{
|
||||
init_pink_noise();
|
||||
generator_ptr = &get_pink_samples;
|
||||
}
|
||||
else
|
||||
{
|
||||
generator_ptr = &get_tone_samples;
|
||||
}
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
pullSamplesFromGenerator(n1);
|
||||
assert(mDanteTxHeadSamples == mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = 0;
|
||||
pullSamplesFromGenerator(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
pullSamplesFromGenerator(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(audioTest, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
|
||||
switch(metadata->audio.encoding)
|
||||
{
|
||||
case 8:
|
||||
mMaxEncodingValue = (double)0x7F;
|
||||
break;
|
||||
case 16:
|
||||
mMaxEncodingValue = (double)0x7FFF;
|
||||
break;
|
||||
case 24:
|
||||
mMaxEncodingValue = (double)0x7FFFFF;
|
||||
break;
|
||||
case 32:
|
||||
mMaxEncodingValue = (double)0x7FFFFFFF;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Error encoding value unexpected" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
if (mOutputChannel >= (int) mDanteTxChannels.size())
|
||||
{
|
||||
std::cout << "The given channel index '" << mOutputChannel << "' was greater than the number of dep transmitter channels" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
|
||||
unsigned int mTxLatencySamples;
|
||||
int mOutputChannel;
|
||||
double mFrequency;
|
||||
double mSecondsPerChannel;
|
||||
double mAmplitude;
|
||||
unsigned int mCurrentChannel;
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
|
||||
int32_t sampleBuffer[MAX_SAMPLE_PROCESS_BLOCK];
|
||||
uint32_t mSamplesSoFar = 0;
|
||||
double mMaxEncodingValue;
|
||||
|
||||
void (*generator_ptr)(double, double, unsigned int, double, double, int32_t*, uint16_t, double);
|
||||
|
||||
void pullSamplesFromGenerator(unsigned int numSamples)
|
||||
{
|
||||
auto * metadata = &mBuffers.getHeader()->audio;
|
||||
|
||||
unsigned int samplesThisCycle;
|
||||
|
||||
// -1 indicates we should cycle through available channels
|
||||
if (mOutputChannel == -1)
|
||||
{
|
||||
unsigned int nextChannel = (unsigned int)( mSamplesSoFar / ( (double)metadata->sample_rate * mSecondsPerChannel ) ) % mDanteTxChannels.size();
|
||||
if (nextChannel != mCurrentChannel)
|
||||
{
|
||||
mCurrentChannel = nextChannel;
|
||||
std::cout << "Now playing over channel " << ONE_INDEXED(mCurrentChannel) << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentChannel = mOutputChannel;
|
||||
}
|
||||
|
||||
do {
|
||||
samplesThisCycle = numSamples > MAX_SAMPLE_PROCESS_BLOCK ? MAX_SAMPLE_PROCESS_BLOCK : numSamples;
|
||||
numSamples -= samplesThisCycle;
|
||||
|
||||
double time = ((double) mSamplesSoFar) / ((double) metadata->sample_rate);
|
||||
double timedelta = ((double) 1.0) / ((double) metadata->sample_rate);
|
||||
|
||||
(*generator_ptr)(time, timedelta, samplesThisCycle, mFrequency, mAmplitude, sampleBuffer, metadata->encoding, mMaxEncodingValue);
|
||||
|
||||
unsigned int numBytes = samplesThisCycle * sizeof(int32_t);
|
||||
memcpy(mDanteTxChannels[mCurrentChannel] + mDanteTxHeadSamples, sampleBuffer, numBytes);
|
||||
|
||||
mDanteTxHeadSamples += samplesThisCycle;
|
||||
mSamplesSoFar += samplesThisCycle;
|
||||
} while (numSamples > 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 48 // 1ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
|
||||
char signalGenerator = SIGNAL_GENERATOR_DEFAULT;
|
||||
int outputChannel = OUTPUT_CHANNEL_DEFAULT;
|
||||
double secondsPerChannel = SECONDS_PER_CHANNEL_DEFAULT;
|
||||
double frequency = FREQUENCY_DEFAULT;
|
||||
double amplitude = AMPLITUDE_DEFAULT;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "l:s:f:a:t:c:g:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'g':
|
||||
signalGenerator = optarg[0];
|
||||
if (signalGenerator != 'n' && signalGenerator != 't')
|
||||
{
|
||||
std::cout << "Signal generator must be one of 'n' (noise), 't' (tone)" <<
|
||||
std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
outputChannel = atoi(optarg);
|
||||
if (outputChannel < 0)
|
||||
{
|
||||
std::cout << "Output channel must be greater than or equal to 0." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
frequency = atof(optarg);
|
||||
if (frequency < 30.0 || frequency > 8000.0)
|
||||
{
|
||||
std::cout << "Tone frequencies must be in the range 30-8000hz." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
secondsPerChannel = atof(optarg);
|
||||
if (secondsPerChannel < 0)
|
||||
{
|
||||
std::cout << "Seconds per channel must be greater than 0." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
amplitude = atof(optarg);
|
||||
if (amplitude < 0 || amplitude > 1)
|
||||
{
|
||||
std::cout << "Amplitude must be in the range 0-1." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Playback Tx Latency (# samples) - default is " << PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-g Signal generator - default is " << SIGNAL_GENERATOR_DEFAULT << std::endl <<
|
||||
"\t\t n -> noise" << std::endl <<
|
||||
"\t\t t -> tone" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-c Output channel - default is " << OUTPUT_CHANNEL_DEFAULT << std::endl <<
|
||||
"\t\t Setting the output channel to '0' cycles through the available channels" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-f Tone frequency (Hz) - default is " << FREQUENCY_DEFAULT << "Hz" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-t Time spent on each channel when cycling (seconds) - default is " << SECONDS_PER_CHANNEL_DEFAULT << "s" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-a Amplitude of tone - default is " << AMPLITUDE_DEFAULT <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
std::cout << "Signal generator: " << signalGenerator << std::endl;
|
||||
std::cout << "Output channel: " << outputChannel << std::endl;
|
||||
std::cout << "Frequency: " << frequency << "Hz" << std::endl;
|
||||
std::cout << "Seconds per channel: " << secondsPerChannel << "s" << std::endl;
|
||||
std::cout << "Amplitude: " << amplitude << std::endl;
|
||||
|
||||
#endif
|
||||
|
||||
AudioTest audioTest(buffers, txLatencySamples, signalGenerator, ZERO_INDEXED(outputChannel), frequency, secondsPerChannel, amplitude);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&audioTest](unsigned int numPeriods) { audioTest.work(numPeriods); };
|
||||
auto _reset = [&audioTest]() { audioTest.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
// DanteTrace.cpp
|
||||
// Audio trace example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "versions.h"
|
||||
#include <signal.h>
|
||||
#include <iostream>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
static void traceMetadata(const volatile Dante::buffer_header_t & m)
|
||||
{
|
||||
std::cerr
|
||||
<< "METADATA"
|
||||
<< " magic=0x" << std::hex << m.metadata.magic_marker << std::dec
|
||||
<< " buffer_length=" << m.metadata.buffer_length
|
||||
<< " header_length=" << m.metadata.metadata_header_length
|
||||
<< " flags=0x" << std::hex << m.metadata.flags << std::dec
|
||||
<< " channel0_offset_bytes=" << m.metadata.first_tx_channel_offset_bytes << "/" << m.metadata.first_rx_channel_offset_bytes
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
static void traceLayout(const volatile Dante::buffer_header_t & m)
|
||||
{
|
||||
std::cerr
|
||||
<< "AUDIO"
|
||||
<< " samplerate=" << m.audio.sample_rate
|
||||
<< " encoding=" << m.audio.encoding
|
||||
<< " samples/channel=" << m.audio.samples_per_channel
|
||||
<< " bytes/channels=" << m.audio.bytes_per_channel
|
||||
<< " num_channels=" << m.audio.num_tx_channels << "/" << m.audio.num_rx_channels
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
static void traceTime(const volatile Dante::buffer_header_t & m)
|
||||
{
|
||||
std::cerr
|
||||
<< "TIME"
|
||||
<< " epoch=" << m.time.epoch_seconds << "." << m.time.epoch_samples
|
||||
<< " reset_count=" << m.metadata.reset_count << " samples_per_period=" << m.time.samples_per_period
|
||||
<< " period_count=" << m.time.period_count << " clock_drift_ppb=" << m.time.clock_drift_ppb
|
||||
<< " monotonic=" << m.time.monotonic
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
|
||||
static void traceNetworkMonotonicTime(const volatile Dante::buffer_header_t & m, uint32_t samplerate, unsigned int intervalMs)
|
||||
{
|
||||
static uint64_t lastNetwork = 0;
|
||||
static uint64_t lastMonotonic = 0;
|
||||
|
||||
static uint64_t rate = 0;
|
||||
#ifdef WIN32
|
||||
{
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
rate = freq.QuadPart;
|
||||
}
|
||||
#else
|
||||
{
|
||||
rate = 1000000000;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Collect the network and monotonic time from the
|
||||
// memory buffers. If DEP is currently updating these
|
||||
// values, DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC will be set.
|
||||
// This is one method to capture a consistent snapshot of these values.
|
||||
uint64_t network = 0;
|
||||
uint64_t monotonic = 0;
|
||||
bool consistent = false;
|
||||
while (!consistent)
|
||||
{
|
||||
network = m.time.period_count * m.time.samples_per_period;
|
||||
monotonic = m.time.monotonic;
|
||||
|
||||
Dante::memory_barrier_acquire();
|
||||
if (m.metadata.flags & DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Dante::memory_barrier_acquire();
|
||||
|
||||
if (network == m.time.period_count * m.time.samples_per_period &&
|
||||
monotonic == m.time.monotonic)
|
||||
{
|
||||
consistent = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t monotonicDelta = monotonic - lastMonotonic;
|
||||
double monotonicDeltaNs = (((double) monotonicDelta) * 1000000000.0) / ((double) rate);
|
||||
|
||||
if (monotonicDelta > ((double) intervalMs) * 1000000.0)
|
||||
{
|
||||
uint64_t networkDelta = (network - lastNetwork);
|
||||
if (samplerate != 0)
|
||||
{
|
||||
double networkDeltaNs = (((double) networkDelta) * 1000000000.0) / ((double) samplerate);
|
||||
double ratio = monotonicDeltaNs / networkDeltaNs;
|
||||
std::cerr << "network time in samples " << network << " (" << networkDeltaNs << "ns delta)"
|
||||
<< " monotonic counter " << monotonic << " (" << monotonicDeltaNs << "ns delta)"
|
||||
<< " ratio=" << ratio << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note that if samplerate is zero in the header, DEP audio will be muted
|
||||
std::cerr << "network time in samples " << network << " (time in ns unknown)"
|
||||
<< " monotonic counter " << monotonic << "ns (" << monotonicDeltaNs << "ns delta)"
|
||||
<< " Samplerate unavailable" << std::endl;
|
||||
}
|
||||
lastNetwork = network;
|
||||
lastMonotonic = monotonic;
|
||||
}
|
||||
}
|
||||
|
||||
static void work(Dante::Buffers & buffers, unsigned int numPeriods, uint32_t samplerate, unsigned int interval)
|
||||
{
|
||||
traceNetworkMonotonicTime(*buffers.getHeader(), samplerate, interval);
|
||||
}
|
||||
|
||||
static void reset(Dante::Buffers & buffers, uint32_t *samplerate)
|
||||
{
|
||||
std::cerr << "-----------------------------------------" << std::endl;
|
||||
*samplerate = buffers.getHeader()->audio.sample_rate;
|
||||
traceMetadata(*buffers.getHeader());
|
||||
traceLayout(*buffers.getHeader());
|
||||
traceTime(*buffers.getHeader());
|
||||
}
|
||||
|
||||
// Sample at 10Hz
|
||||
#define SAMPLING_INTERVAL_MS 100
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "v")) != -1) {
|
||||
switch (opt) {
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
|
||||
uint32_t samplerate = 0;
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect("DanteEP", false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
traceMetadata(*buffers.getHeader());
|
||||
traceLayout(*buffers.getHeader());
|
||||
traceTime(*buffers.getHeader());
|
||||
|
||||
auto _work = [&buffers, &samplerate](unsigned int numPeriods) { work(buffers, numPeriods, samplerate, SAMPLING_INTERVAL_MS); };
|
||||
auto _reset = [&buffers, &samplerate]() { reset(buffers, &samplerate); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,41 @@
|
||||
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES TOOLCHAINS_DIR)
|
||||
|
||||
SET(CMAKE_SYSTEM_NAME Linux)
|
||||
SET(CMAKE_SYSTEM_VERSION 1)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
# toolchain location
|
||||
set(TOOLCHAIN_URL https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64/tarballs/aarch64--glibc--stable-2020.08-1.tar.bz2)
|
||||
|
||||
set(TOOLCHAIN_TAR aarch64--glibc--stable-2020.08-1.tar.bz2)
|
||||
|
||||
set(TOOLCHAIN_DIR aarch64--glibc--stable-2020.08-1)
|
||||
# for autotools
|
||||
set(TOOLCHAIN_TARGET aarch64-linux-gnu)
|
||||
|
||||
set(TOOLCHAIN_NAME aarch64-buildroot-linux-gnu)
|
||||
set(TOOLCHAIN_PATH ${CMAKE_CURRENT_LIST_DIR}/toolchains/${TOOLCHAIN_DIR})
|
||||
set(TOOLCHAIN_PREFIX ${CMAKE_CURRENT_LIST_DIR}/toolchains/${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_NAME})
|
||||
|
||||
if(NOT EXISTS ${TOOLCHAIN_PATH})
|
||||
execute_process(COMMAND mkdir -p ${CMAKE_CURRENT_LIST_DIR}/toolchains)
|
||||
execute_process(COMMAND wget --progress=dot:giga ${TOOLCHAIN_URL})
|
||||
execute_process(COMMAND tar -xf ${TOOLCHAIN_TAR} -C ${CMAKE_CURRENT_LIST_DIR}/toolchains)
|
||||
endif()
|
||||
|
||||
# specify the cross compiler
|
||||
SET(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-gcc)
|
||||
SET(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-g++)
|
||||
set(CMAKE_STRIP ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-strip CACHE INTERNAL "")
|
||||
set(CMAKE_OBJCOPY ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-objcopy CACHE INTERNAL "")
|
||||
SET(CMAKE_LD ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-ld)
|
||||
set(CMAKE_AR ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-ar CACHE FILEPATH "Archiver")
|
||||
|
||||
|
||||
set(CMAKE_SYSROOT ${TOOLCHAIN_PATH}/${TOOLCHAIN_NAME}/sysroot)
|
||||
|
||||
# search for programs in the build host directories
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
# for libraries and headers in the target directories
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
@@ -0,0 +1 @@
|
||||
1.2.2.1
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include "versions.h"
|
||||
|
||||
const char * DEP_EXAMPLES_HASH_FULL = "4013852115ba69f9bbeb740816f3a1435dc2c075";
|
||||
const char * DEP_EXAMPLES_HASH_SHORT = "40138521";
|
||||
const bool DEP_EXAMPLES_STATE_CHANGES = 0 ? true : false;
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_HASH = "4013852115ba69f9bbeb740816f3a1435dc2c075";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_HASH_SHORT = "40138521";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG = "dep_examples_v1.2.2.rc1";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_VERSION = "1.2.2.rc1";
|
||||
const bool DEP_EXAMPLES_COMMITS_SINCE_COMPONENT_TAG = 0 ? true : false;
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_VERSION_TYPE = "";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_VERSION_FULL = "1.2.2.1";
|
||||
const unsigned int DEP_EXAMPLES_VERSION_MAJOR = 1;
|
||||
const unsigned int DEP_EXAMPLES_VERSION_MINOR = 2;
|
||||
const unsigned int DEP_EXAMPLES_VERSION_PATCH = 2;
|
||||
const char * DEP_EXAMPLES_VERSION_SUFFIX = "rc1";
|
||||
|
||||
#define PRINT_VERSION_BUF_SIZE 256
|
||||
const char * getVersionInfo() {
|
||||
static char versionStr[PRINT_VERSION_BUF_SIZE];
|
||||
snprintf(versionStr, sizeof(versionStr), "dep_examples : v%s:%s:%s",
|
||||
DEP_EXAMPLES_COMPONENT_TAG_VERSION,
|
||||
DEP_EXAMPLES_COMPONENT_TAG_VERSION_TYPE,
|
||||
DEP_EXAMPLES_HASH_SHORT);
|
||||
return versionStr;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
//
|
||||
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
|
||||
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
|
||||
// Software, provided always that the following conditions are met:
|
||||
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
|
||||
// or be used in a product, solution or offering which requires the use of another licensed Audinate
|
||||
// product, solution or offering. The Software is not for use as a standalone product without any
|
||||
// reference to Audinate’s products;
|
||||
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
|
||||
// or expectation of performance, compatibility, support, updates or security; and
|
||||
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
|
||||
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
|
||||
// solely in the form of machine-executable object code generated by the source language processor.
|
||||
//
|
||||
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT.
|
||||
//
|
||||
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
|
||||
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
|
||||
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
|
||||
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
|
||||
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
|
||||
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
|
||||
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
|
||||
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
|
||||
//
|
||||
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
|
||||
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
|
||||
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
|
||||
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
|
||||
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATE’S OPTION) TO ONE OF THE
|
||||
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
|
||||
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
|
||||
// 4.2. THE REPAIR OF THE SOFTWARE;
|
||||
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
|
||||
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
|
||||
//
|
||||
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
|
||||
// the trade names, or product names of Audinate.
|
||||
//
|
||||
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
|
||||
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
|
||||
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
|
||||
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
|
||||
// liability.
|
||||
//
|
||||
#pragma once
|
||||
#ifndef DEP_AUDIO_VERSIONS_H
|
||||
#define DEP_AUDIO_VERSIONS_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
extern const char * DEP_AUDIO_HASH_FULL ;
|
||||
extern const char * DEP_AUDIO_HASH_SHORT ;
|
||||
extern const bool DEP_AUDIO_STATE_CHANGES ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH_SHORT ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION ;
|
||||
extern const bool DEP_AUDIO_COMMITS_SINCE_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_TYPE ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_FULL ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MAJOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MINOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_PATCH ;
|
||||
extern const char * DEP_AUDIO_VERSION_SUFFIX ;
|
||||
|
||||
const char * getVersionInfo();
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
Reference in New Issue
Block a user