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:
Cameron Gutman 2013-11-28 14:22:12 -05:00
parent a2a4159b0c
commit b0d138b7f2
2 changed files with 90 additions and 90 deletions

View File

@ -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)
{ {

View File

@ -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()