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,7 +15,6 @@ 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!!!!
@ -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()
{ {
if (avcNalDataChain != null && avcNalDataLength != 0) {
avcNalDataChain = null; avcNalDataChain = null;
avcNalDataLength = 0; avcNalDataLength = 0;
return true;
} }
private void reassembleAvcNal() return false;
}
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,91 +107,56 @@ 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))
{
// We're decoding H264 now
currentlyDecoding = AvDecodeUnit.TYPE_H264;
// Check if it's the end of the last frame
if (NAL.isAvcFrameStart(cachedDesc))
{
// Reassemble any pending AVC NAL
reassembleAvcNal(); reassembleAvcNal();
}
else {
int packetIndex = packet.getPacketIndex();
int packetsInFrame = packet.getTotalPackets();
// Setup state for the new NAL // Check if this is the first packet for a frame
if (packetIndex == 0) {
// Setup state for the new frame
avcNalDataChain = new LinkedList<AvByteBufferDescriptor>(); avcNalDataChain = new LinkedList<AvByteBufferDescriptor>();
avcNalDataLength = 0; avcNalDataLength = 0;
} }
// Skip the start sequence // Check if this packet falls in the range of packets in frame
location.length -= cachedDesc.length; if (packetIndex >= packetsInFrame) {
location.offset += cachedDesc.length; // This isn't H264 frame data
return;
} }
else
{ // Adjust the length to only contain valid data
// Check if this is padding after a full AVC frame location.length = packet.getPayloadLength();
if (currentlyDecoding == AvDecodeUnit.TYPE_H264 &&
NAL.isPadding(cachedDesc)) { // Add the payload data to the chain
// The decode unit is complete if (avcNalDataChain != null) {
avcNalDataChain.add(location);
avcNalDataLength += location.length;
}
// Reassemble the NALs if this was the last packet for this frame
if (packetIndex + 1 == packetsInFrame) {
reassembleAvcNal(); reassembleAvcNal();
} }
// Not decoding AVC
currentlyDecoding = AvDecodeUnit.TYPE_UNKNOWN;
// Just skip this byte
location.length--;
location.offset++;
}
}
// Move to the next special sequence
while (location.length != 0)
{
// 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,12 +172,11 @@ 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
// Request an IDR frame
controlListener.connectionNeedsResync(); 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()