mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-01 23:35:28 +00:00
Rewrite video support so that it actually works. Add a janky NAL parser dissector.
This commit is contained in:
parent
1abdefdc15
commit
e6af9df142
@ -27,6 +27,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.limelight.Game"
|
android:name="com.limelight.Game"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
android:label="@string/title_activity_game"
|
android:label="@string/title_activity_game"
|
||||||
android:parentActivityName="com.limelight.Connection"
|
android:parentActivityName="com.limelight.Connection"
|
||||||
|
62
LuaScripts/NALParser.lua
Normal file
62
LuaScripts/NALParser.lua
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
-- H264 NAL Parser
|
||||||
|
-- Version: 1.0
|
||||||
|
-- Cameron Gutman
|
||||||
|
|
||||||
|
-- NAL header
|
||||||
|
local nal_start = ProtoField.bytes("nal.start", "H264 NAL Start Sequence") -- 4 Byte Start
|
||||||
|
local nal_type = ProtoField.uint8("nal.type", "H264 NAL Type") -- 1 byte NAL type
|
||||||
|
local nal_data = ProtoField.bytes("nal.data", "H264 NAL Data") -- variable byte NAL data
|
||||||
|
|
||||||
|
p_h264raw = Proto("h264raw", "H264 Raw NAL Parser")
|
||||||
|
p_h264raw.fields = {
|
||||||
|
nal_start,
|
||||||
|
nal_type,
|
||||||
|
nal_data
|
||||||
|
}
|
||||||
|
|
||||||
|
function p_h264raw.dissector(buf, pkt, root)
|
||||||
|
pkt.cols.protocol = p_h264raw.name
|
||||||
|
subtree = root:add(p_h264raw, buf(0))
|
||||||
|
|
||||||
|
local i = 0
|
||||||
|
local data_start = -1
|
||||||
|
while i < buf:len do
|
||||||
|
-- Make sure we have a potential start sequence and type
|
||||||
|
if buf:len() - i < 5 then
|
||||||
|
-- We need more data
|
||||||
|
pkt.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
|
||||||
|
pkt.desegment_offset = 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for start sequence
|
||||||
|
start = buf(i, 4):uint()
|
||||||
|
if start == 1 then
|
||||||
|
if data_start ~= -1 then
|
||||||
|
-- End the last NAL
|
||||||
|
subtree:add(nal_data, buf(data_start, i-data_start))
|
||||||
|
end
|
||||||
|
-- This is the start of a NAL
|
||||||
|
subtree:add(nal_start, buf(i, 4))
|
||||||
|
i = i + 4
|
||||||
|
-- Next byte is NAL type
|
||||||
|
subtree:add(nal_type, buf(i, 1), buf(i, 1):uint8())
|
||||||
|
i = i + 1
|
||||||
|
-- Data begins here
|
||||||
|
data_start = i
|
||||||
|
else
|
||||||
|
-- This must be a data byte
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function p_h264raw.init()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local udp_dissector_table = DissectorTable.get("rtp.pt")
|
||||||
|
udp_dissector_table:add(96, p_h264raw)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,19 +21,9 @@ public class Connection extends Activity {
|
|||||||
private TextView hostText;
|
private TextView hostText;
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
private static final String DEFAULT_HOST = "35.0.113.120";
|
private static final String DEFAULT_HOST = "192.168.1.240";
|
||||||
public static final String HOST_KEY = "hostText";
|
public static final String HOST_KEY = "hostText";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
@ -8,12 +8,18 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import com.limelight.nvstream.av.AvBufferDescriptor;
|
||||||
|
import com.limelight.nvstream.av.AvDecodeUnit;
|
||||||
|
import com.limelight.nvstream.av.AvPacket;
|
||||||
|
import com.limelight.nvstream.av.AvParser;
|
||||||
|
|
||||||
import jlibrtp.DataFrame;
|
import jlibrtp.DataFrame;
|
||||||
import jlibrtp.Participant;
|
import jlibrtp.Participant;
|
||||||
import jlibrtp.RTPAppIntf;
|
import jlibrtp.RTPAppIntf;
|
||||||
@ -24,16 +30,16 @@ import android.media.MediaCodec.BufferInfo;
|
|||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
public class NvVideoStream implements RTPAppIntf {
|
public class NvVideoStream {
|
||||||
public static final int RTP_PORT = 47998;
|
public static final int RTP_PORT = 47998;
|
||||||
public static final int RTCP_PORT = 47999;
|
public static final int RTCP_PORT = 47999;
|
||||||
public static final int FIRST_FRAME_PORT = 47996;
|
public static final int FIRST_FRAME_PORT = 47996;
|
||||||
|
|
||||||
private static final int FRAME_RATE = 60;
|
private static final int FRAME_RATE = 60;
|
||||||
private ByteBuffer[] decoderInputBuffers = null;
|
private ByteBuffer[] videoDecoderInputBuffers = null;
|
||||||
private MediaCodec decoder;
|
private MediaCodec videoDecoder;
|
||||||
|
|
||||||
private int frameIndex = 0;
|
private AvParser parser = new AvParser();
|
||||||
|
|
||||||
private InputStream getFirstFrame(String host) throws UnknownHostException, IOException
|
private InputStream getFirstFrame(String host) throws UnknownHostException, IOException
|
||||||
{
|
{
|
||||||
@ -79,31 +85,16 @@ public class NvVideoStream implements RTPAppIntf {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder = MediaCodec.createDecoderByType("video/avc");
|
videoDecoder = MediaCodec.createDecoderByType("video/avc");
|
||||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720);
|
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720);
|
||||||
|
|
||||||
decoder.configure(mediaFormat, surface, null, 0);
|
videoDecoder.configure(videoFormat, surface, null, 0);
|
||||||
decoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||||
decoder.start();
|
videoDecoder.start();
|
||||||
decoderInputBuffers = decoder.getInputBuffers();
|
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
|
||||||
|
|
||||||
int inputIndex = decoder.dequeueInputBuffer(-1);
|
|
||||||
if (inputIndex >= 0)
|
|
||||||
{
|
|
||||||
ByteBuffer buf = decoderInputBuffers[inputIndex];
|
|
||||||
|
|
||||||
buf.clear();
|
|
||||||
buf.put(firstFrame);
|
|
||||||
|
|
||||||
decoder.queueInputBuffer(inputIndex,
|
|
||||||
0, firstFrame.length,
|
|
||||||
0, 0);
|
|
||||||
frameIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
final RTPSession session = new RTPSession(rtp, rtcp);
|
final RTPSession session = new RTPSession(rtp, rtcp);
|
||||||
session.addParticipant(new Participant(host, RTP_PORT, RTCP_PORT));
|
session.addParticipant(new Participant(host, RTP_PORT, RTCP_PORT));
|
||||||
//session.RTPSessionRegister(NvVideoStream.this, null, null);
|
|
||||||
|
|
||||||
// Ping thread
|
// Ping thread
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@ -129,64 +120,103 @@ public class NvVideoStream implements RTPAppIntf {
|
|||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
// Receive thread
|
// Decoder thread
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
byte[] packet = new byte[1500];
|
// Read the decode units generated from the RTP stream
|
||||||
|
|
||||||
// Send PING every 100 ms
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
DatagramPacket dp = new DatagramPacket(packet, 0, packet.length);
|
AvDecodeUnit du;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rtp.receive(dp);
|
du = parser.getNextDecodeUnit();
|
||||||
} catch (IOException e) {
|
} catch (InterruptedException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("in receiveData");
|
switch (du.getType())
|
||||||
int inputIndex = decoder.dequeueInputBuffer(-1);
|
|
||||||
if (inputIndex >= 0)
|
|
||||||
{
|
{
|
||||||
ByteBuffer buf = decoderInputBuffers[inputIndex];
|
case AvDecodeUnit.TYPE_H264:
|
||||||
NvVideoPacket nvVideo = new NvVideoPacket(dp.getData());
|
{
|
||||||
|
int inputIndex = videoDecoder.dequeueInputBuffer(-1);
|
||||||
|
if (inputIndex >= 0)
|
||||||
|
{
|
||||||
|
ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
|
||||||
|
|
||||||
buf.clear();
|
// Clear old input data
|
||||||
buf.put(nvVideo.data);
|
buf.clear();
|
||||||
|
|
||||||
System.out.println(nvVideo);
|
// Copy data from our buffer list into the input buffer
|
||||||
if (nvVideo.length == 0xc803) {
|
for (AvBufferDescriptor desc : du.getBufferList())
|
||||||
decoder.queueInputBuffer(inputIndex,
|
{
|
||||||
0, nvVideo.length,
|
buf.put(desc.data, desc.offset, desc.length);
|
||||||
0, 0);
|
}
|
||||||
frameIndex++;
|
|
||||||
} else {
|
videoDecoder.queueInputBuffer(inputIndex,
|
||||||
decoder.queueInputBuffer(inputIndex,
|
0, du.getDataLength(),
|
||||||
0, 0,
|
0, 0);
|
||||||
0, 0);
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
System.out.println("Unknown decode unit type");
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
|
// Receive thread
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
byte[] buffer = new byte[1500];
|
||||||
|
AvBufferDescriptor desc = new AvBufferDescriptor(null, 0, 0);
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Give the data to the AV parser
|
||||||
|
parser.addInputData(new AvPacket(desc));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).start();
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
BufferInfo info = new BufferInfo();
|
BufferInfo info = new BufferInfo();
|
||||||
System.out.println("dequeuing outputbuffer");
|
int outIndex = videoDecoder.dequeueOutputBuffer(info, -1);
|
||||||
int outIndex = decoder.dequeueOutputBuffer(info, -1);
|
|
||||||
System.out.println("done dequeuing output buffer");
|
|
||||||
switch (outIndex) {
|
switch (outIndex) {
|
||||||
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
||||||
System.out.println("Output buffers changed");
|
System.out.println("Output buffers changed");
|
||||||
break;
|
break;
|
||||||
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
||||||
System.out.println("Output format changed");
|
System.out.println("Output format changed");
|
||||||
//decoderOutputFormat = decoder.getOutputFormat();
|
System.out.println("New output Format: " + videoDecoder.getOutputFormat());
|
||||||
System.out.println("New output Format: " + decoder.getOutputFormat());
|
|
||||||
break;
|
break;
|
||||||
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
case MediaCodec.INFO_TRY_AGAIN_LATER:
|
||||||
System.out.println("Try again later");
|
System.out.println("Try again later");
|
||||||
@ -195,9 +225,7 @@ public class NvVideoStream implements RTPAppIntf {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (outIndex >= 0) {
|
if (outIndex >= 0) {
|
||||||
System.out.println("releasing output buffer");
|
videoDecoder.releaseOutputBuffer(outIndex, true);
|
||||||
decoder.releaseOutputBuffer(outIndex, true);
|
|
||||||
System.out.println("output buffer released");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -205,52 +233,10 @@ public class NvVideoStream implements RTPAppIntf {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receiveData(DataFrame frame, Participant participant) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEvent(int type, Participant[] participant) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int frameSize(int payloadType) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the presentation time for frame N, in microseconds.
|
* Generates the presentation time for frame N, in microseconds.
|
||||||
*/
|
*/
|
||||||
private static long computePresentationTime(int frameIndex) {
|
private static long computePresentationTime(int frameIndex) {
|
||||||
return 132 + frameIndex * 1000000 / FRAME_RATE;
|
return 132 + frameIndex * 1000000 / FRAME_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NvVideoPacket {
|
|
||||||
byte[] preamble;
|
|
||||||
short length;
|
|
||||||
byte[] extra;
|
|
||||||
byte[] data;
|
|
||||||
|
|
||||||
public NvVideoPacket(byte[] payload)
|
|
||||||
{
|
|
||||||
ByteBuffer bb = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
|
|
||||||
preamble = new byte[12+16];
|
|
||||||
extra = new byte[38];
|
|
||||||
|
|
||||||
bb.get(preamble);
|
|
||||||
length = bb.getShort();
|
|
||||||
bb.get(extra);
|
|
||||||
data = new byte[length];
|
|
||||||
|
|
||||||
if (bb.remaining() + length <= payload.length)
|
|
||||||
bb.get(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "";//String.format("Length: %d | %02x %02x %02x %02x %02x %02x %02x %02x",
|
|
||||||
//length, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
14
src/com/limelight/nvstream/av/AvBufferDescriptor.java
Normal file
14
src/com/limelight/nvstream/av/AvBufferDescriptor.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package com.limelight.nvstream.av;
|
||||||
|
|
||||||
|
public class AvBufferDescriptor {
|
||||||
|
public byte[] data;
|
||||||
|
public int offset;
|
||||||
|
public int length;
|
||||||
|
|
||||||
|
public AvBufferDescriptor(byte[] data, int offset, int length)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
this.offset = offset;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
}
|
34
src/com/limelight/nvstream/av/AvDecodeUnit.java
Normal file
34
src/com/limelight/nvstream/av/AvDecodeUnit.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package com.limelight.nvstream.av;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AvDecodeUnit {
|
||||||
|
public static final int TYPE_UNKNOWN = 0;
|
||||||
|
public static final int TYPE_H264 = 1;
|
||||||
|
|
||||||
|
private int type;
|
||||||
|
private List<AvBufferDescriptor> bufferList;
|
||||||
|
private int dataLength;
|
||||||
|
|
||||||
|
public AvDecodeUnit(int type, List<AvBufferDescriptor> bufferList, int dataLength)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
this.bufferList = bufferList;
|
||||||
|
this.dataLength = dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType()
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AvBufferDescriptor> getBufferList()
|
||||||
|
{
|
||||||
|
return bufferList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDataLength()
|
||||||
|
{
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
}
|
18
src/com/limelight/nvstream/av/AvPacket.java
Normal file
18
src/com/limelight/nvstream/av/AvPacket.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.limelight.nvstream.av;
|
||||||
|
|
||||||
|
public class AvPacket {
|
||||||
|
private AvBufferDescriptor buffer;
|
||||||
|
|
||||||
|
public AvPacket(AvBufferDescriptor rtpPayload)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[rtpPayload.length];
|
||||||
|
System.arraycopy(rtpPayload.data, rtpPayload.offset, data, 0, rtpPayload.length);
|
||||||
|
buffer = new AvBufferDescriptor(data, 0, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvBufferDescriptor getPayload()
|
||||||
|
{
|
||||||
|
int payloadOffset = buffer.offset+56;
|
||||||
|
return new AvBufferDescriptor(buffer.data, payloadOffset, buffer.length-payloadOffset);
|
||||||
|
}
|
||||||
|
}
|
131
src/com/limelight/nvstream/av/AvParser.java
Normal file
131
src/com/limelight/nvstream/av/AvParser.java
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package com.limelight.nvstream.av;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
|
||||||
|
public class AvParser {
|
||||||
|
|
||||||
|
// Current NAL state
|
||||||
|
private LinkedList<AvBufferDescriptor> nalDataChain;
|
||||||
|
private int nalDataLength;
|
||||||
|
|
||||||
|
private LinkedBlockingQueue<AvDecodeUnit> decodedUnits = new LinkedBlockingQueue<AvDecodeUnit>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
AvBufferDescriptor payload = packet.getPayload();
|
||||||
|
AvBufferDescriptor location = new AvBufferDescriptor(payload.data, payload.offset, payload.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<AvBufferDescriptor>();
|
||||||
|
nalDataLength = 0;
|
||||||
|
|
||||||
|
// Skip the start sequence and the type byte
|
||||||
|
location.length -= 5;
|
||||||
|
location.offset += 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (payload.length == 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user