mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 03:23:07 +00:00
Rewrite the NAL depacketizer to run in O(1) time by parsing the proprietary header rather than the H264 bytestream
This commit is contained in:
parent
a2a4159b0c
commit
b0d138b7f2
@ -15,8 +15,7 @@ public class AvVideoDepacketizer {
|
|||||||
// Current NAL state
|
// Current NAL state
|
||||||
private LinkedList<AvByteBufferDescriptor> avcNalDataChain = null;
|
private LinkedList<AvByteBufferDescriptor> avcNalDataChain = null;
|
||||||
private int avcNalDataLength = 0;
|
private int avcNalDataLength = 0;
|
||||||
private int currentlyDecoding;
|
|
||||||
|
|
||||||
// Cached buffer descriptor to save on allocations
|
// Cached buffer descriptor to save on allocations
|
||||||
// Only safe to use in decode thread!!!!
|
// Only safe to use in decode thread!!!!
|
||||||
private AvByteBufferDescriptor cachedDesc;
|
private AvByteBufferDescriptor cachedDesc;
|
||||||
@ -35,13 +34,18 @@ public class AvVideoDepacketizer {
|
|||||||
this.cachedDesc = new AvByteBufferDescriptor(null, 0, 0);
|
this.cachedDesc = new AvByteBufferDescriptor(null, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearAvcNalState()
|
private boolean clearAvcNalState()
|
||||||
{
|
{
|
||||||
avcNalDataChain = null;
|
if (avcNalDataChain != null && avcNalDataLength != 0) {
|
||||||
avcNalDataLength = 0;
|
avcNalDataChain = null;
|
||||||
|
avcNalDataLength = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reassembleAvcNal()
|
private boolean reassembleAvcNal()
|
||||||
{
|
{
|
||||||
// This is the start of a new NAL
|
// This is the start of a new NAL
|
||||||
if (avcNalDataChain != null && avcNalDataLength != 0)
|
if (avcNalDataChain != null && avcNalDataLength != 0)
|
||||||
@ -103,90 +107,55 @@ public class AvVideoDepacketizer {
|
|||||||
// Clear old state
|
// Clear old state
|
||||||
avcNalDataChain = null;
|
avcNalDataChain = null;
|
||||||
avcNalDataLength = 0;
|
avcNalDataLength = 0;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addInputData(AvVideoPacket packet)
|
public void addInputData(AvVideoPacket packet)
|
||||||
{
|
{
|
||||||
AvByteBufferDescriptor location = packet.getNewPayloadDescriptor();
|
AvByteBufferDescriptor location = packet.getNewPayloadDescriptor();
|
||||||
|
|
||||||
while (location.length != 0)
|
// SPS and PPS packet doesn't have standard headers, so submit it as is
|
||||||
{
|
if (location.length < 968) {
|
||||||
// Remember the start of the NAL data in this packet
|
avcNalDataChain = new LinkedList<AvByteBufferDescriptor>();
|
||||||
int start = location.offset;
|
avcNalDataLength = 0;
|
||||||
|
|
||||||
// Check for a special sequence
|
avcNalDataChain.add(location);
|
||||||
if (NAL.getSpecialSequenceDescriptor(location, cachedDesc))
|
avcNalDataLength += location.length;
|
||||||
{
|
|
||||||
if (NAL.isAvcStartSequence(cachedDesc))
|
reassembleAvcNal();
|
||||||
{
|
}
|
||||||
// We're decoding H264 now
|
else {
|
||||||
currentlyDecoding = AvDecodeUnit.TYPE_H264;
|
int packetIndex = packet.getPacketIndex();
|
||||||
|
int packetsInFrame = packet.getTotalPackets();
|
||||||
// Check if it's the end of the last frame
|
|
||||||
if (NAL.isAvcFrameStart(cachedDesc))
|
// Check if this is the first packet for a frame
|
||||||
{
|
if (packetIndex == 0) {
|
||||||
// Reassemble any pending AVC NAL
|
// Setup state for the new frame
|
||||||
reassembleAvcNal();
|
avcNalDataChain = new LinkedList<AvByteBufferDescriptor>();
|
||||||
|
avcNalDataLength = 0;
|
||||||
// Setup state for the new NAL
|
}
|
||||||
avcNalDataChain = new LinkedList<AvByteBufferDescriptor>();
|
|
||||||
avcNalDataLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the start sequence
|
|
||||||
location.length -= cachedDesc.length;
|
|
||||||
location.offset += cachedDesc.length;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Check if this is padding after a full AVC frame
|
|
||||||
if (currentlyDecoding == AvDecodeUnit.TYPE_H264 &&
|
|
||||||
NAL.isPadding(cachedDesc)) {
|
|
||||||
// The decode unit is complete
|
|
||||||
reassembleAvcNal();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not decoding AVC
|
// Check if this packet falls in the range of packets in frame
|
||||||
currentlyDecoding = AvDecodeUnit.TYPE_UNKNOWN;
|
if (packetIndex >= packetsInFrame) {
|
||||||
|
// This isn't H264 frame data
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Just skip this byte
|
// Adjust the length to only contain valid data
|
||||||
location.length--;
|
location.length = packet.getPayloadLength();
|
||||||
location.offset++;
|
|
||||||
}
|
// Add the payload data to the chain
|
||||||
|
if (avcNalDataChain != null) {
|
||||||
|
avcNalDataChain.add(location);
|
||||||
|
avcNalDataLength += location.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to the next special sequence
|
// Reassemble the NALs if this was the last packet for this frame
|
||||||
while (location.length != 0)
|
if (packetIndex + 1 == packetsInFrame) {
|
||||||
{
|
reassembleAvcNal();
|
||||||
// Catch the easy case first where byte 0 != 0x00
|
|
||||||
if (location.data[location.offset] == 0x00)
|
|
||||||
{
|
|
||||||
// Check if this should end the current NAL
|
|
||||||
if (NAL.getSpecialSequenceDescriptor(location, cachedDesc))
|
|
||||||
{
|
|
||||||
// Only stop if we're decoding something or this
|
|
||||||
// isn't padding
|
|
||||||
if (currentlyDecoding != AvDecodeUnit.TYPE_UNKNOWN ||
|
|
||||||
!NAL.isPadding(cachedDesc))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This byte is part of the NAL data
|
|
||||||
location.offset++;
|
|
||||||
location.length--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentlyDecoding == AvDecodeUnit.TYPE_H264 && avcNalDataChain != null)
|
|
||||||
{
|
|
||||||
AvByteBufferDescriptor data = new AvByteBufferDescriptor(location.data, start, location.offset-start);
|
|
||||||
|
|
||||||
// Add a buffer descriptor describing the NAL data in this packet
|
|
||||||
avcNalDataChain.add(data);
|
|
||||||
avcNalDataLength += location.offset-start;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,11 +172,10 @@ public class AvVideoDepacketizer {
|
|||||||
System.out.println("Received OOS video data (expected "+(lastSequenceNumber + 1)+", got "+seq+")");
|
System.out.println("Received OOS video data (expected "+(lastSequenceNumber + 1)+", got "+seq+")");
|
||||||
|
|
||||||
// Reset the depacketizer state
|
// Reset the depacketizer state
|
||||||
currentlyDecoding = AvDecodeUnit.TYPE_UNKNOWN;
|
if (clearAvcNalState()) {
|
||||||
clearAvcNalState();
|
// Request an IDR frame if we had to drop a NAL
|
||||||
|
controlListener.connectionNeedsResync();
|
||||||
// Request an IDR frame
|
}
|
||||||
controlListener.connectionNeedsResync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSequenceNumber = seq;
|
lastSequenceNumber = seq;
|
||||||
@ -232,13 +200,6 @@ class NAL {
|
|||||||
return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x01);
|
return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This assumes that the buffer passed in is already a special sequence
|
|
||||||
public static boolean isPadding(AvByteBufferDescriptor specialSeq)
|
|
||||||
{
|
|
||||||
// The padding sequence is 00 00 00
|
|
||||||
return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This assumes that the buffer passed in is already a special sequence
|
// This assumes that the buffer passed in is already a special sequence
|
||||||
public static boolean isAvcFrameStart(AvByteBufferDescriptor specialSeq)
|
public static boolean isAvcFrameStart(AvByteBufferDescriptor specialSeq)
|
||||||
{
|
{
|
||||||
|
@ -1,13 +1,52 @@
|
|||||||
package com.limelight.nvstream.av.video;
|
package com.limelight.nvstream.av.video;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
import com.limelight.nvstream.av.AvByteBufferDescriptor;
|
import com.limelight.nvstream.av.AvByteBufferDescriptor;
|
||||||
|
|
||||||
public class AvVideoPacket {
|
public class AvVideoPacket {
|
||||||
private AvByteBufferDescriptor buffer;
|
private AvByteBufferDescriptor buffer;
|
||||||
|
|
||||||
|
private int frameIndex;
|
||||||
|
private int packetIndex;
|
||||||
|
private int totalPackets;
|
||||||
|
private int payloadLength;
|
||||||
|
|
||||||
public AvVideoPacket(AvByteBufferDescriptor rtpPayload)
|
public AvVideoPacket(AvByteBufferDescriptor rtpPayload)
|
||||||
{
|
{
|
||||||
buffer = new AvByteBufferDescriptor(rtpPayload);
|
buffer = new AvByteBufferDescriptor(rtpPayload);
|
||||||
|
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(buffer.data).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
bb.position(buffer.offset);
|
||||||
|
|
||||||
|
frameIndex = bb.getInt();
|
||||||
|
packetIndex = bb.getInt();
|
||||||
|
totalPackets = bb.getInt();
|
||||||
|
|
||||||
|
bb.position(bb.position()+4);
|
||||||
|
|
||||||
|
payloadLength = bb.getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFrameIndex()
|
||||||
|
{
|
||||||
|
return frameIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPacketIndex()
|
||||||
|
{
|
||||||
|
return packetIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPayloadLength()
|
||||||
|
{
|
||||||
|
return payloadLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalPackets()
|
||||||
|
{
|
||||||
|
return totalPackets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AvByteBufferDescriptor getNewPayloadDescriptor()
|
public AvByteBufferDescriptor getNewPayloadDescriptor()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user