Skip to content

Commit 4beb173

Browse files
authored
1.6.1.1: Multi-channel audio, spatial audio, and packet loss recovery (#4)
2 parents 274b2c4 + afcb84a commit 4beb173

74 files changed

Lines changed: 12918 additions & 286 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 93 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@ on:
1616
jobs:
1717
# ═══════════════════════════════════════════════════════════════════════════
1818
# Cross-Platform Build (Linux/Windows native libraries and JNI)
19+
# Runs in parallel for base and full variants
1920
# ═══════════════════════════════════════════════════════════════════════════
20-
21+
2122
build-cross-platform:
22-
name: Build Cross-Platform Libraries (Linux/Windows)
23+
name: Build Cross-Platform (${{ matrix.variant }})
2324
runs-on: ubuntu-latest
25+
strategy:
26+
matrix:
27+
variant: [base, full]
2428
steps:
2529
- name: Checkout
2630
uses: actions/checkout@v4
2731
with:
2832
submodules: recursive
29-
33+
3034
- name: Fetch git tags for version detection
3135
run: |
3236
echo "Fetching tags for opus submodule to enable version detection..."
@@ -42,12 +46,12 @@ jobs:
4246
sudo apt-get update && sudo apt-get install -y \
4347
build-essential autoconf automake libtool pkg-config \
4448
cmake mingw-w64 gcc-aarch64-linux-gnu curl unzip
45-
49+
4650
- name: Run autogen
4751
run: |
4852
cd third_party/opus
4953
./autogen.sh
50-
54+
5155
- name: Debug package_version after autogen
5256
run: |
5357
echo "=== Checking Opus submodule ==="
@@ -64,103 +68,120 @@ jobs:
6468
else
6569
echo "ERROR: package_version file not created by autogen.sh"
6670
fi
67-
71+
6872
- name: Download Windows JDK
6973
run: |
7074
curl -L -o /tmp/jdk-win.zip https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.11+9/OpenJDK17U-jdk_x64_windows_hotspot_17.0.11_9.zip
7175
mkdir -p /tmp/winjdk
7276
unzip /tmp/jdk-win.zip -d /tmp/winjdk
7377
rm /tmp/jdk-win.zip
74-
78+
79+
- name: Set build configuration
80+
id: config
81+
run: |
82+
if [ "${{ matrix.variant }}" == "full" ]; then
83+
echo "extra_configure_flags=--enable-dred --enable-osce --enable-qext" >> $GITHUB_OUTPUT
84+
echo "cmake_dred_flag=-DKOPUS_ENABLE_DRED=ON" >> $GITHUB_OUTPUT
85+
else
86+
echo "extra_configure_flags=" >> $GITHUB_OUTPUT
87+
echo "cmake_dred_flag=" >> $GITHUB_OUTPUT
88+
fi
89+
7590
- name: Build Linux x86_64 native library
7691
run: |
7792
cd third_party/opus
7893
make distclean || true
79-
./configure --host=x86_64-linux-gnu --disable-shared --disable-rtcd --enable-static --prefix=/tmp/out/linux/x86_64 CFLAGS="-O3 -fPIC"
94+
./configure --host=x86_64-linux-gnu --disable-shared --disable-rtcd --enable-static ${{ steps.config.outputs.extra_configure_flags }} --prefix=/tmp/out/linux/x86_64 CFLAGS="-O3 -fPIC"
8095
make -j$(nproc) && make install
81-
96+
8297
- name: Build Linux arm64 native library
8398
run: |
8499
cd third_party/opus
85100
make distclean || true
86-
CC=aarch64-linux-gnu-gcc ./configure --host=aarch64-linux-gnu --disable-shared --disable-rtcd --enable-static --prefix=/tmp/out/linux/arm64 CFLAGS="-O3"
101+
CC=aarch64-linux-gnu-gcc ./configure --host=aarch64-linux-gnu --disable-shared --disable-rtcd --enable-static ${{ steps.config.outputs.extra_configure_flags }} --prefix=/tmp/out/linux/arm64 CFLAGS="-O3"
87102
make -j$(nproc) && make install
88-
103+
89104
- name: Build Windows x86_64 native library
90105
run: |
91106
cd third_party/opus
92107
make distclean || true
93-
CC=x86_64-w64-mingw32-gcc ./configure --host=x86_64-w64-mingw32 --disable-shared --enable-static --prefix=/tmp/out/windows/x86_64 CFLAGS="-D_FORTIFY_SOURCE=0 -O3"
108+
CC=x86_64-w64-mingw32-gcc ./configure --host=x86_64-w64-mingw32 --disable-shared --enable-static ${{ steps.config.outputs.extra_configure_flags }} --prefix=/tmp/out/windows/x86_64 CFLAGS="-D_FORTIFY_SOURCE=0 -O3"
94109
make -j$(nproc) && make install
95-
110+
96111
- name: Build JNI libraries
97112
run: |
98113
# Linux x86_64 JNI
99114
cmake -B /tmp/jni-linux-x64 -S kopus/jni \
100115
-DJAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
101116
-DOPUS_INCLUDE_DIR=/tmp/out/linux/x86_64/include/opus \
102-
-DOPUS_LIB_PATH=/tmp/out/linux/x86_64/lib/libopus.a
117+
-DOPUS_LIB_PATH=/tmp/out/linux/x86_64/lib/libopus.a \
118+
${{ steps.config.outputs.cmake_dred_flag }}
103119
cmake --build /tmp/jni-linux-x64 --config Release
104-
105-
# Linux arm64 JNI
120+
121+
# Linux arm64 JNI
106122
cmake -B /tmp/jni-linux-arm64 -S kopus/jni \
107123
-DJAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
108124
-DOPUS_INCLUDE_DIR=/tmp/out/linux/arm64/include/opus \
109125
-DOPUS_LIB_PATH=/tmp/out/linux/arm64/lib/libopus.a \
110126
-DCMAKE_SYSTEM_NAME=Linux \
111127
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
112-
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc
128+
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
129+
${{ steps.config.outputs.cmake_dred_flag }}
113130
cmake --build /tmp/jni-linux-arm64 --config Release
114-
131+
115132
# Windows x86_64 JNI
116133
cmake -B /tmp/jni-win-x64 -S kopus/jni \
117134
-DJAVA_HOME=/tmp/winjdk/jdk-17.0.11+9 \
118135
-DOPUS_INCLUDE_DIR=/tmp/out/windows/x86_64/include/opus \
119136
-DOPUS_LIB_PATH=/tmp/out/windows/x86_64/lib/libopus.a \
120137
-DCMAKE_SYSTEM_NAME=Windows \
121-
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc
138+
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
139+
${{ steps.config.outputs.cmake_dred_flag }}
122140
cmake --build /tmp/jni-win-x64 --config Release
123-
141+
124142
- name: Collect artifacts
125143
run: |
126144
mkdir -p desktop-native-libs/linux/x86_64
127-
mkdir -p desktop-native-libs/linux/arm64
145+
mkdir -p desktop-native-libs/linux/arm64
128146
mkdir -p desktop-native-libs/windows/x86_64
129-
130147
cp /tmp/jni-linux-x64/libopus_jni.so desktop-native-libs/linux/x86_64/
131148
cp /tmp/jni-linux-arm64/libopus_jni.so desktop-native-libs/linux/arm64/
132149
cp /tmp/jni-win-x64/libopus_jni.dll desktop-native-libs/windows/x86_64/
133-
150+
134151
- name: Upload Desktop Native Artifacts
135152
uses: actions/upload-artifact@v4
136153
with:
137-
name: desktop-native-libs
154+
name: desktop-native-libs-${{ matrix.variant }}
138155
path: desktop-native-libs/
139156
retention-days: 1
140157

141158
# ═══════════════════════════════════════════════════════════════════════════
142-
# Comprehensive macOS Build (Android, Apple platforms, iOS, JVM with all natives)
159+
# macOS Build (Android, Apple platforms, iOS, JVM with all natives)
160+
# Runs in parallel for base and full variants
143161
# ═══════════════════════════════════════════════════════════════════════════
144-
145-
build-macos-comprehensive:
146-
name: Build All Platforms (macOS runner)
162+
163+
build-macos:
164+
name: Build & Test (${{ matrix.variant }})
147165
runs-on: macos-latest
148166
needs: [build-cross-platform]
167+
strategy:
168+
matrix:
169+
variant: [base, full]
149170
steps:
150171
- name: Checkout
151172
uses: actions/checkout@v4
152173
with:
153174
submodules: recursive
154-
175+
155176
- name: Setup Java
156177
uses: actions/setup-java@v4
157178
with:
158179
distribution: 'temurin'
159180
java-version: '17'
160-
181+
161182
- name: Setup Gradle
162183
uses: gradle/actions/setup-gradle@v3
163-
184+
164185
- name: Fetch git tags for version detection
165186
run: |
166187
echo "Fetching tags for opus submodule to enable version detection..."
@@ -174,45 +195,70 @@ jobs:
174195
- name: Download Desktop Native Libraries
175196
uses: actions/download-artifact@v4
176197
with:
177-
name: desktop-native-libs
198+
name: desktop-native-libs-${{ matrix.variant }}
178199
path: kopus/build/jni_docker/
179200

180201
- name: Install autotools dependencies
181202
run: |
182203
brew install autoconf automake libtool
183-
204+
205+
- name: Set build configuration
206+
id: config
207+
run: |
208+
if [ "${{ matrix.variant }}" == "full" ]; then
209+
echo "script_flag=--full" >> $GITHUB_OUTPUT
210+
echo "gradle_flag=-Pkopus.full=true" >> $GITHUB_OUTPUT
211+
echo "artifact_suffix=-full" >> $GITHUB_OUTPUT
212+
else
213+
echo "script_flag=" >> $GITHUB_OUTPUT
214+
echo "gradle_flag=" >> $GITHUB_OUTPUT
215+
echo "artifact_suffix=" >> $GITHUB_OUTPUT
216+
fi
217+
184218
- name: Build Opus for Apple Platforms
185-
run: ./scripts/build_opus_apple.sh
219+
run: ./scripts/build_opus_apple.sh ${{ steps.config.outputs.script_flag }}
186220

187221
- name: Build macOS JNI Libraries
188-
run: ./scripts/build_opus_jni.sh
222+
run: ./scripts/build_opus_jni.sh ${{ steps.config.outputs.script_flag }}
223+
224+
- name: Run JVM Tests
225+
run: ./gradlew :kopus:jvmTest -Pci.skip.native.build ${{ steps.config.outputs.gradle_flag }} --no-configuration-cache
226+
227+
- name: Upload Test Results
228+
if: always()
229+
uses: actions/upload-artifact@v4
230+
with:
231+
name: jvm-test-results-${{ matrix.variant }}
232+
path: |
233+
kopus/build/reports/tests/jvmTest/
234+
kopus/build/test-results/jvmTest/
235+
retention-days: 7
189236

190-
- name: Publish All Platforms to Maven Local
191-
run: ./gradlew publishToMavenLocal -Pci.skip.native.build --no-configuration-cache
237+
- name: Publish to Maven Local
238+
run: ./gradlew publishToMavenLocal -Pci.skip.native.build ${{ steps.config.outputs.gradle_flag }} --no-configuration-cache
192239
env:
193240
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
194241
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
195242
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
196243

197-
- name: Publish All Platforms to Maven Central
244+
- name: Publish to Maven Central
198245
if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish == 'true'
199-
run: ./gradlew publishToMavenCentral -Pci.skip.native.build --no-configuration-cache
246+
run: ./gradlew publishToMavenCentral -Pci.skip.native.build ${{ steps.config.outputs.gradle_flag }} --no-configuration-cache
200247
env:
201248
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
202249
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
203250
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
204251
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
205252
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
206-
207-
- name: Pack Kopus Maven Artifacts
253+
254+
- name: Pack Maven Artifacts
208255
run: |
209256
cd ~/.m2/repository
210-
tar -czf /tmp/kopus-maven-artifacts.tar.gz eu/buney/
211-
212-
- name: Upload Kopus Maven Artifacts
257+
tar -czf /tmp/kopus-maven-artifacts${{ steps.config.outputs.artifact_suffix }}.tar.gz eu/buney/
258+
259+
- name: Upload Maven Artifacts
213260
uses: actions/upload-artifact@v4
214261
with:
215-
name: kopus-maven-artifacts
216-
path: /tmp/kopus-maven-artifacts.tar.gz
262+
name: kopus-maven-artifacts-${{ matrix.variant }}
263+
path: /tmp/kopus-maven-artifacts${{ steps.config.outputs.artifact_suffix }}.tar.gz
217264
retention-days: 7
218-

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ captures
1616
!*.xcodeproj/project.xcworkspace/
1717
!*.xcworkspace/contents.xcworkspacedata
1818
**/xcshareddata/WorkspaceSettings.xcsettings
19-
.env.development
19+
.env.development
20+
docs/

README.md

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22

33
Kopus is a lightweight Kotlin Multiplatform wrapper for the [Opus audio codec](https://opus-codec.org/). It provides Kotlin bindings for Opus encoding and decoding functionality across Android, JVM, and iOS platforms.
44

5-
![Kopus encoding/decoding demonstration](Screenshot_20250615_160803.png)
5+
![Kopus encoding/decoding demonstration](screenshots/encoding.png)
66

77
## Features
88

99
- **Thin wrapper** over the native Opus C API
1010
- **Kotlin Multiplatform** support for Android, JVM, and iOS
1111
- **Complete API access** to all Opus encoder and decoder settings
1212
- **Optimized native libraries** for various architectures
13+
- **Multi-channel audio** with multistream encoder/decoder for surround sound (5.1, 7.1)
14+
- **Spatial audio** with projection encoder/decoder for ambisonics
15+
- **Packet loss handling** with PLC and DRED support (kopus-full)
1316

1417
## Supported Platforms
1518

@@ -19,15 +22,17 @@ Kopus is a lightweight Kotlin Multiplatform wrapper for the [Opus audio codec](h
1922
- **Linux**: x86_64, arm64
2023
- **Windows**: x86_64
2124

22-
Note: The library has been primarily tested on macOS arm64 and Linux x86_64. While other platforms should work, they haven't been extensively tested.
23-
2425
## Installation
2526

2627
### Gradle
2728

2829
```kotlin
2930
dependencies {
30-
implementation("eu.buney.kopus:kopus:1.6.1")
31+
// Standard version
32+
implementation("eu.buney.kopus:kopus:1.6.1.1")
33+
34+
// Full version with DRED/OSCE/QEXT (larger binary size)
35+
implementation("eu.buney.kopus:kopus-full:1.6.1.1")
3136
}
3237
```
3338

@@ -76,6 +81,59 @@ val pcmOutput = decoder.decode(encodedData, frameSize)
7681
decoder.close()
7782
```
7883

84+
## Packet Loss Concealment (PLC)
85+
86+
Handle missing packets gracefully. When a packet is lost, pass `null` to the decoder to generate concealment audio. With `kopus-full`, DRED provides neural network-based recovery for even better quality.
87+
88+
```kotlin
89+
val decoder = OpusDecoder(sampleRate = 48000, channels = 1)
90+
91+
// Normal decode
92+
val audio = decoder.decode(packet, frameSize = 960)
93+
94+
// Packet lost - generate concealment
95+
val concealed = decoder.decode(null, frameSize = 960)
96+
```
97+
98+
![PLC Demo](screenshots/plc.png)
99+
100+
## Surround Sound (5.1, 7.1)
101+
102+
Encode and decode multi-channel audio using the multistream API. Supports standard channel layouts like 5.1 and 7.1 surround.
103+
104+
```kotlin
105+
// 5.1 surround: 6 channels
106+
val encoder = OpusMultistreamEncoder(
107+
sampleRate = 48000,
108+
channels = 6,
109+
streams = 4,
110+
coupledStreams = 2,
111+
mapping = byteArrayOf(0, 4, 1, 2, 3, 5)
112+
)
113+
val encoded = encoder.encode(surroundPcm)
114+
encoder.close()
115+
```
116+
117+
![5.1 Surround](screenshots/surround.png)
118+
119+
## Ambisonics (Spatial Audio)
120+
121+
Encode spatial audio using ambisonics with the projection API. The encoder automatically handles the channel mapping for first-order ambisonics (4 channels).
122+
123+
```kotlin
124+
// First-order ambisonics: 4 channels (W, Y, Z, X)
125+
val encoder = OpusProjectionEncoder(
126+
sampleRate = 48000,
127+
channels = 4,
128+
streams = 2,
129+
coupledStreams = 2
130+
)
131+
val encoded = encoder.encode(ambisonicsPcm)
132+
encoder.close()
133+
```
134+
135+
![Ambisonics](screenshots/ambisonics.png)
136+
79137
## Advanced Usage
80138

81139
### Direct Control with ctl/ctlQuery
@@ -163,6 +221,14 @@ Desktop builds are more complex as they require libraries for multiple operating
163221

164222
This produces native libraries that are packaged into the JVM artifacts, ensuring cross-platform compatibility.
165223

224+
### Building kopus-full
225+
226+
To build the full variant with DRED/OSCE/QEXT support:
227+
```bash
228+
./scripts/build_opus_apple.sh --full
229+
./gradlew build -Pkopus.full=true
230+
```
231+
166232
## License
167233

168234
Kopus is released under the MIT License. See [LICENSE](LICENSE) for details.

Screenshot_20250615_160803.png

-504 KB
Binary file not shown.

0 commit comments

Comments
 (0)