Skip to content

Commit

Permalink
Externalize opus binaries (discord-jda#659)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MinnDevelopment authored Jun 3, 2018
1 parent 599b414 commit 1af46b4
Show file tree
Hide file tree
Showing 17 changed files with 206 additions and 620 deletions.
8 changes: 3 additions & 5 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
root = true

[*]
charset = utf-8
end_of_line = crlf
insert_final_newline = true
indent_style = space
indent_size = 4

[gradlew]
end_of_line = lf

[*.{java, md, gradle, properties}]
charset = utf-8
indent_style = space
indent_size = 4
3 changes: 1 addition & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# Preserve gradlew's line ending
.gitattributes text eol=crlf

gradlew binary

.git* text eol=crlf
*.java text eol=crlf
*.gradle text eol=crlf
*.bat text eol=crlf
Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ Be sure to replace the **VERSION** key below with the one of the versions shown

```

**Maven without Audio**
```xml
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>VERSION</version>
<exclusions>
<exclusion>
<artifactId>opus-java</artifactId>
</exclusion>
</exclusions>
</dependency>
```

**Gradle**
```gradle
dependencies {
Expand All @@ -183,8 +197,26 @@ repositories {
}
```

**Gradle without Audio**
```gradle
dependencies {
compile ('net.dv8tion:JDA:VERSION') {
exclude module: 'opus-java'
}
}
```

The builds are distributed using JCenter through Bintray [JDA JCenter Bintray](https://bintray.com/dv8fromtheworld/maven/JDA/)

If you do not need any opus de-/encoding done by JDA (voice receive/send with PCM) you can exclude `opus-java` entirely.
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))

If you want to use a custom opus library you can provide the absolute path to `OpusLibrary.loadFrom(String)` before using
the audio api of JDA. This works without `opus-java-natives` as it only requires `opus-java-api`.
<br>_For this setup you should only exclude `opus-java-natives` as `opus-java-api` is a requirement for en-/decoding._

See [opus-java](https://github.com/discord-java/opus-java)

### Logging Framework - SLF4J
JDA is using [SLF4J](https://www.slf4j.org/) to log its messages.

Expand Down Expand Up @@ -303,10 +335,6 @@ All dependencies are managed automatically by Gradle.
* Version: **20160810**
* [Github](https://github.com/douglascrockford/JSON-java)
* [JCenter Repository](https://bintray.com/bintray/jcenter/org.json%3Ajson/view)
* JNA
* Version: **4.4.0**
* [Github](https://github.com/java-native-access/jna)
* [JCenter Repository](https://bintray.com/bintray/jcenter/net.java.dev.jna%3Ajna/view)
* Trove4j
* Version: **3.0.3**
* [BitBucket](https://bitbucket.org/trove4j/trove)
Expand All @@ -315,6 +343,10 @@ All dependencies are managed automatically by Gradle.
* Version: **1.7.25**
* [Website](https://www.slf4j.org/)
* [JCenter Repository](https://bintray.com/bintray/jcenter/org.slf4j%3Aslf4j-api/view)
* opus-java
* Version: **1.0.2**
* [GitHub](https://github.com/discord-java/opus-java)
* [JCenter Repository](https://bintray.com/minndevelopment/maven/opus-java)

## Related Projects

Expand Down
32 changes: 21 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//to build and upload everything: "gradlew bintrayUpload"

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

plugins {
id 'com.jfrog.bintray' version '1.7.3'
Expand Down Expand Up @@ -61,7 +62,7 @@ dependencies {
compile 'net.sf.trove4j:trove4j:3.0.3'

//Native Library Support
compile 'net.java.dev.jna:jna:4.4.0'
compile 'club.minnced:opus-java:1.0.2'

//Web Connection Support
compile 'com.neovisionaries:nv-websocket-client:2.2'
Expand Down Expand Up @@ -101,7 +102,22 @@ jar {
}

shadowJar {
classifier = "withDependencies"
classifier = 'withDependencies'
}

task noOpusJar(type: ShadowJar, dependsOn: shadowJar) {
classifier = shadowJar.classifier + '-no-opus'

configurations = [project.configurations.runtime]
from sourceSets.main.output
exclude 'natives/**' // ~2 MB
exclude 'com/sun/jna/**' // ~1 MB
exclude 'club/minnced/opus/util/*'
exclude 'tomp2p/opuswrapper/*'

manifest {
inheritFrom jar.manifest
}
}

task sourcesJar(type: Jar, dependsOn: classes) {
Expand Down Expand Up @@ -147,14 +163,9 @@ javadoc {
'net/dv8tion/jda/core/requests/Route.java',
'net/dv8tion/jda/core/requests/Requester.java',
'net/dv8tion/jda/core/requests/Response.java',
'net/dv8tion/jda/core/requests/ratelimit',
'net/dv8tion/jda/core/requests/restaction/CompletedFuture.java',
'net/dv8tion/jda/core/requests/restaction/RequestFuture.java')
'net/dv8tion/jda/core/requests/ratelimit')
exclude('net/dv8tion/jda/core/utils/cache/impl')

//opuswrapper
exclude('tomp2p/opuswrapper')

//voice crypto
exclude('com/iwebpp/crypto')
}
Expand Down Expand Up @@ -199,16 +210,15 @@ String getProjectProperty(String propertyName)
return property
}

task wrapper(type: Wrapper) {
gradleVersion = '4.2.1'
}
wrapper.gradleVersion = '4.6'

build {
dependsOn clean
dependsOn jar
dependsOn javadocJar
dependsOn sourcesJar
dependsOn shadowJar
dependsOn noOpusJar

jar.mustRunAfter clean
javadocJar.mustRunAfter jar
Expand Down
28 changes: 23 additions & 5 deletions src/main/java/net/dv8tion/jda/core/audio/AudioConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class AudioConnection
private volatile int silenceCounter = 0;
private boolean sentSilenceOnConnect = false;
private final byte[] silenceBytes = new byte[] {(byte)0xF8, (byte)0xFF, (byte)0xFE};
private static boolean printedError = false;

public AudioConnection(AudioWebSocket webSocket, VoiceChannel channel)
{
Expand Down Expand Up @@ -219,7 +220,7 @@ protected void updateUserSSRC(int ssrc, long userId)
ssrcMap.put(ssrc, userId);

//Only create a decoder if we are actively handling received audio.
if (receiveThread != null)
if (receiveThread != null && AudioNatives.ensureOpus())
opusDecoders.put(ssrc, new Decoder(ssrc));
}
}
Expand Down Expand Up @@ -262,9 +263,6 @@ private synchronized void setupSendSystem()
{
if (udpSocket != null && !udpSocket.isClosed() && sendHandler != null && sendSystem == null)
{
IntBuffer error = IntBuffer.allocate(4);
opusEncoder = Opus.INSTANCE.opus_encoder_create(OPUS_SAMPLE_RATE, OPUS_CHANNEL_COUNT, Opus.OPUS_APPLICATION_AUDIO, error);

IAudioSendFactory factory = ((JDAImpl) channel.getJDA()).getAudioSendFactory();
sendSystem = factory.createSendSystem(new PacketProvider());
sendSystem.setContextMap(contextMap);
Expand Down Expand Up @@ -360,7 +358,15 @@ private synchronized void setupReceiveThread()
}
if (decoder == null)
{
opusDecoders.put(ssrc, decoder = new Decoder(ssrc));
if (AudioNatives.ensureOpus())
{
opusDecoders.put(ssrc, decoder = new Decoder(ssrc));
}
else
{
LOG.error("Unable to decode audio due to missing opus binaries!");
break;
}
}
if (!decoder.isInOrder(decryptedPacket.getSequence()))
{
Expand Down Expand Up @@ -631,6 +637,18 @@ public DatagramPacket getNextPacket(boolean changeTalking)
{
if (!sendHandler.isOpus())
{
if (opusEncoder == null)
{
if (!AudioNatives.ensureOpus())
{
if (!printedError)
LOG.error("Unable to process PCM audio without opus binaries!");
printedError = true;
return null;
}
IntBuffer error = IntBuffer.allocate(4);
opusEncoder = Opus.INSTANCE.opus_encoder_create(OPUS_SAMPLE_RATE, OPUS_CHANNEL_COUNT, Opus.OPUS_APPLICATION_AUDIO, error);
}
rawAudio = encodeToOpus(rawAudio);
}
AudioPacket packet = new AudioPacket(seq, timestamp, webSocket.getSSRC(), rawAudio);
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/net/dv8tion/jda/core/audio/AudioNatives.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2015-2018 Austin Keener & Michael Ritter & Florian Spieß
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.dv8tion.jda.core.audio;

import club.minnced.opus.util.OpusLibrary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
* Controller used by JDA to ensure the native
* binaries for opus en-/decoding are available.
*
* @see <a href="https://github.com/discord-java/opus-java" target="_blank">opus-java source</a>
*/
public final class AudioNatives
{
private static final Logger LOG = LoggerFactory.getLogger(AudioNatives.class);
private static boolean initialized;
private static boolean audioSupported;

private AudioNatives() {}

/**
* Whether the opus library is loaded or not.
* <br>This is initialized by the first call to {@link #ensureOpus()}.
*
* @return True, opus library is loaded.
*/
public static boolean isAudioSupported()
{
return audioSupported;
}

/**
* Whether this class was already initialized or not.
*
* @return True, if this class was already initialized.
*
* @see #ensureOpus()
*/
public static boolean isInitialized()
{
return initialized;
}

/**
* Checks whether the opus binary was loaded, if not it will be initialized here.
* <br>This is used by JDA to check at runtime whether the opus library is available or not.
*
* @return True, if the library could be loaded.
*/
public static synchronized boolean ensureOpus()
{
if (initialized)
return audioSupported;
initialized = true;
try
{
if (OpusLibrary.isInitialized())
return audioSupported = true;
audioSupported = OpusLibrary.loadFromJar();
}
catch (Throwable e)
{
handleException(e);
}
finally
{
if (audioSupported)
LOG.info("Audio System successfully setup!");
else
LOG.info("Audio System encountered problems while loading, thus, is disabled.");
}
return audioSupported;
}

private static void handleException(Throwable e)
{
if (e instanceof UnsupportedOperationException)
{
LOG.error("Sorry, JDA's audio system doesn't support this system.\n{}", e.getMessage());
}
else if (e instanceof NoClassDefFoundError)
{
LOG.error("Missing opus dependency, unable to initialize audio!");
}
else if (e instanceof IOException)
{
LOG.error("There was an IO Exception when setting up the temp files for audio.", e);
}
else if (e instanceof UnsatisfiedLinkError)
{
LOG.error("JDA encountered a problem when attempting to load the Native libraries. Contact a DEV.", e);
}
else if (e instanceof Error)
{
throw (Error) e;
}
else
{
LOG.error("An unknown exception occurred while attempting to setup JDA's audio system!", e);
}
}
}
Loading

0 comments on commit 1af46b4

Please sign in to comment.