From aee255a6ee816406e4d5159246b75cc77c76a0bd Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 25 Jan 2015 17:59:23 -0500 Subject: [PATCH] Backwards compatibility for video and control stream to GFE 2.1.x --- .../com/limelight/nvstream/NvConnection.java | 10 ++ .../nvstream/av/audio/AudioStream.java | 1 - .../nvstream/av/video/VideoStream.java | 11 +- .../nvstream/control/ControlStream.java | 111 ++++++++++++++---- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/moonlight-common/src/com/limelight/nvstream/NvConnection.java b/moonlight-common/src/com/limelight/nvstream/NvConnection.java index 2206054d..963e07b5 100644 --- a/moonlight-common/src/com/limelight/nvstream/NvConnection.java +++ b/moonlight-common/src/com/limelight/nvstream/NvConnection.java @@ -119,6 +119,16 @@ public class NvConnection { context.connListener.displayTransientMessage("This version of GFE is not currently supported. You may experience issues until Limelight is updated"); } + switch (majorVersion) { + case 3: + context.serverGeneration = ConnectionContext.SERVER_GENERATION_3; + break; + case 4: + default: + context.serverGeneration = ConnectionContext.SERVER_GENERATION_4; + break; + } + LimeLog.info("Server major version: "+majorVersion); } catch (NumberFormatException e) { context.connListener.displayMessage("Server version malformed: "+serverVersion); diff --git a/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java index c5be8d22..22f8244c 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java +++ b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java @@ -3,7 +3,6 @@ package com.limelight.nvstream.av.audio; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketException; import java.util.LinkedList; diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java index 88473930..a7cbb370 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; @@ -179,10 +178,20 @@ public class VideoStream { startReceiveThread(); } + // Open the first frame port connection on Gen 3 servers + if (context.serverGeneration == ConnectionContext.SERVER_GENERATION_3) { + connectFirstFrame(); + } + // Start pinging before reading the first frame // so GFE knows where to send UDP data startUdpPingThread(); + // Read the first frame on Gen 3 servers + if (context.serverGeneration == ConnectionContext.SERVER_GENERATION_3) { + readFirstFrame(); + } + return true; } diff --git a/moonlight-common/src/com/limelight/nvstream/control/ControlStream.java b/moonlight-common/src/com/limelight/nvstream/control/ControlStream.java index 8615fd0b..103495fa 100644 --- a/moonlight-common/src/com/limelight/nvstream/control/ControlStream.java +++ b/moonlight-common/src/com/limelight/nvstream/control/ControlStream.java @@ -19,23 +19,55 @@ public class ControlStream implements ConnectionStatusListener { public static final int CONTROL_TIMEOUT = 5000; - public static final short PTYPE_START_STREAM_A = 0x0606; - public static final short PPAYLEN_START_STREAM_A = 2; - public static final byte[] PPAYLOAD_START_STREAM_A = new byte[]{0, 0}; + private static final int IDX_START_A = 0; + private static final int IDX_START_B = 1; + private static final int IDX_RESYNC = 2; + private static final int IDX_LOSS_STATS = 3; - public static final short PTYPE_START_STREAM_B = 0x0609; - public static final short PPAYLEN_START_STREAM_B = 1; - public static final byte[] PPAYLOAD_START_STREAM_B = new byte[]{0}; + private static final short packetTypesGen3[] = { + 0x140b, // Start A + 0x1410, // Start B + 0x1404, // Resync + 0x140c, // Loss Stats + 0x1417, // Frame Stats (unused) + }; + private static final short packetTypesGen4[] = { + 0x0606, // Start A + 0x0609, // Start B + 0x0604, // Resync + 0x060a, // Loss Stats + 0x0611, // Frame Stats (unused) + }; - public static final short PTYPE_RESYNC = 0x0604; - public static final short PPAYLEN_RESYNC = 24; + private static final short payloadLengthsGen3[] = { + -1, // Start A + 16, // Start B + 24, // Resync + 32, // Loss Stats + 64, // Frame Stats + }; + private static final short payloadLengthsGen4[] = { + -1, // Start A + -1, // Start B + 24, // Resync + 32, // Loss Stats + 64, // Frame Stats + }; - public static final short PTYPE_LOSS_STATS = 0x060a; - public static final short PPAYLEN_LOSS_STATS = 32; - - // Currently unused - public static final short PTYPE_FRAME_STATS = 0x0611; - public static final short PPAYLEN_FRAME_STATS = 64; + private static final byte[] precontructedPayloadsGen3[] = { + new byte[]{0}, // Start A + null, // Start B + null, // Resync + null, // Loss Stats + null, // Frame Stats + }; + private static final byte[] precontructedPayloadsGen4[] = { + new byte[]{0, 0}, // Start A + new byte[]{0}, // Start B + null, // Resync + null, // Loss Stats + null, // Frame Stats + }; public static final int LOSS_REPORT_INTERVAL_MS = 50; @@ -62,9 +94,28 @@ public class ControlStream implements ConnectionStatusListener { private LinkedBlockingQueue invalidReferenceFrameTuples = new LinkedBlockingQueue(); private boolean aborting = false; + private final short[] packetTypes; + private final short[] payloadLengths; + private final byte[][] preconstructedPayloads; + public ControlStream(ConnectionContext context) { this.context = context; + + switch (context.serverGeneration) + { + case ConnectionContext.SERVER_GENERATION_3: + packetTypes = packetTypesGen3; + payloadLengths = payloadLengthsGen3; + preconstructedPayloads = precontructedPayloadsGen3; + break; + case ConnectionContext.SERVER_GENERATION_4: + default: + packetTypes = packetTypesGen4; + payloadLengths = payloadLengthsGen4; + preconstructedPayloads = precontructedPayloadsGen4; + break; + } } public void initialize() throws IOException @@ -102,7 +153,8 @@ public class ControlStream implements ConnectionStatusListener { bb.putInt(0); bb.putInt(0x14); - sendPacket(new NvCtlPacket(PTYPE_LOSS_STATS, PPAYLEN_LOSS_STATS, bb.array())); + sendPacket(new NvCtlPacket(packetTypes[IDX_LOSS_STATS], + payloadLengths[IDX_LOSS_STATS], bb.array())); } public void abort() @@ -148,7 +200,7 @@ public class ControlStream implements ConnectionStatusListener { lossStatsThread = new Thread() { @Override public void run() { - ByteBuffer bb = ByteBuffer.allocate(PPAYLEN_LOSS_STATS).order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer bb = ByteBuffer.allocate(payloadLengths[IDX_LOSS_STATS]).order(ByteOrder.LITTLE_ENDIAN); while (!isInterrupted()) { @@ -225,19 +277,33 @@ public class ControlStream implements ConnectionStatusListener { private ControlStream.NvCtlResponse doStartA() throws IOException { - return sendAndGetReply(new NvCtlPacket(PTYPE_START_STREAM_A, - PPAYLEN_START_STREAM_A, PPAYLOAD_START_STREAM_A)); + return sendAndGetReply(new NvCtlPacket(packetTypes[IDX_START_A], + (short) preconstructedPayloads[IDX_START_A].length, + preconstructedPayloads[IDX_START_A])); } private ControlStream.NvCtlResponse doStartB() throws IOException { - return sendAndGetReply(new NvCtlPacket(PTYPE_START_STREAM_B, - PPAYLEN_START_STREAM_B, PPAYLOAD_START_STREAM_B)); + if (context.serverGeneration == ConnectionContext.SERVER_GENERATION_3) { + ByteBuffer payload = ByteBuffer.wrap(new byte[payloadLengths[IDX_START_B]]).order(ByteOrder.LITTLE_ENDIAN); + + payload.putInt(0); + payload.putInt(0); + payload.putInt(0); + payload.putInt(0xa); + + return sendAndGetReply(new NvCtlPacket(packetTypes[IDX_START_B], + payloadLengths[IDX_START_B], payload.array())); + } + else { + return sendAndGetReply(new NvCtlPacket(packetTypes[IDX_START_B], + payloadLengths[IDX_START_B], preconstructedPayloads[IDX_START_B])); + } } private void sendResync(int firstLostFrame, int nextSuccessfulFrame) throws IOException { - ByteBuffer conf = ByteBuffer.wrap(new byte[PPAYLEN_RESYNC]).order(ByteOrder.LITTLE_ENDIAN); + ByteBuffer conf = ByteBuffer.wrap(new byte[payloadLengths[IDX_RESYNC]]).order(ByteOrder.LITTLE_ENDIAN); //conf.putLong(firstLostFrame); //conf.putLong(nextSuccessfulFrame); @@ -245,7 +311,8 @@ public class ControlStream implements ConnectionStatusListener { conf.putLong(0xFFFFF); conf.putLong(0); - sendAndGetReply(new NvCtlPacket(PTYPE_RESYNC, PPAYLEN_RESYNC, conf.array())); + sendAndGetReply(new NvCtlPacket(packetTypes[IDX_RESYNC], + payloadLengths[IDX_RESYNC], conf.array())); } static class NvCtlPacket {