When the COMPRESSED flag is set by the official Teamspeak Gbmh client on a VOICE or VOICE_WHISPER packet, which is done for the first 5 packets of any decoder session, the packet is always 1 byte longer. More importantly, the final byte is static for those 5 packets.
You have to trim that last byte off, otherwise your audio will be corrupted. TS3J now does this automatically and stores it in ServerFlag0 for you, as a nullable Byte object. This way you can handle it if you need to. This has also been factored into the output/write stage. Digging into theory, I believe this ID has something to do with the opening of a new decoder to ensure that the decoder opens cleanly each time. You do NOT have to do anything to your decoder to reset states, etc., from what I can tell. In other words, COMPRESSED being on shouldn't change the behavior of your decoding.
This commit is contained in:
@@ -2,6 +2,8 @@ package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
import com.github.manevolent.ts3j.enums.CodecType;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -12,6 +14,13 @@ public class PacketBody0Voice extends PacketBody {
|
||||
private CodecType codecType;
|
||||
private byte[] codecData;
|
||||
|
||||
// Unknown at this time (have seen: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)
|
||||
// Set for the first 5 packets of a new voice session
|
||||
// I believe this is related to tracking opening a new decoder
|
||||
// Not all new voice sessions will change this flag
|
||||
// Best to ignore it for now, but it's here if you need it or figure it out
|
||||
private Byte serverFlag0;
|
||||
|
||||
public PacketBody0Voice(ProtocolRole role) {
|
||||
super(PacketBodyType.VOICE, role);
|
||||
}
|
||||
@@ -44,6 +53,12 @@ public class PacketBody0Voice extends PacketBody {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public Byte getServerFlag0() { return serverFlag0; }
|
||||
|
||||
public void setServerFlag0(Byte flag) {
|
||||
this.serverFlag0 = flag;
|
||||
}
|
||||
|
||||
public void setClientId(int clientId) {
|
||||
if (getRole() != ProtocolRole.SERVER)
|
||||
throw new IllegalStateException("cannot set client ID field on non-server packet");
|
||||
@@ -51,6 +66,32 @@ public class PacketBody0Voice extends PacketBody {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaderValues(PacketHeader header) {
|
||||
if (serverFlag0 != null) // Ensure padding is signaled
|
||||
header.setPacketFlag(HeaderFlag.COMPRESSED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(PacketHeader header, ByteBuffer buffer) {
|
||||
super.read(header, buffer);
|
||||
|
||||
int padding = 0;
|
||||
boolean compressed = header.getPacketFlag(HeaderFlag.COMPRESSED);
|
||||
|
||||
if (compressed)
|
||||
padding ++;
|
||||
|
||||
codecData = new byte[buffer.remaining() - padding];
|
||||
buffer.get(codecData);
|
||||
|
||||
// Handle trailing data
|
||||
if (padding > 0) {
|
||||
if (compressed)
|
||||
serverFlag0 = buffer.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
packetId = buffer.getShort() & 0x0000FFFF;
|
||||
@@ -59,10 +100,6 @@ public class PacketBody0Voice extends PacketBody {
|
||||
clientId = buffer.getShort() & 0x0000FFFF;
|
||||
|
||||
codecType = CodecType.fromId(buffer.get() & 0xFF);
|
||||
|
||||
codecData = new byte[buffer.remaining()];
|
||||
|
||||
buffer.get(codecData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,10 +112,17 @@ public class PacketBody0Voice extends PacketBody {
|
||||
buffer.put((byte)(codecType.getIndex() & 0xFF));
|
||||
|
||||
buffer.put(codecData);
|
||||
|
||||
if (serverFlag0 != null)
|
||||
buffer.put(serverFlag0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 2 + (getRole() == ProtocolRole.SERVER ? 2 : 0) + 1 + codecData.length;
|
||||
return 2 +
|
||||
(getRole() == ProtocolRole.SERVER ? 2 : 0) +
|
||||
1 +
|
||||
codecData.length +
|
||||
(serverFlag0 != null ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
+36
-6
@@ -16,20 +16,42 @@ public class PacketBody1VoiceWhisper extends PacketBody {
|
||||
private WhisperTarget target;
|
||||
private byte[] codecData;
|
||||
|
||||
// Unknown at this time (have seen: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07)
|
||||
// Set for the first 5 packets of a new voice session
|
||||
// I believe this is related to tracking opening a new decoder
|
||||
// Not all new voice sessions will change this flag
|
||||
// Best to ignore it for now, but it's here if you need it or figure it out
|
||||
private Byte serverFlag0;
|
||||
|
||||
public PacketBody1VoiceWhisper(ProtocolRole role) {
|
||||
super(PacketBodyType.VOICE_WHISPER, role);
|
||||
}
|
||||
|
||||
public Byte getServerFlag0() { return serverFlag0; }
|
||||
|
||||
public void setServerFlag0(Byte flag) {
|
||||
this.serverFlag0 = flag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaderValues(PacketHeader header) {
|
||||
if (target instanceof WhisperTargetGroup)
|
||||
header.setPacketFlag(HeaderFlag.NEW_PROTOCOL, true);
|
||||
else
|
||||
header.setPacketFlag(HeaderFlag.NEW_PROTOCOL, false);
|
||||
|
||||
if (serverFlag0 != null) // Ensure padding is signaled
|
||||
header.setPacketFlag(HeaderFlag.COMPRESSED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(PacketHeader header, ByteBuffer buffer) {
|
||||
int padding = 0;
|
||||
boolean compressed = header.getPacketFlag(HeaderFlag.COMPRESSED);
|
||||
|
||||
if (compressed)
|
||||
padding ++;
|
||||
|
||||
packetId = buffer.getShort() & 0xFFFF;
|
||||
|
||||
if (getRole() == ProtocolRole.SERVER)
|
||||
@@ -37,7 +59,6 @@ public class PacketBody1VoiceWhisper extends PacketBody {
|
||||
|
||||
codecType = CodecType.fromId((int) (buffer.get() & 0xFF));
|
||||
|
||||
|
||||
if (getRole() == ProtocolRole.CLIENT) {
|
||||
if (header.getPacketFlag(HeaderFlag.NEW_PROTOCOL)) {
|
||||
setTarget(new WhisperTargetGroup());
|
||||
@@ -50,14 +71,20 @@ public class PacketBody1VoiceWhisper extends PacketBody {
|
||||
|
||||
target.read(buffer);
|
||||
|
||||
read(buffer);
|
||||
codecData = new byte[buffer.remaining() - padding];
|
||||
buffer.get(codecData);
|
||||
|
||||
// Handle trailing data
|
||||
if (padding > 0) {
|
||||
if (compressed)
|
||||
serverFlag0 = buffer.get();
|
||||
}
|
||||
}
|
||||
|
||||
// We implemented the above so this is unneccessary.
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
codecData = new byte[buffer.remaining()];
|
||||
buffer.get(codecData);
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,15 +100,18 @@ public class PacketBody1VoiceWhisper extends PacketBody {
|
||||
target.write(buffer);
|
||||
|
||||
buffer.put(codecData);
|
||||
|
||||
if (serverFlag0 != null)
|
||||
buffer.put(serverFlag0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
switch (getRole()) {
|
||||
case CLIENT:
|
||||
return 2 + 1 + target.getSize() + codecData.length;
|
||||
return 2 + 1 + target.getSize() + codecData.length + (serverFlag0 != null ? 1 : 0);
|
||||
case SERVER:
|
||||
return 2 + 2 + 1 + codecData.length;
|
||||
return 2 + 2 + 1 + codecData.length + (serverFlag0 != null ? 1 : 0);
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown role");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -22,7 +23,7 @@ public class ServerConnectionTest {
|
||||
|
||||
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
|
||||
|
||||
Ts3Debugging.setEnabled(true);
|
||||
Ts3Debugging.setEnabled(false);
|
||||
|
||||
LocalIdentity identity = LocalIdentity.generateNew(10);
|
||||
|
||||
@@ -30,6 +31,10 @@ public class ServerConnectionTest {
|
||||
client.setNickname("ts3j issue 2");
|
||||
client.setHWID("TestTestTest");
|
||||
|
||||
client.setVoiceHandler(packet ->
|
||||
System.out.println(((PacketBody0Voice)packet.getBody()).getServerFlag0())
|
||||
);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
client.connect(
|
||||
|
||||
Reference in New Issue
Block a user