Example of music streaming from disk
This commit is contained in:
Binary file not shown.
@@ -14,6 +14,10 @@
|
||||
<name>tomp2p</name>
|
||||
<url>http://tomp2p.net/dev/mvn/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
@@ -56,5 +60,11 @@
|
||||
<artifactId>ts3j</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.manevolent</groupId>
|
||||
<artifactId>ffmpeg4j</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+1
-1
@@ -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
|
||||
|
||||
+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) {
|
||||
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;
|
||||
|
||||
|
||||
+2
-1
@@ -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());
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user