diff --git a/examples/audio/bensound-ukulele.mp3 b/examples/audio/bensound-ukulele.mp3
new file mode 100644
index 0000000..c56b657
Binary files /dev/null and b/examples/audio/bensound-ukulele.mp3 differ
diff --git a/examples/audio/pom.xml b/examples/audio/pom.xml
index 22b5370..8fc2f95 100644
--- a/examples/audio/pom.xml
+++ b/examples/audio/pom.xml
@@ -14,6 +14,10 @@
tomp2p
http://tomp2p.net/dev/mvn/
+
+ jitpack.io
+ https://jitpack.io
+
@@ -56,5 +60,11 @@
ts3j
1.0-SNAPSHOT
+
+
+ com.github.manevolent
+ ffmpeg4j
+ 1.0
+
\ No newline at end of file
diff --git a/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/AudioSender.java b/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/AudioSender.java
index 881abd6..f443b86 100644
--- a/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/AudioSender.java
+++ b/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/AudioSender.java
@@ -21,7 +21,7 @@ public class AudioSender {
TeamspeakFastMixerSink sink = new TeamspeakFastMixerSink(
AUDIO_FORMAT,
- (int) AUDIO_FORMAT.getSampleRate() * AUDIO_FORMAT.getChannels() * 4 /*2=16bit float*/,
+ (int) AUDIO_FORMAT.getSampleRate() * AUDIO_FORMAT.getChannels() * 4 /*4=32bit float*/,
new OpusParameters(
20,
96000, // 96kbps
diff --git a/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/MusicPlayer.java b/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/MusicPlayer.java
new file mode 100644
index 0000000..32ac824
--- /dev/null
+++ b/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/MusicPlayer.java
@@ -0,0 +1,141 @@
+package com.github.manevolent.ts3j.examples.audio;
+
+import com.github.manevolent.ffmpeg4j.*;
+import com.github.manevolent.ffmpeg4j.filter.audio.FFmpegAudioResampleFilter;
+import com.github.manevolent.ffmpeg4j.source.FFmpegAudioSourceSubstream;
+import com.github.manevolent.ffmpeg4j.stream.source.FFmpegSourceStream;
+import com.github.manevolent.ts3j.identity.LocalIdentity;
+import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
+import com.github.manevolent.ts3j.util.Ts3Debugging;
+
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static com.github.manevolent.ts3j.examples.audio.TeamspeakFastMixerSink.AUDIO_FORMAT;
+
+public class MusicPlayer {
+ public static void main(String[] args) throws Exception {
+ Ts3Debugging.setEnabled(false);
+
+ // Open file
+ FFmpeg.register();
+ InputStream inputStream = new FileInputStream("bensound-ukulele.mp3");
+ FFmpegInput input = new FFmpegInput(inputStream);
+ FFmpegSourceStream stream = input.open(FFmpeg.getInputFormatByName("mp3"));
+ FFmpegAudioSourceSubstream audioSourceSubstream =
+ (FFmpegAudioSourceSubstream) stream.registerStreams()
+ .stream()
+ .filter(x -> x.getMediaType() == MediaType.AUDIO)
+ .findFirst().orElse(null);
+
+ if (audioSourceSubstream == null) throw new NullPointerException("ya dun goofed, bud");
+
+ // Teamspeak start up
+ LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
+ LocalIdentity identity = LocalIdentity.generateNew(10);
+ client.setIdentity(identity);
+ client.setNickname(MusicPlayer.class.getSimpleName());
+ client.setHWID("TestTestTest");
+
+ // Create a sink
+ TeamspeakFastMixerSink sink = new TeamspeakFastMixerSink(
+ AUDIO_FORMAT,
+ (int) AUDIO_FORMAT.getSampleRate() * AUDIO_FORMAT.getChannels() * 4 /*4=32bit float*/,
+ new OpusParameters(
+ 20,
+ 96000, // 96kbps
+ 10, // max complexity
+ 0, // 0 expected packet loss
+ false, // no VBR
+ false, // no FEC
+ true // OPUS MUSIC - channel doesn't have to be Opus Music ;)
+ )
+ );
+
+ client.setMicrophone(sink);
+
+ sink.start();
+
+ client.connect(
+ "your.teamspeak.server",
+ 10000L
+ );
+
+ int bufferSize = AUDIO_FORMAT.getChannels() * (int) AUDIO_FORMAT.getSampleRate(); // Just to keep it orderly
+ FFmpegAudioResampleFilter resampleFilter = new FFmpegAudioResampleFilter(
+ audioSourceSubstream.getFormat(),
+ new AudioFormat(
+ (int) AUDIO_FORMAT.getSampleRate(),
+ AUDIO_FORMAT.getChannels(),
+ FFmpeg.guessFFMpegChannelLayout(AUDIO_FORMAT.getChannels())
+ ),
+ bufferSize
+ );
+
+ Queue frameQueue = new LinkedBlockingQueue<>();
+ AudioFrame currentFrame = null;
+ int frameOffset = 0; // offset within current frame
+
+ long wake = System.nanoTime();
+ long delay = 150 * 1_000_000; // 50ms interval
+ long sleep;
+ while (true) {
+ int available = sink.availableInput();
+
+ if (available > 0) {
+ if (currentFrame == null || frameOffset >= currentFrame.getLength()) {
+ if (frameQueue.peek() == null) {
+ try {
+ frameQueue.addAll(resampleFilter.apply(audioSourceSubstream.next()));
+ } catch (EOFException ex) {
+ // flush currentFrame
+ break;
+ }
+ }
+
+ currentFrame = frameQueue.remove();
+ frameOffset = 0;
+ }
+
+ int write = Math.min(currentFrame.getLength() - frameOffset, available);
+
+ sink.write(
+ currentFrame.getSamples(),
+ frameOffset,
+ write
+ );
+
+ frameOffset += write;
+
+ continue;
+ }
+
+ wake += delay;
+ sleep = (wake - System.nanoTime()) / 1_000_000;
+
+ if (sleep > 0) Thread.sleep(sleep);
+ }
+
+ System.err.println("Draining...");
+
+ sink.drain(); // make sure TS gets everything
+
+ client.disconnect("BYE");
+
+ System.err.println("Encoder time taken = " + sink.getNanotime() + " ns");
+ System.err.println("Audio position = " + sink.getPosition() + " samples sank");
+ System.err.println("Encoder position = " + sink.getEncoderPosition() + " samples encoded");
+ System.err.println("Network position = " + sink.getNetworkPosition() + " bytes sent");
+ System.err.println("Packets encoded = " + sink.getPacketsEncoded() + " packets encoded");
+ System.err.println("Packets sent = " + sink.getPacketsSent() + " packets sent");
+ System.err.println("Overflow events = " + sink.getOverflows());
+
+ // (resource starvation; can cause lost packets)
+ System.err.println("Underflow events = " + sink.getUnderflows());
+
+ System.exit(0);
+ }
+}
diff --git a/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/TeamspeakFastMixerSink.java b/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/TeamspeakFastMixerSink.java
index 7454e03..0c7b49b 100644
--- a/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/TeamspeakFastMixerSink.java
+++ b/examples/audio/src/main/java/com/github/manevolent/ts3j/examples/audio/TeamspeakFastMixerSink.java
@@ -77,6 +77,10 @@ public class TeamspeakFastMixerSink implements TeamspeakMixerSink {
}
public void write(float[] buffer, int len) {
+ write(buffer, 0, len);
+ }
+
+ public void write(float[] buffer, int offs, int len) {
synchronized (stateLock) {
if (!running) throw new IllegalStateException("not running");
@@ -85,9 +89,9 @@ public class TeamspeakFastMixerSink implements TeamspeakMixerSink {
throw new IllegalArgumentException(len + " > " + availableInput());
}
- if (len > buffer.length) {
+ if (offs+len > buffer.length) {
overflowed++;
- throw new IllegalArgumentException(len + " > " + buffer.length);
+ throw new IllegalArgumentException(offs+len + " > " + buffer.length);
}
if (len <= 0)
@@ -97,7 +101,7 @@ public class TeamspeakFastMixerSink implements TeamspeakMixerSink {
throw new IllegalArgumentException("not a full frame");
// Write to the buffer
- System.arraycopy(buffer, 0, sampleBuffer, samplePosition, len);
+ System.arraycopy(buffer, offs, sampleBuffer, samplePosition, len);
samplePosition += len;
availableInput -= len;
diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java
index 4780f1c..95aaf08 100644
--- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java
+++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/LocalTeamspeakClientSocket.java
@@ -536,7 +536,8 @@ public class LocalTeamspeakClientSocket
// Set a state and send a muted event
} else if (microphone.isReady()) {
byte[] data = microphone.provide();
- if (data.length <= 0) return;
+ if (data.length <= 0)
+ return;
PacketBody0Voice voice = new PacketBody0Voice(getRole().getOut());
diff --git a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java
index 13e352a..bb206d4 100644
--- a/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java
+++ b/src/test/java/com/github/manevolent/ts3j/ServerConnectionTest.java
@@ -1,31 +1,19 @@
package com.github.manevolent.ts3j;
;
-import com.github.manevolent.ts3j.api.Client;
-import com.github.manevolent.ts3j.audio.Microphone;
-import com.github.manevolent.ts3j.enums.CodecType;
-import com.github.manevolent.ts3j.event.*;
import com.github.manevolent.ts3j.identity.LocalIdentity;
-import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
-import com.github.manevolent.ts3j.protocol.packet.PacketBody0Voice;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Debugging;
-import java.math.BigInteger;
-import java.net.InetSocketAddress;
-import java.util.Base64;
-import java.util.function.Consumer;
-
import static org.junit.Assert.assertEquals;
public class ServerConnectionTest {
public static void main(String[] args) throws Exception {
Ts3Debugging.setEnabled(true);
- LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
-
LocalIdentity identity = LocalIdentity.generateNew(10);
+ LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
client.setIdentity(identity);
client.setNickname(ServerConnectionTest.class.getSimpleName());
client.setHWID("TestTestTest");
@@ -38,6 +26,8 @@ public class ServerConnectionTest {
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
+ client.subscribeAll();
+
client.disconnect("BYE");
} catch (Exception ex) {
ex.printStackTrace();