diff --git a/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java b/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java index d9a7bd2a..e03e3b7c 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java +++ b/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java @@ -5,6 +5,8 @@ public class ByteBufferDescriptor { public int offset; public int length; + public ByteBufferDescriptor nextDescriptor; + public ByteBufferDescriptor(byte[] data, int offset, int length) { this.data = data; @@ -24,6 +26,7 @@ public class ByteBufferDescriptor { this.data = data; this.offset = offset; this.length = length; + this.nextDescriptor = null; } public void print() diff --git a/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java b/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java index dd81eab4..cdce37d7 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java +++ b/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java @@ -1,8 +1,5 @@ package com.limelight.nvstream.av; -import java.util.HashSet; -import java.util.List; - import com.limelight.nvstream.av.video.VideoPacket; public class DecodeUnit { @@ -14,26 +11,26 @@ public class DecodeUnit { public static final int DU_FLAG_SYNC_FRAME = 0x2; private int type; - private List bufferList; + private ByteBufferDescriptor bufferHead; private int dataLength; private int frameNumber; private long receiveTimestamp; private int flags; - private HashSet backingPackets; + private VideoPacket backingPacketHead; public DecodeUnit() { } - public void initialize(int type, List bufferList, int dataLength, - int frameNumber, long receiveTimestamp, int flags, HashSet backingPackets) + public void initialize(int type, ByteBufferDescriptor bufferHead, int dataLength, + int frameNumber, long receiveTimestamp, int flags, VideoPacket backingPacketHead) { this.type = type; - this.bufferList = bufferList; + this.bufferHead = bufferHead; this.dataLength = dataLength; this.frameNumber = frameNumber; this.receiveTimestamp = receiveTimestamp; this.flags = flags; - this.backingPackets = backingPackets; + this.backingPacketHead = backingPacketHead; } public int getType() @@ -46,9 +43,9 @@ public class DecodeUnit { return receiveTimestamp; } - public List getBufferList() + public ByteBufferDescriptor getBufferHead() { - return bufferList; + return bufferHead; } public int getDataLength() @@ -67,12 +64,11 @@ public class DecodeUnit { } // Internal use only - public HashSet getBackingPackets() { - return backingPackets; - } - - // Internal use only - public void clearBackingPackets() { - backingPackets.clear(); + public VideoPacket removeBackingPacketHead() { + VideoPacket pkt = backingPacketHead; + if (pkt != null) { + backingPacketHead = pkt.nextPacket; + } + return pkt; } } diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java index 0fb70f7d..6be5cdc4 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java @@ -1,8 +1,5 @@ package com.limelight.nvstream.av.video; -import java.util.HashSet; -import java.util.LinkedList; - import com.limelight.LimeLog; import com.limelight.nvstream.av.ByteBufferDescriptor; import com.limelight.nvstream.av.DecodeUnit; @@ -15,9 +12,11 @@ import com.limelight.nvstream.av.buffer.UnsynchronizedPopulatedBufferList; public class VideoDepacketizer { // Current frame state - private LinkedList avcFrameDataChain = null; private int avcFrameDataLength = 0; - private HashSet packetSet = null; + private ByteBufferDescriptor frameDataChainHead; + private ByteBufferDescriptor frameDataChainTail; + private VideoPacket backingPacketHead; + private VideoPacket backingPacketTail; // Sequencing state private int lastPacketInStream = 0; @@ -55,10 +54,10 @@ public class VideoDepacketizer { DecodeUnit du = (DecodeUnit) o; // Disassociate video packets from this DU - for (VideoPacket pkt : du.getBackingPackets()) { - pkt.decodeUnitRefCount.decrementAndGet(); + VideoPacket pkt; + while ((pkt = du.removeBackingPacketHead()) != null) { + pkt.dereferencePacket(); } - du.clearBackingPackets(); } }; @@ -94,22 +93,21 @@ public class VideoDepacketizer { private void cleanupAvcFrameState() { - if (packetSet != null) { - for (VideoPacket pkt : packetSet) { - pkt.decodeUnitRefCount.decrementAndGet(); - } - packetSet = null; + backingPacketTail = null; + while (backingPacketHead != null) { + backingPacketHead.dereferencePacket(); + backingPacketHead = backingPacketHead.nextPacket; } - avcFrameDataChain = null; + frameDataChainHead = frameDataChainTail = null; avcFrameDataLength = 0; } private void reassembleAvcFrame(int frameNumber) { // This is the start of a new frame - if (avcFrameDataChain != null && avcFrameDataLength != 0) { - ByteBufferDescriptor firstBuffer = avcFrameDataChain.getFirst(); + if (frameDataChainHead != null) { + ByteBufferDescriptor firstBuffer = frameDataChainHead; int flags = 0; if (NAL.getSpecialSequenceDescriptor(firstBuffer, cachedSpecialDesc) && NAL.isAvcFrameStart(cachedSpecialDesc)) { @@ -141,11 +139,11 @@ public class VideoDepacketizer { } // Initialize the free DU - du.initialize(DecodeUnit.TYPE_H264, avcFrameDataChain, - avcFrameDataLength, frameNumber, frameStartTime, flags, packetSet); + du.initialize(DecodeUnit.TYPE_H264, frameDataChainHead, + avcFrameDataLength, frameNumber, frameStartTime, flags, backingPacketHead); // Packets now owned by the DU - packetSet = null; + backingPacketTail = backingPacketHead = null; controlListener.connectionReceivedFrame(frameNumber); @@ -160,6 +158,39 @@ public class VideoDepacketizer { } } + private void chainBufferToCurrentFrame(ByteBufferDescriptor desc) { + desc.nextDescriptor = null; + + // Chain the packet + if (frameDataChainTail != null) { + frameDataChainTail.nextDescriptor = desc; + frameDataChainTail = desc; + } + else { + frameDataChainHead = frameDataChainTail = desc; + } + + avcFrameDataLength += desc.length; + } + + private void chainPacketToCurrentFrame(VideoPacket packet) { + // It's possible to get more than one NAL from a packet but we can cheaply + // check for this condition because all duplicates must be contiguous + if (backingPacketTail != packet) { + packet.referencePacket(); + packet.nextPacket = null; + + // Chain the packet + if (backingPacketTail != null) { + backingPacketTail.nextPacket = packet; + backingPacketTail = packet; + } + else { + backingPacketHead = backingPacketTail = packet; + } + } + } + private void addInputDataSlow(VideoPacket packet, ByteBufferDescriptor location) { boolean isDecodingH264 = false; @@ -185,11 +216,6 @@ public class VideoDepacketizer { // Reassemble any pending AVC NAL reassembleAvcFrame(packet.getFrameIndex()); - - // Setup state for the new NAL - avcFrameDataChain = new LinkedList(); - avcFrameDataLength = 0; - packetSet = new HashSet(); if (cachedSpecialDesc.data[cachedSpecialDesc.offset+cachedSpecialDesc.length] == 0x65) { // This is the NALU code for I-frame data @@ -241,17 +267,13 @@ public class VideoDepacketizer { location.length--; } - if (isDecodingH264 && avcFrameDataChain != null) + if (isDecodingH264 && decodingFrame) { - ByteBufferDescriptor data = new ByteBufferDescriptor(location.data, start, location.offset-start); + // Chain this packet to the current frame + chainPacketToCurrentFrame(packet); - if (packetSet.add(packet)) { - packet.decodeUnitRefCount.incrementAndGet(); - } - // Add a buffer descriptor describing the NAL data in this packet - avcFrameDataChain.add(data); - avcFrameDataLength += location.offset-start; + chainBufferToCurrentFrame(new ByteBufferDescriptor(location.data, start, location.offset-start)); } } } @@ -261,19 +283,13 @@ public class VideoDepacketizer { if (firstPacket) { // Setup state for the new frame frameStartTime = System.currentTimeMillis(); - avcFrameDataChain = new LinkedList(); - avcFrameDataLength = 0; - packetSet = new HashSet(); } // Add the payload data to the chain - avcFrameDataChain.add(new ByteBufferDescriptor(location)); - avcFrameDataLength += location.length; + chainBufferToCurrentFrame(new ByteBufferDescriptor(location)); // The receive thread can't use this until we're done with it - if (packetSet.add(packet)) { - packet.decodeUnitRefCount.incrementAndGet(); - } + chainPacketToCurrentFrame(packet); } private static boolean isFirstPacket(int flags) { diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java index 2b7426dc..7ea73968 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java @@ -9,8 +9,9 @@ import com.limelight.nvstream.av.RtpPacket; import com.limelight.nvstream.av.RtpPacketFields; public class VideoPacket implements RtpPacketFields { - private ByteBufferDescriptor buffer; - private ByteBuffer byteBuffer; + private final ByteBufferDescriptor buffer; + private final ByteBuffer byteBuffer; + private final boolean useAtomicRefCount; private int dataOffset; @@ -20,7 +21,11 @@ public class VideoPacket implements RtpPacketFields { private short rtpSequenceNumber; - AtomicInteger decodeUnitRefCount = new AtomicInteger(); + private AtomicInteger duAtomicRefCount = new AtomicInteger(); + private int duRefCount; + + // Only for use in DecodeUnit for packet queuing + public VideoPacket nextPacket; public static final int FLAG_CONTAINS_PIC_DATA = 0x1; public static final int FLAG_EOF = 0x2; @@ -28,10 +33,11 @@ public class VideoPacket implements RtpPacketFields { public static final int HEADER_SIZE = 16; - public VideoPacket(byte[] buffer) + public VideoPacket(byte[] buffer, boolean useAtomicRefCount) { this.buffer = new ByteBufferDescriptor(buffer, 0, buffer.length); this.byteBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN); + this.useAtomicRefCount = useAtomicRefCount; } public void initializeWithLengthNoRtpHeader(int length) @@ -108,4 +114,31 @@ public class VideoPacket implements RtpPacketFields { public short getRtpSequenceNumber() { return rtpSequenceNumber; } + + int referencePacket() { + if (useAtomicRefCount) { + return duAtomicRefCount.incrementAndGet(); + } + else { + return ++duRefCount; + } + } + + int dereferencePacket() { + if (useAtomicRefCount) { + return duAtomicRefCount.decrementAndGet(); + } + else { + return --duRefCount; + } + } + + int getRefCount() { + if (useAtomicRefCount) { + return duAtomicRefCount.get(); + } + else { + return duRefCount; + } + } } 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 2fc5272f..ac18344a 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java @@ -189,14 +189,14 @@ public class VideoStream { RtpReorderQueue rtpQueue = new RtpReorderQueue(16, MAX_RTP_QUEUE_DELAY_MS); RtpReorderQueue.RtpQueueStatus queueStatus; + boolean directSubmit = (decRend != null && (decRend.getCapabilities() & + VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT) != 0); + // Preinitialize the ring buffer int requiredBufferSize = context.streamConfig.getMaxPacketSize() + RtpPacket.MAX_HEADER_SIZE; for (int i = 0; i < VIDEO_RING_SIZE; i++) { - ring[i] = new VideoPacket(new byte[requiredBufferSize]); + ring[i] = new VideoPacket(new byte[requiredBufferSize], !directSubmit); } - - boolean directSubmit = (decRend != null && (decRend.getCapabilities() & - VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT) != 0); byte[] buffer; DatagramPacket packet = new DatagramPacket(new byte[1], 1); // Placeholder array @@ -243,11 +243,11 @@ public class VideoStream { // Reinitialize the video ring since they're all being used LimeLog.warning("Packet ring wrapped around!"); for (int i = 0; i < VIDEO_RING_SIZE; i++) { - ring[i] = new VideoPacket(new byte[requiredBufferSize]); + ring[i] = new VideoPacket(new byte[requiredBufferSize], !directSubmit); } break; } - } while (ring[ringIndex].decodeUnitRefCount.get() != 0); + } while (ring[ringIndex].getRefCount() != 0); } catch (IOException e) { context.connListener.connectionTerminated(e); return;