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 getUsers() @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/OpusPacket.java b/src/main/java/net/dv8tion/jda/api/audio/OpusPacket.java new file mode 100644 index 0000000000..da9ad24be9 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/audio/OpusPacket.java @@ -0,0 +1,243 @@ +/* + * Copyright 2015-2019 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * 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.api.audio; + +import net.dv8tion.jda.internal.audio.AudioPacket; +import net.dv8tion.jda.internal.audio.Decoder; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Objects; + +/** + * A raw OPUS packet received from Discord that can be used for lazy decoding. + * + * @since 4.0.0 + * + * @see AudioReceiveHandler#canReceiveEncoded() + * @see AudioReceiveHandler#handleEncodedAudio(OpusPacket) + */ +public final class OpusPacket implements Comparable +{ + /** (Hz) We want to use the highest of qualities! All the bandwidth! */ + public static final int OPUS_SAMPLE_RATE = 48000; + /** An opus frame size of 960 at 48000hz represents 20 milliseconds of audio. */ + public static final int OPUS_FRAME_SIZE = 960; + /** This is 20 milliseconds. We are only dealing with 20ms opus packets. */ + public static final int OPUS_FRAME_TIME_AMOUNT = 20; + /** 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 int OPUS_CHANNEL_COUNT = 2; + + private final long userId; + private final byte[] opusAudio; + private final Decoder decoder; + private final AudioPacket rawPacket; + + private short[] decoded; + private boolean triedDecode; + + public OpusPacket(@Nonnull AudioPacket packet, long userId, @Nullable Decoder decoder) + { + this.rawPacket = packet; + this.userId = userId; + this.decoder = decoder; + this.opusAudio = packet.getEncodedAudio().array(); + } + + /** + * The sequence number of this packet. This is used as ordering key for {@link #compareTo(OpusPacket)}. + *
A char represents an unsigned short value in this case. + * + *

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. + *
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); + } }