Skip to content

Commit 1af46b4

Browse files
authoredJun 3, 2018
Externalize opus binaries (discord-jda#659)
* Moved opus out into external repository * Added information on how to exclude opus * Add opus-java to dependencies list in README * Add support for using custom opus library paths
1 parent 599b414 commit 1af46b4

File tree

17 files changed

+206
-620
lines changed

17 files changed

+206
-620
lines changed
 

‎.editorconfig

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22
root = true
33

44
[*]
5+
charset = utf-8
56
end_of_line = crlf
67
insert_final_newline = true
8+
indent_style = space
9+
indent_size = 4
710

811
[gradlew]
912
end_of_line = lf
10-
11-
[*.{java, md, gradle, properties}]
12-
charset = utf-8
13-
indent_style = space
14-
indent_size = 4

‎.gitattributes

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Preserve gradlew's line ending
2-
.gitattributes text eol=crlf
3-
42
gradlew binary
53

4+
.git* text eol=crlf
65
*.java text eol=crlf
76
*.gradle text eol=crlf
87
*.bat text eol=crlf

‎README.md

+36-4
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,20 @@ Be sure to replace the **VERSION** key below with the one of the versions shown
172172

173173
```
174174

175+
**Maven without Audio**
176+
```xml
177+
<dependency>
178+
<groupId>net.dv8tion</groupId>
179+
<artifactId>JDA</artifactId>
180+
<version>VERSION</version>
181+
<exclusions>
182+
<exclusion>
183+
<artifactId>opus-java</artifactId>
184+
</exclusion>
185+
</exclusions>
186+
</dependency>
187+
```
188+
175189
**Gradle**
176190
```gradle
177191
dependencies {
@@ -183,8 +197,26 @@ repositories {
183197
}
184198
```
185199

200+
**Gradle without Audio**
201+
```gradle
202+
dependencies {
203+
compile ('net.dv8tion:JDA:VERSION') {
204+
exclude module: 'opus-java'
205+
}
206+
}
207+
```
208+
186209
The builds are distributed using JCenter through Bintray [JDA JCenter Bintray](https://bintray.com/dv8fromtheworld/maven/JDA/)
187210

211+
If you do not need any opus de-/encoding done by JDA (voice receive/send with PCM) you can exclude `opus-java` entirely.
212+
This can be done if you only send audio with an `AudioSendHandler` which only sends opus (`isOpus() = true`). (See [lavaplayer](https://github.com/sedmelluq/lavaplayer))
213+
214+
If you want to use a custom opus library you can provide the absolute path to `OpusLibrary.loadFrom(String)` before using
215+
the audio api of JDA. This works without `opus-java-natives` as it only requires `opus-java-api`.
216+
<br>_For this setup you should only exclude `opus-java-natives` as `opus-java-api` is a requirement for en-/decoding._
217+
218+
See [opus-java](https://github.com/discord-java/opus-java)
219+
188220
### Logging Framework - SLF4J
189221
JDA is using [SLF4J](https://www.slf4j.org/) to log its messages.
190222

@@ -303,10 +335,6 @@ All dependencies are managed automatically by Gradle.
303335
* Version: **20160810**
304336
* [Github](https://github.com/douglascrockford/JSON-java)
305337
* [JCenter Repository](https://bintray.com/bintray/jcenter/org.json%3Ajson/view)
306-
* JNA
307-
* Version: **4.4.0**
308-
* [Github](https://github.com/java-native-access/jna)
309-
* [JCenter Repository](https://bintray.com/bintray/jcenter/net.java.dev.jna%3Ajna/view)
310338
* Trove4j
311339
* Version: **3.0.3**
312340
* [BitBucket](https://bitbucket.org/trove4j/trove)
@@ -315,6 +343,10 @@ All dependencies are managed automatically by Gradle.
315343
* Version: **1.7.25**
316344
* [Website](https://www.slf4j.org/)
317345
* [JCenter Repository](https://bintray.com/bintray/jcenter/org.slf4j%3Aslf4j-api/view)
346+
* opus-java
347+
* Version: **1.0.2**
348+
* [GitHub](https://github.com/discord-java/opus-java)
349+
* [JCenter Repository](https://bintray.com/minndevelopment/maven/opus-java)
318350

319351
## Related Projects
320352

‎build.gradle

+21-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//to build and upload everything: "gradlew bintrayUpload"
1919

2020
import org.apache.tools.ant.filters.ReplaceTokens
21+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
2122

2223
plugins {
2324
id 'com.jfrog.bintray' version '1.7.3'
@@ -61,7 +62,7 @@ dependencies {
6162
compile 'net.sf.trove4j:trove4j:3.0.3'
6263

6364
//Native Library Support
64-
compile 'net.java.dev.jna:jna:4.4.0'
65+
compile 'club.minnced:opus-java:1.0.2'
6566

6667
//Web Connection Support
6768
compile 'com.neovisionaries:nv-websocket-client:2.2'
@@ -101,7 +102,22 @@ jar {
101102
}
102103

103104
shadowJar {
104-
classifier = "withDependencies"
105+
classifier = 'withDependencies'
106+
}
107+
108+
task noOpusJar(type: ShadowJar, dependsOn: shadowJar) {
109+
classifier = shadowJar.classifier + '-no-opus'
110+
111+
configurations = [project.configurations.runtime]
112+
from sourceSets.main.output
113+
exclude 'natives/**' // ~2 MB
114+
exclude 'com/sun/jna/**' // ~1 MB
115+
exclude 'club/minnced/opus/util/*'
116+
exclude 'tomp2p/opuswrapper/*'
117+
118+
manifest {
119+
inheritFrom jar.manifest
120+
}
105121
}
106122

107123
task sourcesJar(type: Jar, dependsOn: classes) {
@@ -147,14 +163,9 @@ javadoc {
147163
'net/dv8tion/jda/core/requests/Route.java',
148164
'net/dv8tion/jda/core/requests/Requester.java',
149165
'net/dv8tion/jda/core/requests/Response.java',
150-
'net/dv8tion/jda/core/requests/ratelimit',
151-
'net/dv8tion/jda/core/requests/restaction/CompletedFuture.java',
152-
'net/dv8tion/jda/core/requests/restaction/RequestFuture.java')
166+
'net/dv8tion/jda/core/requests/ratelimit')
153167
exclude('net/dv8tion/jda/core/utils/cache/impl')
154168

155-
//opuswrapper
156-
exclude('tomp2p/opuswrapper')
157-
158169
//voice crypto
159170
exclude('com/iwebpp/crypto')
160171
}
@@ -199,16 +210,15 @@ String getProjectProperty(String propertyName)
199210
return property
200211
}
201212

202-
task wrapper(type: Wrapper) {
203-
gradleVersion = '4.2.1'
204-
}
213+
wrapper.gradleVersion = '4.6'
205214

206215
build {
207216
dependsOn clean
208217
dependsOn jar
209218
dependsOn javadocJar
210219
dependsOn sourcesJar
211220
dependsOn shadowJar
221+
dependsOn noOpusJar
212222

213223
jar.mustRunAfter clean
214224
javadocJar.mustRunAfter jar

‎src/main/java/net/dv8tion/jda/core/audio/AudioConnection.java

+23-5
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public class AudioConnection
8383
private volatile int silenceCounter = 0;
8484
private boolean sentSilenceOnConnect = false;
8585
private final byte[] silenceBytes = new byte[] {(byte)0xF8, (byte)0xFF, (byte)0xFE};
86+
private static boolean printedError = false;
8687

8788
public AudioConnection(AudioWebSocket webSocket, VoiceChannel channel)
8889
{
@@ -219,7 +220,7 @@ protected void updateUserSSRC(int ssrc, long userId)
219220
ssrcMap.put(ssrc, userId);
220221

221222
//Only create a decoder if we are actively handling received audio.
222-
if (receiveThread != null)
223+
if (receiveThread != null && AudioNatives.ensureOpus())
223224
opusDecoders.put(ssrc, new Decoder(ssrc));
224225
}
225226
}
@@ -262,9 +263,6 @@ private synchronized void setupSendSystem()
262263
{
263264
if (udpSocket != null && !udpSocket.isClosed() && sendHandler != null && sendSystem == null)
264265
{
265-
IntBuffer error = IntBuffer.allocate(4);
266-
opusEncoder = Opus.INSTANCE.opus_encoder_create(OPUS_SAMPLE_RATE, OPUS_CHANNEL_COUNT, Opus.OPUS_APPLICATION_AUDIO, error);
267-
268266
IAudioSendFactory factory = ((JDAImpl) channel.getJDA()).getAudioSendFactory();
269267
sendSystem = factory.createSendSystem(new PacketProvider());
270268
sendSystem.setContextMap(contextMap);
@@ -360,7 +358,15 @@ private synchronized void setupReceiveThread()
360358
}
361359
if (decoder == null)
362360
{
363-
opusDecoders.put(ssrc, decoder = new Decoder(ssrc));
361+
if (AudioNatives.ensureOpus())
362+
{
363+
opusDecoders.put(ssrc, decoder = new Decoder(ssrc));
364+
}
365+
else
366+
{
367+
LOG.error("Unable to decode audio due to missing opus binaries!");
368+
break;
369+
}
364370
}
365371
if (!decoder.isInOrder(decryptedPacket.getSequence()))
366372
{
@@ -631,6 +637,18 @@ public DatagramPacket getNextPacket(boolean changeTalking)
631637
{
632638
if (!sendHandler.isOpus())
633639
{
640+
if (opusEncoder == null)
641+
{
642+
if (!AudioNatives.ensureOpus())
643+
{
644+
if (!printedError)
645+
LOG.error("Unable to process PCM audio without opus binaries!");
646+
printedError = true;
647+
return null;
648+
}
649+
IntBuffer error = IntBuffer.allocate(4);
650+
opusEncoder = Opus.INSTANCE.opus_encoder_create(OPUS_SAMPLE_RATE, OPUS_CHANNEL_COUNT, Opus.OPUS_APPLICATION_AUDIO, error);
651+
}
634652
rawAudio = encodeToOpus(rawAudio);
635653
}
636654
AudioPacket packet = new AudioPacket(seq, timestamp, webSocket.getSSRC(), rawAudio);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.dv8tion.jda.core.audio;
18+
19+
import club.minnced.opus.util.OpusLibrary;
20+
import org.slf4j.Logger;
21+
import org.slf4j.LoggerFactory;
22+
23+
import java.io.IOException;
24+
25+
/**
26+
* Controller used by JDA to ensure the native
27+
* binaries for opus en-/decoding are available.
28+
*
29+
* @see <a href="https://github.com/discord-java/opus-java" target="_blank">opus-java source</a>
30+
*/
31+
public final class AudioNatives
32+
{
33+
private static final Logger LOG = LoggerFactory.getLogger(AudioNatives.class);
34+
private static boolean initialized;
35+
private static boolean audioSupported;
36+
37+
private AudioNatives() {}
38+
39+
/**
40+
* Whether the opus library is loaded or not.
41+
* <br>This is initialized by the first call to {@link #ensureOpus()}.
42+
*
43+
* @return True, opus library is loaded.
44+
*/
45+
public static boolean isAudioSupported()
46+
{
47+
return audioSupported;
48+
}
49+
50+
/**
51+
* Whether this class was already initialized or not.
52+
*
53+
* @return True, if this class was already initialized.
54+
*
55+
* @see #ensureOpus()
56+
*/
57+
public static boolean isInitialized()
58+
{
59+
return initialized;
60+
}
61+
62+
/**
63+
* Checks whether the opus binary was loaded, if not it will be initialized here.
64+
* <br>This is used by JDA to check at runtime whether the opus library is available or not.
65+
*
66+
* @return True, if the library could be loaded.
67+
*/
68+
public static synchronized boolean ensureOpus()
69+
{
70+
if (initialized)
71+
return audioSupported;
72+
initialized = true;
73+
try
74+
{
75+
if (OpusLibrary.isInitialized())
76+
return audioSupported = true;
77+
audioSupported = OpusLibrary.loadFromJar();
78+
}
79+
catch (Throwable e)
80+
{
81+
handleException(e);
82+
}
83+
finally
84+
{
85+
if (audioSupported)
86+
LOG.info("Audio System successfully setup!");
87+
else
88+
LOG.info("Audio System encountered problems while loading, thus, is disabled.");
89+
}
90+
return audioSupported;
91+
}
92+
93+
private static void handleException(Throwable e)
94+
{
95+
if (e instanceof UnsupportedOperationException)
96+
{
97+
LOG.error("Sorry, JDA's audio system doesn't support this system.\n{}", e.getMessage());
98+
}
99+
else if (e instanceof NoClassDefFoundError)
100+
{
101+
LOG.error("Missing opus dependency, unable to initialize audio!");
102+
}
103+
else if (e instanceof IOException)
104+
{
105+
LOG.error("There was an IO Exception when setting up the temp files for audio.", e);
106+
}
107+
else if (e instanceof UnsatisfiedLinkError)
108+
{
109+
LOG.error("JDA encountered a problem when attempting to load the Native libraries. Contact a DEV.", e);
110+
}
111+
else if (e instanceof Error)
112+
{
113+
throw (Error) e;
114+
}
115+
else
116+
{
117+
LOG.error("An unknown exception occurred while attempting to setup JDA's audio system!", e);
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)
Please sign in to comment.