Example of music streaming from disk

This commit is contained in:
Manevolent
2018-12-24 20:32:26 -05:00
parent 7b25be6420
commit 941308d67b
7 changed files with 164 additions and 18 deletions
Binary file not shown.
+10
View File
@@ -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>
@@ -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
@@ -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);
}
}
@@ -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;
@@ -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();