Example of music streaming from disk
This commit is contained in:
Binary file not shown.
@@ -14,6 +14,10 @@
|
|||||||
<name>tomp2p</name>
|
<name>tomp2p</name>
|
||||||
<url>http://tomp2p.net/dev/mvn/</url>
|
<url>http://tomp2p.net/dev/mvn/</url>
|
||||||
</repository>
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -56,5 +60,11 @@
|
|||||||
<artifactId>ts3j</artifactId>
|
<artifactId>ts3j</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.manevolent</groupId>
|
||||||
|
<artifactId>ffmpeg4j</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
+1
-1
@@ -21,7 +21,7 @@ public class AudioSender {
|
|||||||
|
|
||||||
TeamspeakFastMixerSink sink = new TeamspeakFastMixerSink(
|
TeamspeakFastMixerSink sink = new TeamspeakFastMixerSink(
|
||||||
AUDIO_FORMAT,
|
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(
|
new OpusParameters(
|
||||||
20,
|
20,
|
||||||
96000, // 96kbps
|
96000, // 96kbps
|
||||||
|
|||||||
+141
@@ -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<AudioFrame> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-3
@@ -77,6 +77,10 @@ public class TeamspeakFastMixerSink implements TeamspeakMixerSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void write(float[] buffer, int len) {
|
public void write(float[] buffer, int len) {
|
||||||
|
write(buffer, 0, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(float[] buffer, int offs, int len) {
|
||||||
synchronized (stateLock) {
|
synchronized (stateLock) {
|
||||||
if (!running) throw new IllegalStateException("not running");
|
if (!running) throw new IllegalStateException("not running");
|
||||||
|
|
||||||
@@ -85,9 +89,9 @@ public class TeamspeakFastMixerSink implements TeamspeakMixerSink {
|
|||||||
throw new IllegalArgumentException(len + " > " + availableInput());
|
throw new IllegalArgumentException(len + " > " + availableInput());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len > buffer.length) {
|
if (offs+len > buffer.length) {
|
||||||
overflowed++;
|
overflowed++;
|
||||||
throw new IllegalArgumentException(len + " > " + buffer.length);
|
throw new IllegalArgumentException(offs+len + " > " + buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len <= 0)
|
if (len <= 0)
|
||||||
@@ -97,7 +101,7 @@ public class TeamspeakFastMixerSink implements TeamspeakMixerSink {
|
|||||||
throw new IllegalArgumentException("not a full frame");
|
throw new IllegalArgumentException("not a full frame");
|
||||||
|
|
||||||
// Write to the buffer
|
// Write to the buffer
|
||||||
System.arraycopy(buffer, 0, sampleBuffer, samplePosition, len);
|
System.arraycopy(buffer, offs, sampleBuffer, samplePosition, len);
|
||||||
samplePosition += len;
|
samplePosition += len;
|
||||||
availableInput -= len;
|
availableInput -= len;
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -536,7 +536,8 @@ public class LocalTeamspeakClientSocket
|
|||||||
// Set a state and send a muted event
|
// Set a state and send a muted event
|
||||||
} else if (microphone.isReady()) {
|
} else if (microphone.isReady()) {
|
||||||
byte[] data = microphone.provide();
|
byte[] data = microphone.provide();
|
||||||
if (data.length <= 0) return;
|
if (data.length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
PacketBody0Voice voice = new PacketBody0Voice(getRole().getOut());
|
PacketBody0Voice voice = new PacketBody0Voice(getRole().getOut());
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,19 @@
|
|||||||
package com.github.manevolent.ts3j;
|
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.identity.LocalIdentity;
|
||||||
import com.github.manevolent.ts3j.protocol.Packet;
|
|
||||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
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.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
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;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
public class ServerConnectionTest {
|
public class ServerConnectionTest {
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
Ts3Debugging.setEnabled(true);
|
Ts3Debugging.setEnabled(true);
|
||||||
|
|
||||||
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
|
|
||||||
|
|
||||||
LocalIdentity identity = LocalIdentity.generateNew(10);
|
LocalIdentity identity = LocalIdentity.generateNew(10);
|
||||||
|
|
||||||
|
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
|
||||||
client.setIdentity(identity);
|
client.setIdentity(identity);
|
||||||
client.setNickname(ServerConnectionTest.class.getSimpleName());
|
client.setNickname(ServerConnectionTest.class.getSimpleName());
|
||||||
client.setHWID("TestTestTest");
|
client.setHWID("TestTestTest");
|
||||||
@@ -38,6 +26,8 @@ public class ServerConnectionTest {
|
|||||||
|
|
||||||
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
|
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
|
||||||
|
|
||||||
|
client.subscribeAll();
|
||||||
|
|
||||||
client.disconnect("BYE");
|
client.disconnect("BYE");
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
|||||||
Reference in New Issue
Block a user