diff --git a/src/examples/java/AudioEchoExample.java b/src/examples/java/AudioEchoExample.java
index 880ae13e2a..4f4b8401c6 100644
--- a/src/examples/java/AudioEchoExample.java
+++ b/src/examples/java/AudioEchoExample.java
@@ -19,7 +19,6 @@
import net.dv8tion.jda.api.audio.AudioReceiveHandler;
import net.dv8tion.jda.api.audio.AudioSendHandler;
import net.dv8tion.jda.api.audio.CombinedAudio;
-import net.dv8tion.jda.api.audio.UserAudio;
import net.dv8tion.jda.api.entities.*;
import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
@@ -202,13 +201,6 @@ public boolean canReceiveCombined()
return queue.size() < 10;
}
- @Override // give audio separately for each user that is speaking
- public boolean canReceiveUser()
- {
- // this is not useful if we want to echo the audio of the voice channel, thus disabled for this purpose
- return false;
- }
-
@Override
public void handleCombinedAudio(CombinedAudio combinedAudio)
{
@@ -219,9 +211,19 @@ public void handleCombinedAudio(CombinedAudio combinedAudio)
byte[] data = combinedAudio.getAudioData(1.0f); // volume at 100% = 1.0 (50% = 0.5 / 55% = 0.55)
queue.add(data);
}
+/*
+ Disable per-user audio since we want to echo the entire channel and not specific users.
+
+ @Override // give audio separately for each user that is speaking
+ public boolean canReceiveUser()
+ {
+ // this is not useful if we want to echo the audio of the voice channel, thus disabled for this purpose
+ return false;
+ }
@Override
public void handleUserAudio(UserAudio userAudio) {} // per-user is not helpful in an echo system
+*/
/* Send Handling */
diff --git a/src/main/java/net/dv8tion/jda/api/audio/AudioReceiveHandler.java b/src/main/java/net/dv8tion/jda/api/audio/AudioReceiveHandler.java
index 80cf00fbab..460e807223 100644
--- a/src/main/java/net/dv8tion/jda/api/audio/AudioReceiveHandler.java
+++ b/src/main/java/net/dv8tion/jda/api/audio/AudioReceiveHandler.java
@@ -37,14 +37,51 @@ public interface AudioReceiveHandler
*
* @return If true, JDA enables subsystems to combine all user audio into a single provided data packet.
*/
- boolean canReceiveCombined();
+ default boolean canReceiveCombined()
+ {
+ return false;
+ }
/**
* If this method returns true, then JDA will provide audio data to the {@link #handleUserAudio(UserAudio)} method.
*
* @return If true, JDA enables subsystems to provide user specific audio data.
*/
- boolean canReceiveUser();
+ default boolean canReceiveUser()
+ {
+ return false;
+ }
+
+ /**
+ * If this method returns true, then JDA will provide raw OPUS encoded packets to {@link #handleEncodedAudio(OpusPacket)}.
+ *
This can be used in combination with the other receive methods but will not be combined audio of multiple users.
+ *
+ *
Each user sends their own stream of OPUS encoded audio and each packet is assigned with a user id and SSRC. + * The decoder will be provided by JDA but need not be used. + * + * @return True, if {@link #handleEncodedAudio(OpusPacket)} should receive opus packets. + * + * @since 4.0.0 + */ + default boolean canReceiveEncoded() + { + return false; + } + + /** + * If {@link #canReceiveEncoded()} returns true, JDA will provide raw {@link net.dv8tion.jda.api.audio.OpusPacket OpusPackets} + * to this method every 20 milliseconds. These packets are for specific users rather than a combined packet + * of all users like {@link #handleCombinedAudio(CombinedAudio)}. + * + *
This is useful for systems that want to either do lazy decoding of audio through {@link net.dv8tion.jda.api.audio.OpusPacket#getAudioData(double)}
+ * or for systems that can decode and transform the audio data manually without JDA involvement.
+ *
+ * @param packet
+ * The {@link net.dv8tion.jda.api.audio.OpusPacket}
+ *
+ * @since 4.0.0
+ */
+ default void handleEncodedAudio(@Nonnull OpusPacket packet) {}
/**
* If {@link #canReceiveCombined()} returns true, JDA will provide a {@link net.dv8tion.jda.api.audio.CombinedAudio CombinedAudio}
@@ -65,7 +102,7 @@ public interface AudioReceiveHandler
* @param combinedAudio
* The combined audio data.
*/
- void handleCombinedAudio(@Nonnull CombinedAudio combinedAudio);
+ default void handleCombinedAudio(@Nonnull CombinedAudio combinedAudio) {}
/**
* If {@link #canReceiveUser()} returns true, JDA will provide a {@link net.dv8tion.jda.api.audio.UserAudio UserAudio}
@@ -88,7 +125,7 @@ public interface AudioReceiveHandler
* @param userAudio
* The user audio data
*/
- void handleUserAudio(@Nonnull UserAudio userAudio);
+ default void handleUserAudio(@Nonnull UserAudio userAudio) {}
/**
* This method is a filter predicate used by JDA to determine whether or not to include a
diff --git a/src/main/java/net/dv8tion/jda/api/audio/CombinedAudio.java b/src/main/java/net/dv8tion/jda/api/audio/CombinedAudio.java
index bc1df58806..b5894c779a 100644
--- a/src/main/java/net/dv8tion/jda/api/audio/CombinedAudio.java
+++ b/src/main/java/net/dv8tion/jda/api/audio/CombinedAudio.java
@@ -67,21 +67,6 @@ public List Note that packet sequence is important for decoding. If a packet is out of sequence the decode
+ * step will fail.
+ *
+ * @return The sequence number of this packet
+ *
+ * @see RTP Header
+ */
+ public char getSequence()
+ {
+ return rawPacket.getSequence();
+ }
+
+ /**
+ * The timestamp for this packet. As specified by the RTP header.
+ *
+ * @return The timestamp
+ *
+ * @see RTP Header
+ */
+ public int getTimestamp()
+ {
+ return rawPacket.getTimestamp();
+ }
+
+ /**
+ * The synchronization source identifier (SSRC) for the user that sent this audio packet.
+ *
+ * @return The SSRC
+ *
+ * @see RTP Header
+ */
+ public int getSSRC()
+ {
+ return rawPacket.getSSRC();
+ }
+
+ /**
+ * The ID of the responsible {@link net.dv8tion.jda.api.entities.User}.
+ *
+ * @return The user id
+ */
+ public long getUserId()
+ {
+ return userId;
+ }
+
+ /**
+ * Whether {@link #decode()} is possible.
+ *
+ * @return True, if decode is possible.
+ */
+ public boolean canDecode()
+ {
+ return decoder != null && decoder.isInOrder(getSequence());
+ }
+
+ /**
+ * The raw opus audio, copied to a new array.
+ *
+ * @return The raw opus audio
+ */
+ @Nonnull
+ public byte[] getOpusAudio()
+ {
+ //prevent write access to backing array
+ return Arrays.copyOf(opusAudio, opusAudio.length);
+ }
+
+ /**
+ * Attempts to decode the opus packet.
+ *
A char represents an unsigned short value in this case.
+ *
+ *
This method is idempotent and will provide the same result on multiple calls
+ * without decoding again.
+ *
+ * For most use-cases {@link #getAudioData(double)} should be used instead.
+ *
+ * @throws java.lang.IllegalStateException
+ * If {@link #canDecode()} is false
+ *
+ * @return The decoded audio or {@code null} if decoding failed for some reason.
+ *
+ * @see #canDecode()
+ * @see #getAudioData(double)
+ */
+ @Nullable
+ public synchronized short[] decode()
+ {
+ if (triedDecode)
+ return decoded;
+ if (decoder == null)
+ throw new IllegalStateException("No decoder available");
+ if (!decoder.isInOrder(getSequence()))
+ throw new IllegalStateException("Packet is not in order");
+ triedDecode = true;
+ return decoded = decoder.decodeFromOpus(rawPacket); // null if failed to decode
+ }
+
+ /**
+ * Decodes and adjusts the opus audio for the specified volume.
+ *
The provided volume should be a double precision floating point in the interval from 0 to 1.
+ * In this case 0.5 would represent 50% volume for instance.
+ *
+ * @param volume
+ * The volume
+ *
+ * @throws java.lang.IllegalArgumentException
+ * If {@link #decode()} returns null
+ *
+ * @return The stereo PCM audio data as specified by {@link net.dv8tion.jda.api.audio.AudioReceiveHandler#OUTPUT_FORMAT}.
+ */
+ @Nonnull
+ @SuppressWarnings("ConstantConditions") // the null case is handled with an exception
+ public byte[] getAudioData(double volume)
+ {
+ return getAudioData(decode(), volume); // throws IllegalArgument if decode failed
+ }
+
+ /**
+ * Decodes and adjusts the opus audio for the specified volume.
+ *
The provided volume should be a double precision floating point in the interval from 0 to 1.
+ * In this case 0.5 would represent 50% volume for instance.
+ *
+ * @param decoded
+ * The decoded audio data
+ * @param volume
+ * The volume
+ *
+ * @throws java.lang.IllegalArgumentException
+ * If {@code decoded} is null
+ *
+ * @return The stereo PCM audio data as specified by {@link net.dv8tion.jda.api.audio.AudioReceiveHandler#OUTPUT_FORMAT}.
+ */
+ @Nonnull
+ @SuppressWarnings("ConstantConditions") // the null case is handled with an exception
+ public static byte[] getAudioData(@Nonnull short[] decoded, double volume)
+ {
+ if (decoded == null)
+ throw new IllegalArgumentException("Cannot get audio data from null");
+ int byteIndex = 0;
+ byte[] audio = new byte[decoded.length * 2];
+ for (short s : decoded)
+ {
+ if (volume != 1.0)
+ s = (short) (s * volume);
+
+ byte leftByte = (byte) ((s >>> 8) & 0xFF);
+ byte rightByte = (byte) (s & 0xFF);
+ audio[byteIndex] = leftByte;
+ audio[byteIndex + 1] = rightByte;
+ byteIndex += 2;
+ }
+ return audio;
+ }
+
+ @Override
+ public int compareTo(@Nonnull OpusPacket o)
+ {
+ return getSequence() - o.getSequence();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(getSequence(), getTimestamp(), getOpusAudio());
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof OpusPacket))
+ return false;
+ OpusPacket other = (OpusPacket) obj;
+ return getSequence() == other.getSequence()
+ && getTimestamp() == other.getTimestamp()
+ && getSsrc() == other.getSsrc();
+ }
+}
diff --git a/src/main/java/net/dv8tion/jda/api/audio/UserAudio.java b/src/main/java/net/dv8tion/jda/api/audio/UserAudio.java
index 8ad89d8957..9148b2aa3f 100644
--- a/src/main/java/net/dv8tion/jda/api/audio/UserAudio.java
+++ b/src/main/java/net/dv8tion/jda/api/audio/UserAudio.java
@@ -60,21 +60,6 @@ public User getUser()
@Nonnull
public byte[] getAudioData(double volume)
{
- short s;
- int byteIndex = 0;
- byte[] audio = new byte[audioData.length * 2];
- for (int i = 0; i < audioData.length; i++)
- {
- s = audioData[i];
- if (volume != 1.0)
- s = (short) (s * volume);
-
- byte leftByte = (byte) ((0x000000FF) & (s >> 8));
- byte rightByte = (byte) (0x000000FF & s);
- audio[byteIndex] = leftByte;
- audio[byteIndex + 1] = rightByte;
- byteIndex += 2;
- }
- return audio;
+ return OpusPacket.getAudioData(audioData, volume);
}
}
diff --git a/src/main/java/net/dv8tion/jda/api/audio/factory/DefaultSendSystem.java b/src/main/java/net/dv8tion/jda/api/audio/factory/DefaultSendSystem.java
index faf8b49245..0a80bfe770 100644
--- a/src/main/java/net/dv8tion/jda/api/audio/factory/DefaultSendSystem.java
+++ b/src/main/java/net/dv8tion/jda/api/audio/factory/DefaultSendSystem.java
@@ -27,7 +27,7 @@
import java.net.SocketException;
import java.util.concurrent.ConcurrentMap;
-import static net.dv8tion.jda.internal.audio.AudioConnection.OPUS_FRAME_TIME_AMOUNT;
+import static net.dv8tion.jda.api.audio.OpusPacket.OPUS_FRAME_TIME_AMOUNT;
/**
* The default implementation of the {@link net.dv8tion.jda.api.audio.factory.IAudioSendSystem IAudioSendSystem}.
diff --git a/src/main/java/net/dv8tion/jda/internal/audio/AudioConnection.java b/src/main/java/net/dv8tion/jda/internal/audio/AudioConnection.java
index 3ee441500f..322a19c1f2 100644
--- a/src/main/java/net/dv8tion/jda/internal/audio/AudioConnection.java
+++ b/src/main/java/net/dv8tion/jda/internal/audio/AudioConnection.java
@@ -35,6 +35,7 @@
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.managers.AudioManagerImpl;
+import net.dv8tion.jda.internal.utils.IOUtil;
import net.dv8tion.jda.internal.utils.JDALogger;
import net.dv8tion.jda.internal.utils.cache.UpstreamReference;
import org.slf4j.Logger;
@@ -53,11 +54,7 @@
public class AudioConnection
{
public static final Logger LOG = JDALogger.getLog(AudioConnection.class);
- public static final int OPUS_SAMPLE_RATE = 48000; //(Hz) We want to use the highest of qualities! All the bandwidth!
- public static final int OPUS_FRAME_SIZE = 960; //An opus frame size of 960 at 48000hz represents 20 milliseconds of audio.
- public static final int OPUS_FRAME_TIME_AMOUNT = 20;//This is 20 milliseconds. We are only dealing with 20ms opus packets.
- public static final int OPUS_CHANNEL_COUNT = 2; //We want to use stereo. If the audio given is mono, the encoder promotes it
- // to Left and Right mono (stereo that is the same on both sides)
+
public static final long MAX_UINT_32 = 4294967295L;
private static final int NOT_SPEAKING = 0;
@@ -367,7 +364,9 @@ private synchronized void setupReceiveThread()
{
udpSocket.receive(receivedPacket);
- if (receiveHandler != null && (receiveHandler.canReceiveUser() || receiveHandler.canReceiveCombined()) && webSocket.getSecretKey() != null)
+ boolean shouldDecode = receiveHandler != null && (receiveHandler.canReceiveUser() || receiveHandler.canReceiveCombined());
+ boolean canReceive = receiveHandler != null && (receiveHandler.canReceiveUser() || receiveHandler.canReceiveCombined() || receiveHandler.canReceiveEncoded());
+ if (canReceive && webSocket.getSecretKey() != null)
{
if (!couldReceive)
{
@@ -398,17 +397,17 @@ private synchronized void setupReceiveThread()
{
opusDecoders.put(ssrc, decoder = new Decoder(ssrc));
}
- else
+ else if (!receiveHandler.canReceiveEncoded())
{
LOG.error("Unable to decode audio due to missing opus binaries!");
break;
}
}
- if (!decoder.isInOrder(decryptedPacket.getSequence()))
- {
- LOG.trace("Got out-of-order audio packet. Ignoring.");
+ OpusPacket opusPacket = new OpusPacket(decryptedPacket, userId, decoder);
+ if (receiveHandler.canReceiveEncoded())
+ receiveHandler.handleEncodedAudio(opusPacket);
+ if (!shouldDecode || !opusPacket.canDecode())
continue;
- }
User user = getJDA().getUserById(userId);
if (user == null)
@@ -416,8 +415,7 @@ private synchronized void setupReceiveThread()
LOG.warn("Received audio data with a known SSRC, but the userId associate with the SSRC is unknown to JDA!");
continue;
}
- short[] decodedAudio = decoder.decodeFromOpus(decryptedPacket);
-
+ short[] decodedAudio = opusPacket.decode();
//If decodedAudio is null, then the Opus decode failed, so throw away the packet.
if (decodedAudio == null)
{
@@ -584,7 +582,7 @@ private ByteBuffer encodeToOpus(ByteBuffer rawAudio)
}
((Buffer) nonEncodedBuffer).flip();
- int result = Opus.INSTANCE.opus_encode(opusEncoder, nonEncodedBuffer, OPUS_FRAME_SIZE, encoded, encoded.capacity());
+ int result = Opus.INSTANCE.opus_encode(opusEncoder, nonEncodedBuffer, OpusPacket.OPUS_FRAME_SIZE, encoded, encoded.capacity());
if (result <= 0)
{
LOG.error("Received error code from opus_encode(...): {}", result);
@@ -736,7 +734,7 @@ else if (speaking && changeTalking)
}
if (nextPacket != null)
- timestamp += OPUS_FRAME_SIZE;
+ timestamp += OpusPacket.OPUS_FRAME_SIZE;
return nextPacket;
}
@@ -753,7 +751,7 @@ private ByteBuffer encodeAudio(ByteBuffer rawAudio)
return null;
}
IntBuffer error = IntBuffer.allocate(1);
- opusEncoder = Opus.INSTANCE.opus_encoder_create(OPUS_SAMPLE_RATE, OPUS_CHANNEL_COUNT, Opus.OPUS_APPLICATION_AUDIO, error);
+ opusEncoder = Opus.INSTANCE.opus_encoder_create(OpusPacket.OPUS_SAMPLE_RATE, OpusPacket.OPUS_CHANNEL_COUNT, Opus.OPUS_APPLICATION_AUDIO, error);
if (error.get() != Opus.OPUS_OK && opusEncoder == null)
{
LOG.error("Received error status from opus_encoder_create(...): {}", error.get());
@@ -809,10 +807,7 @@ private void ensureEncryptionBuffer(ByteBuffer data)
private void loadNextNonce(long nonce)
{
- nonceBuffer[0] = (byte) ((nonce >>> 24) & 0xFF);
- nonceBuffer[1] = (byte) ((nonce >>> 16) & 0xFF);
- nonceBuffer[2] = (byte) ((nonce >>> 8) & 0xFF);
- nonceBuffer[3] = (byte) ( nonce & 0xFF);
+ IOUtil.setIntBigEndian(nonceBuffer, 0, (int) nonce);
}
@Override
diff --git a/src/main/java/net/dv8tion/jda/internal/audio/AudioPacket.java b/src/main/java/net/dv8tion/jda/internal/audio/AudioPacket.java
index 83d136a34f..eb7df12893 100644
--- a/src/main/java/net/dv8tion/jda/internal/audio/AudioPacket.java
+++ b/src/main/java/net/dv8tion/jda/internal/audio/AudioPacket.java
@@ -17,6 +17,7 @@
package net.dv8tion.jda.internal.audio;
import com.iwebpp.crypto.TweetNaclFast;
+import net.dv8tion.jda.internal.utils.IOUtil;
import java.net.DatagramPacket;
import java.nio.Buffer;
@@ -85,7 +86,7 @@ public AudioPacket(byte[] rawPacket)
final byte cc = (byte) (profile & 0x0f); // CSRC count - we ignore this for now
final int csrcLength = cc * 4; // defines count of 4-byte words
// it seems as if extensions only exist without a csrc list being present
- final short extension = hasExtension ? getShort(data, RTP_HEADER_BYTE_LENGTH + csrcLength) : 0;
+ final short extension = hasExtension ? IOUtil.getShortBigEndian(data, RTP_HEADER_BYTE_LENGTH + csrcLength) : 0;
int offset = RTP_HEADER_BYTE_LENGTH + csrcLength;
if (hasExtension && extension == RTP_DISCORD_EXTENSION)
@@ -109,7 +110,7 @@ public AudioPacket(ByteBuffer buffer, char seq, int timestamp, int ssrc, ByteBuf
private int getPayloadOffset(byte[] data, int csrcLength)
{
// headerLength defines number of 4-byte words in the extension
- final short headerLength = getShort(data, RTP_HEADER_BYTE_LENGTH + 2 + csrcLength);
+ final short headerLength = IOUtil.getShortBigEndian(data, RTP_HEADER_BYTE_LENGTH + 2 + csrcLength);
int i = RTP_HEADER_BYTE_LENGTH // RTP header = 12 bytes
+ 4 // header which defines a profile and length each 2-bytes = 4 bytes
+ csrcLength // length of CSRC list (this seems to be always 0 when an extension exists)
@@ -121,11 +122,6 @@ private int getPayloadOffset(byte[] data, int csrcLength)
return i;
}
- private short getShort(byte[] arr, int offset)
- {
- return (short) ((arr[offset] & 0xff) << 8 | arr[offset + 1] & 0xff);
- }
-
@SuppressWarnings("unused")
public byte[] getHeader()
{
@@ -166,7 +162,7 @@ public int getTimestamp()
return timestamp;
}
- public ByteBuffer asEncryptedPacket(TweetNaclFast.SecretBox boxer, ByteBuffer buffer, byte[] nonce, int nlen)
+ protected ByteBuffer asEncryptedPacket(TweetNaclFast.SecretBox boxer, ByteBuffer buffer, byte[] nonce, int nlen)
{
//Xsalsa20's Nonce is 24 bytes long, however RTP (and consequently Discord)'s nonce is a different length
// so we need to create a 24 byte array, and copy the nonce into it.
@@ -193,7 +189,7 @@ public ByteBuffer asEncryptedPacket(TweetNaclFast.SecretBox boxer, ByteBuffer bu
return buffer;
}
- public static AudioPacket decryptAudioPacket(AudioEncryption encryption, DatagramPacket packet, byte[] secretKey)
+ protected static AudioPacket decryptAudioPacket(AudioEncryption encryption, DatagramPacket packet, byte[] secretKey)
{
TweetNaclFast.SecretBox boxer = new TweetNaclFast.SecretBox(secretKey);
AudioPacket encryptedPacket = new AudioPacket(packet);
diff --git a/src/main/java/net/dv8tion/jda/internal/audio/Decoder.java b/src/main/java/net/dv8tion/jda/internal/audio/Decoder.java
index 106bed2090..cba6851401 100644
--- a/src/main/java/net/dv8tion/jda/internal/audio/Decoder.java
+++ b/src/main/java/net/dv8tion/jda/internal/audio/Decoder.java
@@ -17,6 +17,7 @@
package net.dv8tion.jda.internal.audio;
import com.sun.jna.ptr.PointerByReference;
+import net.dv8tion.jda.api.audio.OpusPacket;
import tomp2p.opuswrapper.Opus;
import java.nio.ByteBuffer;
@@ -40,28 +41,28 @@ protected Decoder(int ssrc)
this.lastTimestamp = -1;
IntBuffer error = IntBuffer.allocate(1);
- opusDecoder = Opus.INSTANCE.opus_decoder_create(AudioConnection.OPUS_SAMPLE_RATE, AudioConnection.OPUS_CHANNEL_COUNT, error);
+ opusDecoder = Opus.INSTANCE.opus_decoder_create(OpusPacket.OPUS_SAMPLE_RATE, OpusPacket.OPUS_CHANNEL_COUNT, error);
if (error.get() != Opus.OPUS_OK && opusDecoder == null)
throw new IllegalStateException("Received error code from opus_decoder_create(...): " + error.get());
}
- protected boolean isInOrder(char newSeq)
+ public boolean isInOrder(char newSeq)
{
return lastSeq == (char) -1 || newSeq > lastSeq || lastSeq - newSeq > 10;
}
- protected boolean wasPacketLost(char newSeq)
+ public boolean wasPacketLost(char newSeq)
{
return newSeq > lastSeq + 1;
}
- protected short[] decodeFromOpus(AudioPacket decryptedPacket)
+ public short[] decodeFromOpus(AudioPacket decryptedPacket)
{
int result;
ShortBuffer decoded = ShortBuffer.allocate(4096);
if (decryptedPacket == null) //Flag for packet-loss
{
- result = Opus.INSTANCE.opus_decode(opusDecoder, null, 0, decoded, AudioConnection.OPUS_FRAME_SIZE, 0);
+ result = Opus.INSTANCE.opus_decode(opusDecoder, null, 0, decoded, OpusPacket.OPUS_FRAME_SIZE, 0);
lastSeq = (char) -1;
lastTimestamp = -1;
}
@@ -76,7 +77,7 @@ protected short[] decodeFromOpus(AudioPacket decryptedPacket)
byte[] buf = new byte[length];
byte[] data = encodedAudio.array();
System.arraycopy(data, offset, buf, 0, length);
- result = Opus.INSTANCE.opus_decode(opusDecoder, buf, buf.length, decoded, AudioConnection.OPUS_FRAME_SIZE, 0);
+ result = Opus.INSTANCE.opus_decode(opusDecoder, buf, buf.length, decoded, OpusPacket.OPUS_FRAME_SIZE, 0);
}
//If we get a result that is less than 0, then there was an error. Return null as a signifier.
diff --git a/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java b/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java
index e1b53cbe4d..a6fe39fdfc 100644
--- a/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java
+++ b/src/main/java/net/dv8tion/jda/internal/requests/WebSocketClient.java
@@ -43,6 +43,7 @@
import net.dv8tion.jda.internal.handle.*;
import net.dv8tion.jda.internal.managers.AudioManagerImpl;
import net.dv8tion.jda.internal.managers.PresenceImpl;
+import net.dv8tion.jda.internal.utils.IOUtil;
import net.dv8tion.jda.internal.utils.JDALogger;
import net.dv8tion.jda.internal.utils.UnlockHook;
import net.dv8tion.jda.internal.utils.cache.AbstractCacheView;
diff --git a/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java b/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java
index dee4bbbaf5..16b5d92578 100644
--- a/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java
+++ b/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java
@@ -147,4 +147,26 @@ public static RequestBody createRequestBody(final MediaType contentType, final I
{
return new BufferedRequestBody(Okio.source(stream), contentType);
}
+
+ public static short getShortBigEndian(byte[] arr, int offset)
+ {
+ return (short) ((arr[offset ] & 0xff) << 8
+ | arr[offset + 1] & 0xff);
+ }
+
+ public static int getIntBigEndian(byte[] arr, int offset)
+ {
+ return arr[offset + 3] & 0xFF
+ | (arr[offset + 2] & 0xFF) << 8
+ | (arr[offset + 1] & 0xFF) << 16
+ | (arr[offset ] & 0xFF) << 24;
+ }
+
+ public static void setIntBigEndian(byte[] arr, int offset, int it)
+ {
+ arr[offset ] = (byte) ((it >>> 24) & 0xFF);
+ arr[offset + 1] = (byte) ((it >>> 16) & 0xFF);
+ arr[offset + 2] = (byte) ((it >>> 8) & 0xFF);
+ arr[offset + 3] = (byte) ( it & 0xFF);
+ }
}