From 9e09ca2b7afdc9ab629854d1fc2d04462d067b07 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 30 Oct 2013 20:14:54 -0400 Subject: [PATCH] WIP: Add RTP class. Massive refactoring of NvVideoStream. Rename AvParser to AvDepacketizer. Begin parsing other special NAL sequences. --- src/com/limelight/nvstream/NvVideoStream.java | 424 ++++++++++-------- .../limelight/nvstream/av/AvDecodeUnit.java | 9 +- .../limelight/nvstream/av/AvDepacketizer.java | 274 +++++++++++ src/com/limelight/nvstream/av/AvParser.java | 131 ------ .../limelight/nvstream/av/AvRtpPacket.java | 32 ++ 5 files changed, 563 insertions(+), 307 deletions(-) create mode 100644 src/com/limelight/nvstream/av/AvDepacketizer.java delete mode 100644 src/com/limelight/nvstream/av/AvParser.java create mode 100644 src/com/limelight/nvstream/av/AvRtpPacket.java diff --git a/src/com/limelight/nvstream/NvVideoStream.java b/src/com/limelight/nvstream/NvVideoStream.java index 1fe24d0c..c4871f37 100644 --- a/src/com/limelight/nvstream/NvVideoStream.java +++ b/src/com/limelight/nvstream/NvVideoStream.java @@ -1,29 +1,23 @@ package com.limelight.nvstream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import java.util.concurrent.LinkedBlockingQueue; import com.limelight.nvstream.av.AvBufferDescriptor; import com.limelight.nvstream.av.AvBufferPool; import com.limelight.nvstream.av.AvDecodeUnit; import com.limelight.nvstream.av.AvPacket; -import com.limelight.nvstream.av.AvParser; +import com.limelight.nvstream.av.AvDepacketizer; +import com.limelight.nvstream.av.AvRtpPacket; -import jlibrtp.DataFrame; import jlibrtp.Participant; -import jlibrtp.RTPAppIntf; import jlibrtp.RTPSession; import android.media.MediaCodec; @@ -39,15 +33,72 @@ public class NvVideoStream { private ByteBuffer[] videoDecoderInputBuffers = null; private MediaCodec videoDecoder; + private LinkedBlockingQueue packets = new LinkedBlockingQueue(); + + private RTPSession session; + private DatagramSocket rtp; + private AvBufferPool pool = new AvBufferPool(1500); - private AvParser parser = new AvParser(); + private AvDepacketizer depacketizer = new AvDepacketizer(); - private InputStream getFirstFrame(String host) throws UnknownHostException, IOException + private InputStream openFirstFrameInputStream(String host) throws UnknownHostException, IOException { Socket s = new Socket(host, FIRST_FRAME_PORT); return s.getInputStream(); } + + private void readFirstFrame(String host) throws IOException + { + byte[] firstFrame = pool.allocate(); + System.out.println("VID: Waiting for first frame"); + InputStream firstFrameStream = openFirstFrameInputStream(host); + + int offset = 0; + for (;;) + { + int bytesRead = firstFrameStream.read(firstFrame, offset, firstFrame.length-offset); + + if (bytesRead == -1) + break; + + offset += bytesRead; + } + + System.out.println("VID: First frame read ("+offset+" bytes)"); + + // FIXME: Investigate: putting these NALs into the data stream + // causes the picture to get messed up + //depacketizer.addInputData(new AvPacket(new AvBufferDescriptor(firstFrame, 0, offset))); + } + + public void setupRtpSession(String host) throws SocketException + { + DatagramSocket rtcp; + + rtp = new DatagramSocket(RTP_PORT); + + rtp.setReceiveBufferSize(2097152); + System.out.println("RECV BUF: "+rtp.getReceiveBufferSize()); + System.out.println("SEND BUF: "+rtp.getSendBufferSize()); + + + rtcp = new DatagramSocket(RTCP_PORT); + + session = new RTPSession(rtp, rtcp); + session.addParticipant(new Participant(host, RTP_PORT, RTCP_PORT)); + } + + public void setupDecoders(Surface surface) + { + videoDecoder = MediaCodec.createDecoderByType("video/avc"); + MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720); + + videoDecoder.configure(videoFormat, surface, null, 0); + videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); + videoDecoder.start(); + videoDecoderInputBuffers = videoDecoder.getInputBuffers(); + } public void startVideoStream(final String host, final Surface surface) { @@ -55,185 +106,208 @@ public class NvVideoStream { @Override public void run() { + // Setup the decoder context + setupDecoders(surface); - byte[] firstFrame = new byte[98]; + // Open RTP sockets and start session try { - System.out.println("VID: Waiting for first frame"); - InputStream firstFrameStream = getFirstFrame(host); - - int offset = 0; - do - { - offset = firstFrameStream.read(firstFrame, offset, firstFrame.length-offset); - } while (offset != firstFrame.length); - System.out.println("VID: First frame read "); - } catch (UnknownHostException e2) { - // TODO Auto-generated catch block - e2.printStackTrace(); - return; - } catch (IOException e2) { - // TODO Auto-generated catch block - e2.printStackTrace(); - return; - } - - final DatagramSocket rtp, rtcp; - try { - rtp = new DatagramSocket(RTP_PORT); - rtcp = new DatagramSocket(RTCP_PORT); + setupRtpSession(host); } catch (SocketException e1) { - // TODO Auto-generated catch block e1.printStackTrace(); return; } - - videoDecoder = MediaCodec.createDecoderByType("video/avc"); - MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720); - - videoDecoder.configure(videoFormat, surface, null, 0); - videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); - videoDecoder.start(); - videoDecoderInputBuffers = videoDecoder.getInputBuffers(); - final RTPSession session = new RTPSession(rtp, rtcp); - session.addParticipant(new Participant(host, RTP_PORT, RTCP_PORT)); + // Start the receive thread early to avoid missing + // early packets + startReceiveThread(); - // Ping thread - new Thread(new Runnable() { - @Override - public void run() { - // PING in ASCII - final byte[] pingPacket = new byte[] {0x50, 0x49, 0x4E, 0x47}; - - // RTP payload type is 127 (dynamic) - session.payloadType(127); - - // Send PING every 100 ms - for (;;) - { - session.sendData(pingPacket); - - try { - Thread.sleep(100); - } catch (InterruptedException e) { - break; - } - } - } - }).start(); + // Start the keepalive ping to keep the stream going + startUdpPingThread(); - // Decoder thread - new Thread(new Runnable() { - @Override - public void run() { - // Read the decode units generated from the RTP stream - for (;;) - { - AvDecodeUnit du; - try { - du = parser.getNextDecodeUnit(); - } catch (InterruptedException e) { - e.printStackTrace(); - return; - } - - switch (du.getType()) - { - case AvDecodeUnit.TYPE_H264: - { - int inputIndex = videoDecoder.dequeueInputBuffer(-1); - if (inputIndex >= 0) - { - ByteBuffer buf = videoDecoderInputBuffers[inputIndex]; - - // Clear old input data - buf.clear(); - - // Copy data from our buffer list into the input buffer - for (AvBufferDescriptor desc : du.getBufferList()) - { - buf.put(desc.data, desc.offset, desc.length); - - // Release the buffer back to the buffer pool - pool.free(desc.data); - } - - videoDecoder.queueInputBuffer(inputIndex, - 0, du.getDataLength(), - 0, 0); - } - } - break; - - default: - { - System.out.println("Unknown decode unit type"); - } - break; - } - } - } - }).start(); + // Start the depacketizer thread to deal with the RTP data + startDepacketizerThread(); - // Receive thread - new Thread(new Runnable() { - - @Override - public void run() { - DatagramPacket packet = new DatagramPacket(pool.allocate(), 1500); - AvBufferDescriptor desc = new AvBufferDescriptor(null, 0, 0); - - for (;;) - { - try { - rtp.receive(packet); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return; - } - - desc.length = packet.getLength(); - desc.offset = packet.getOffset(); - desc.data = packet.getData(); - - // Skip the RTP header - desc.offset += 12; - desc.length -= 12; - - // !!! We no longer own the data buffer at this point !!! - parser.addInputData(new AvPacket(desc)); - - // Get a new buffer from the buffer pool - packet.setData(pool.allocate(), 0, 1500); - } - } - }).start(); + // Start decoding the data we're receiving + startDecoderThread(); + // Read the first frame to start the UDP video stream + try { + readFirstFrame(host); + } catch (IOException e2) { + e2.printStackTrace(); + return; + } + + // Render the frames that are coming out of the decoder + outputDisplayLoop(); + } + }).start(); + } + + private void startDecoderThread() + { + // Decoder thread + new Thread(new Runnable() { + @Override + public void run() { + // Read the decode units generated from the RTP stream for (;;) { - BufferInfo info = new BufferInfo(); - int outIndex = videoDecoder.dequeueOutputBuffer(info, -1); - switch (outIndex) { - case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: - System.out.println("Output buffers changed"); - break; - case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: - System.out.println("Output format changed"); - System.out.println("New output Format: " + videoDecoder.getOutputFormat()); - break; - case MediaCodec.INFO_TRY_AGAIN_LATER: - System.out.println("Try again later"); - break; - default: - break; - } - if (outIndex >= 0) { - videoDecoder.releaseOutputBuffer(outIndex, true); - } - + AvDecodeUnit du; + try { + du = depacketizer.getNextDecodeUnit(); + } catch (InterruptedException e) { + e.printStackTrace(); + return; + } + + switch (du.getType()) + { + case AvDecodeUnit.TYPE_H264: + { + int inputIndex = videoDecoder.dequeueInputBuffer(-1); + if (inputIndex >= 0) + { + ByteBuffer buf = videoDecoderInputBuffers[inputIndex]; + + // Clear old input data + buf.clear(); + + // Copy data from our buffer list into the input buffer + for (AvBufferDescriptor desc : du.getBufferList()) + { + buf.put(desc.data, desc.offset, desc.length); + + // Release the buffer back to the buffer pool + pool.free(desc.data); + } + + videoDecoder.queueInputBuffer(inputIndex, + 0, du.getDataLength(), + 0, du.getFlags()); + } + } + break; + + default: + { + System.out.println("Unknown decode unit type"); + } + break; + } } } }).start(); } + + private void startDepacketizerThread() + { + // This thread lessens the work on the receive thread + // so it can spend more time waiting for data + new Thread(new Runnable() { + @Override + public void run() { + for (;;) + { + AvRtpPacket packet; + + try { + packet = packets.take(); + } catch (InterruptedException e) { + e.printStackTrace(); + return; + } + + // !!! We no longer own the data buffer at this point !!! + depacketizer.addInputData(packet); + } + } + }).start(); + } + + private void startReceiveThread() + { + // Receive thread + new Thread(new Runnable() { + @Override + public void run() { + DatagramPacket packet = new DatagramPacket(pool.allocate(), 1500); + AvBufferDescriptor desc = new AvBufferDescriptor(null, 0, 0); + + for (;;) + { + try { + rtp.receive(packet); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + desc.length = packet.getLength(); + desc.offset = packet.getOffset(); + desc.data = packet.getData(); + + // Give the packet to the depacketizer thread + packets.add(new AvRtpPacket(desc)); + + // Get a new buffer from the buffer pool + packet.setData(pool.allocate(), 0, 1500); + } + } + }).start(); + } + + private void startUdpPingThread() + { + // Ping thread + new Thread(new Runnable() { + @Override + public void run() { + // PING in ASCII + final byte[] pingPacket = new byte[] {0x50, 0x49, 0x4E, 0x47}; + + // RTP payload type is 127 (dynamic) + session.payloadType(127); + + // Send PING every 100 ms + for (;;) + { + session.sendData(pingPacket); + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + break; + } + } + } + }).start(); + } + + private void outputDisplayLoop() + { + for (;;) + { + BufferInfo info = new BufferInfo(); + int outIndex = videoDecoder.dequeueOutputBuffer(info, -1); + switch (outIndex) { + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + System.out.println("Output buffers changed"); + break; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + System.out.println("Output format changed"); + System.out.println("New output Format: " + videoDecoder.getOutputFormat()); + break; + case MediaCodec.INFO_TRY_AGAIN_LATER: + System.out.println("Try again later"); + break; + default: + break; + } + if (outIndex >= 0) { + videoDecoder.releaseOutputBuffer(outIndex, true); + } + + } + } } diff --git a/src/com/limelight/nvstream/av/AvDecodeUnit.java b/src/com/limelight/nvstream/av/AvDecodeUnit.java index 693503dc..ef3d8552 100644 --- a/src/com/limelight/nvstream/av/AvDecodeUnit.java +++ b/src/com/limelight/nvstream/av/AvDecodeUnit.java @@ -9,12 +9,14 @@ public class AvDecodeUnit { private int type; private List bufferList; private int dataLength; + private int flags; - public AvDecodeUnit(int type, List bufferList, int dataLength) + public AvDecodeUnit(int type, List bufferList, int dataLength, int flags) { this.type = type; this.bufferList = bufferList; this.dataLength = dataLength; + this.flags = flags; } public int getType() @@ -22,6 +24,11 @@ public class AvDecodeUnit { return type; } + public int getFlags() + { + return flags; + } + public List getBufferList() { return bufferList; diff --git a/src/com/limelight/nvstream/av/AvDepacketizer.java b/src/com/limelight/nvstream/av/AvDepacketizer.java new file mode 100644 index 00000000..06e2390c --- /dev/null +++ b/src/com/limelight/nvstream/av/AvDepacketizer.java @@ -0,0 +1,274 @@ +package com.limelight.nvstream.av; + +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; + +import android.media.MediaCodec; + +public class AvDepacketizer { + + // Current NAL state + private LinkedList nalDataChain = null; + private int nalDataLength = 0; + + // Sequencing state + private short lastSequenceNumber; + + private LinkedBlockingQueue decodedUnits = new LinkedBlockingQueue(); + + private void reassembleNal() + { + // This is the start of a new NAL + if (nalDataChain != null && nalDataLength != 0) + { + int flags = 0; + + // Check if this is a special NAL unit + AvBufferDescriptor header = nalDataChain.getFirst(); + AvBufferDescriptor specialSeq = H264NAL.getSpecialSequenceDescriptor(header); + + if (specialSeq != null) + { + // The next byte after the special sequence is the NAL header + byte nalHeader = specialSeq.data[specialSeq.offset+specialSeq.length]; + + switch (nalHeader) + { + // SPS and PPS + case 0x67: + case 0x68: + System.out.println("Codec config"); + flags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG; + break; + + // IDR + case 0x65: + System.out.println("Reference frame"); + flags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME; + break; + + // non-IDR frame + case 0x61: + break; + + // Unknown type + default: + System.out.printf("Unknown NAL header: %02x %02x %02x %02x %02x\n", + header.data[header.offset], header.data[header.offset+1], + header.data[header.offset+2], header.data[header.offset+3], + header.data[header.offset+4]); + break; + } + } + else + { + System.out.printf("Invalid NAL: %02x %02x %02x %02x %02x\n", + header.data[header.offset], header.data[header.offset+1], + header.data[header.offset+2], header.data[header.offset+3], + header.data[header.offset+4]); + } + + // Construct the H264 decode unit + AvDecodeUnit du = new AvDecodeUnit(AvDecodeUnit.TYPE_H264, nalDataChain, nalDataLength, flags); + decodedUnits.add(du); + + // Clear old state + nalDataChain = null; + nalDataLength = 0; + } + } + + public void addInputData(AvPacket packet) + { + AvBufferDescriptor location = packet.getNewPayloadDescriptor(); + int payloadLength = location.length; + boolean terminateNal = false; + + while (location.length != 0) + { + // Remember the start of the NAL data in this packet + int start = location.offset; + + // Check for the start sequence + AvBufferDescriptor specialSeq = H264NAL.getSpecialSequenceDescriptor(location); + if (specialSeq != null && H264NAL.isStartSequence(specialSeq)) + { + // Reassemble any pending NAL + reassembleNal(); + + // Setup state for the new NAL + nalDataChain = new LinkedList(); + nalDataLength = 0; + + // Skip the start sequence + location.length -= specialSeq.length; + location.offset += specialSeq.length; + } + + // If there's a NAL assembly in progress, add the current data + if (nalDataChain != null) + { + // FIXME: This is a hack to make parsing full packets + // take less time. We assume if they don't start with + // a NAL start sequence, they're full of NAL data + if (payloadLength == 968) + { + location.offset += location.length; + location.length = 0; + } + else + { + //System.out.println("Using slow parsing case"); + while (location.length != 0) + { + specialSeq = H264NAL.getSpecialSequenceDescriptor(location); + + // Check if this should end the current NAL + //if (specialSeq != null) + if (specialSeq != null && H264NAL.isStartSequence(specialSeq)) + { + //terminateNal = true; + break; + } + else + { + // This byte is part of the NAL data + location.offset++; + location.length--; + } + } + } + + int endSub; + + // If parsing was finished due to reaching new start sequence, + // remove the last byte from the NAL (since it's the first byte of the + // start of the next one) + if (location.length != 0) + { + endSub = 1; + } + else + { + endSub = 0; + } + + // Add a buffer descriptor describing the NAL data in this packet + nalDataChain.add(new AvBufferDescriptor(location.data, start, location.offset-start-endSub)); + nalDataLength += location.offset-start-endSub; + + // Terminate the NAL if asked + if (terminateNal) + { + reassembleNal(); + } + } + else + { + // Otherwise, skip the data + location.offset++; + location.length--; + } + } + } + + public void addInputData(AvRtpPacket packet) + { + short seq = packet.getSequenceNumber(); + + // Toss out the current NAL if we receive a packet that is + // out of sequence + if (lastSequenceNumber != 0 && + lastSequenceNumber + 1 != seq) + { + System.out.println("Received OOS data (expected "+(lastSequenceNumber + 1)+", got "+seq+")"); + nalDataChain = null; + nalDataLength = 0; + } + + lastSequenceNumber = seq; + + // Pass the payload to the non-sequencing parser + AvBufferDescriptor rtpPayload = packet.getNewPayloadDescriptor(); + addInputData(new AvPacket(rtpPayload)); + } + + public AvDecodeUnit getNextDecodeUnit() throws InterruptedException + { + return decodedUnits.take(); + } +} + +class H264NAL { + + // This assume's that the buffer passed in is already a special sequence + public static boolean isStartSequence(AvBufferDescriptor specialSeq) + { + if (/*specialSeq.length != 3 && */specialSeq.length != 4) + return false; + + // The start sequence is 00 00 01 or 00 00 00 01 + return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x01); + } + + // Returns a buffer descriptor describing the start sequence + public static AvBufferDescriptor getSpecialSequenceDescriptor(AvBufferDescriptor buffer) + { + // NAL start sequence is 00 00 00 01 or 00 00 01 + if (buffer.length < 3) + return null; + + // 00 00 is magic + if (buffer.data[buffer.offset] == 0x00 && + buffer.data[buffer.offset+1] == 0x00) + { + // Another 00 could be the end of the special sequence + // 00 00 00 or the middle of 00 00 00 01 + if (buffer.data[buffer.offset+2] == 0x00) + { + if (buffer.length >= 4 && + buffer.data[buffer.offset+3] == 0x01) + { + // It's the AVC start sequence 00 00 00 01 + return new AvBufferDescriptor(buffer.data, buffer.offset, 4); + } + else + { + // It's 00 00 00 + return new AvBufferDescriptor(buffer.data, buffer.offset, 3); + } + } + else if (buffer.data[buffer.offset+2] == 0x01 || + buffer.data[buffer.offset+2] == 0x02) + { + // These are easy: 00 00 01 or 00 00 02 + return new AvBufferDescriptor(buffer.data, buffer.offset, 3); + } + else if (buffer.data[buffer.offset+2] == 0x03) + { + // 00 00 03 is special because it's a subsequence of the + // NAL wrapping substitute for 00 00 00, 00 00 01, 00 00 02, + // or 00 00 03 in the RBSP sequence. We need to check the next + // byte to see whether it's 00, 01, 02, or 03 (a valid RBSP substitution) + // or whether it's something else + + if (buffer.length < 4) + return null; + + if (buffer.data[buffer.offset+3] >= 0x00 && + buffer.data[buffer.offset+3] <= 0x03) + { + // It's not really a special sequence after all + return null; + } + else + { + // It's not a standard replacement so it's a special sequence + return new AvBufferDescriptor(buffer.data, buffer.offset, 3); + } + } + } + + return null; + } +} diff --git a/src/com/limelight/nvstream/av/AvParser.java b/src/com/limelight/nvstream/av/AvParser.java deleted file mode 100644 index 765c1100..00000000 --- a/src/com/limelight/nvstream/av/AvParser.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.limelight.nvstream.av; - -import java.util.LinkedList; -import java.util.concurrent.LinkedBlockingQueue; - -public class AvParser { - - // Current NAL state - private LinkedList nalDataChain; - private int nalDataLength; - - private LinkedBlockingQueue decodedUnits = new LinkedBlockingQueue(); - - private void reassembleNal() - { - // This is the start of a new NAL - if (nalDataChain != null && nalDataLength != 0) - { - // Construct the H264 decode unit - AvDecodeUnit du = new AvDecodeUnit(AvDecodeUnit.TYPE_H264, nalDataChain, nalDataLength); - decodedUnits.add(du); - - // Clear old state - nalDataChain = null; - nalDataLength = 0; - } - } - - public void addInputData(AvPacket packet) - { - // This payload buffer descriptor belongs to us - AvBufferDescriptor location = packet.getNewPayloadDescriptor(); - int payloadLength = location.length; - - while (location.length != 0) - { - // Remember the start of the NAL data in this packet - int start = location.offset; - - // Check for the start sequence - if (H264NAL.hasStartSequence(location)) - { - // Reassemble any pending NAL - reassembleNal(); - - // Setup state for the new NAL - nalDataChain = new LinkedList(); - nalDataLength = 0; - - // Skip the start sequence - location.length -= 4; - location.offset += 4; - } - - // If there's a NAL assembly in progress, add the current data - if (nalDataChain != null) - { - // FIXME: This is a hack to make parsing full packets - // take less time. We assume if they don't start with - // a NAL start sequence, they're full of NAL data - if (payloadLength == 968) - { - location.offset += location.length; - location.length = 0; - } - else - { - System.out.println("Using slow parsing case"); - while (location.length != 0) - { - // Check if this should end the current NAL - if (H264NAL.hasStartSequence(location)) - { - break; - } - else - { - // This byte is part of the NAL data - location.offset++; - location.length--; - } - } - } - - // Add a buffer descriptor describing the NAL data in this packet - nalDataChain.add(new AvBufferDescriptor(location.data, start, location.offset-start)); - nalDataLength += location.offset-start; - } - else - { - // Otherwise, skip the data - location.offset++; - location.length--; - } - } - } - - public AvDecodeUnit getNextDecodeUnit() throws InterruptedException - { - return decodedUnits.take(); - } -} - -class H264NAL { - public static boolean shouldTerminateNal(AvBufferDescriptor buffer) - { - if (buffer.length < 4) - return false; - - if (buffer.data[buffer.offset] != 0x00 || - buffer.data[buffer.offset+1] != 0x00 || - buffer.data[buffer.offset+2] != 0x00) - { - return false; - } - - return true; - } - - public static boolean hasStartSequence(AvBufferDescriptor buffer) - { - // NAL start sequence is 00 00 00 01 - if (!shouldTerminateNal(buffer)) - return false; - - if (buffer.data[buffer.offset+3] != 0x01) - return false; - - return true; - } -} diff --git a/src/com/limelight/nvstream/av/AvRtpPacket.java b/src/com/limelight/nvstream/av/AvRtpPacket.java new file mode 100644 index 00000000..e9f8bfd0 --- /dev/null +++ b/src/com/limelight/nvstream/av/AvRtpPacket.java @@ -0,0 +1,32 @@ +package com.limelight.nvstream.av; + +import java.nio.ByteBuffer; + +public class AvRtpPacket { + + private short seqNum; + private AvBufferDescriptor buffer; + + public AvRtpPacket(AvBufferDescriptor buffer) + { + this.buffer = new AvBufferDescriptor(buffer); + + ByteBuffer bb = ByteBuffer.wrap(buffer.data, buffer.offset, buffer.length); + + // Discard the first couple of bytes + bb.getShort(); + + // Get the sequence number + seqNum = bb.getShort(); + } + + public short getSequenceNumber() + { + return seqNum; + } + + public AvBufferDescriptor getNewPayloadDescriptor() + { + return new AvBufferDescriptor(buffer.data, buffer.offset+12, buffer.length-12); + } +}