Stage WIP audio support

This commit is contained in:
Cameron Gutman 2013-10-30 22:23:11 -04:00
parent 9e09ca2b7a
commit e067669044
3 changed files with 199 additions and 83 deletions

View File

@ -30,8 +30,8 @@ public class NvVideoStream {
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 ByteBuffer[] videoDecoderInputBuffers = null; private ByteBuffer[] videoDecoderInputBuffers, audioDecoderInputBuffers;
private MediaCodec videoDecoder; private MediaCodec videoDecoder, audioDecoder;
private LinkedBlockingQueue<AvRtpPacket> packets = new LinkedBlockingQueue<AvRtpPacket>(); private LinkedBlockingQueue<AvRtpPacket> packets = new LinkedBlockingQueue<AvRtpPacket>();
@ -94,10 +94,19 @@ public class NvVideoStream {
videoDecoder = MediaCodec.createDecoderByType("video/avc"); videoDecoder = MediaCodec.createDecoderByType("video/avc");
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720); MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", 1280, 720);
audioDecoder = MediaCodec.createDecoderByType("audio/mp4a-latm");
MediaFormat audioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 2);
videoDecoder.configure(videoFormat, surface, null, 0); videoDecoder.configure(videoFormat, surface, null, 0);
audioDecoder.configure(audioFormat, null, null, 0);
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT); videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
videoDecoder.start(); videoDecoder.start();
audioDecoder.start();
videoDecoderInputBuffers = videoDecoder.getInputBuffers(); videoDecoderInputBuffers = videoDecoder.getInputBuffers();
audioDecoderInputBuffers = audioDecoder.getInputBuffers();
} }
public void startVideoStream(final String host, final Surface surface) public void startVideoStream(final String host, final Surface surface)
@ -130,6 +139,9 @@ public class NvVideoStream {
// Start decoding the data we're receiving // Start decoding the data we're receiving
startDecoderThread(); startDecoderThread();
// Start playing back audio data
startAudioPlaybackThread();
// Read the first frame to start the UDP video stream // Read the first frame to start the UDP video stream
try { try {
readFirstFrame(host); readFirstFrame(host);
@ -188,6 +200,32 @@ public class NvVideoStream {
} }
} }
break; break;
case AvDecodeUnit.TYPE_AAC:
{
int inputIndex = audioDecoder.dequeueInputBuffer(0);
if (inputIndex == -4)
{
ByteBuffer buf = audioDecoderInputBuffers[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);
}
audioDecoder.queueInputBuffer(inputIndex,
0, du.getDataLength(),
0, du.getFlags());
}
}
break;
default: default:
{ {
@ -284,6 +322,40 @@ public class NvVideoStream {
}).start(); }).start();
} }
private void startAudioPlaybackThread()
{
new Thread(new Runnable() {
@Override
public void run() {
for (;;)
{
BufferInfo info = new BufferInfo();
System.out.println("Waiting for audio");
int outIndex = audioDecoder.dequeueOutputBuffer(info, -1);
System.out.println("Got audio");
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) {
audioDecoder.releaseOutputBuffer(outIndex, true);
}
}
}
}).start();
}
private void outputDisplayLoop() private void outputDisplayLoop()
{ {
for (;;) for (;;)

View File

@ -5,6 +5,7 @@ import java.util.List;
public class AvDecodeUnit { public class AvDecodeUnit {
public static final int TYPE_UNKNOWN = 0; public static final int TYPE_UNKNOWN = 0;
public static final int TYPE_H264 = 1; public static final int TYPE_H264 = 1;
public static final int TYPE_AAC = 2;
private int type; private int type;
private List<AvBufferDescriptor> bufferList; private List<AvBufferDescriptor> bufferList;

View File

@ -8,24 +8,49 @@ import android.media.MediaCodec;
public class AvDepacketizer { public class AvDepacketizer {
// Current NAL state // Current NAL state
private LinkedList<AvBufferDescriptor> nalDataChain = null; private LinkedList<AvBufferDescriptor> avcNalDataChain = null;
private int nalDataLength = 0; private int avcNalDataLength = 0;
private LinkedList<AvBufferDescriptor> aacNalDataChain = null;
private int aacNalDataLength = 0;
private int currentlyDecoding;
// Sequencing state // Sequencing state
private short lastSequenceNumber; private short lastSequenceNumber;
private LinkedBlockingQueue<AvDecodeUnit> decodedUnits = new LinkedBlockingQueue<AvDecodeUnit>(); private LinkedBlockingQueue<AvDecodeUnit> decodedUnits = new LinkedBlockingQueue<AvDecodeUnit>();
private void reassembleNal() private void reassembleAacNal()
{
// This is the start of a new AAC NAL
if (aacNalDataChain != null && aacNalDataLength != 0)
{
System.out.println("Assembling AAC NAL: "+aacNalDataLength);
/*AvBufferDescriptor header = aacNalDataChain.getFirst();
for (int i = 0; i < header.length; i++)
System.out.printf("%02x ", header.data[header.offset+i]);
System.out.println();*/
// Construct the AAC decode unit
AvDecodeUnit du = new AvDecodeUnit(AvDecodeUnit.TYPE_AAC, aacNalDataChain, aacNalDataLength, 0);
decodedUnits.add(du);
// Clear old state
aacNalDataChain = null;
aacNalDataLength = 0;
}
}
private void reassembleAvcNal()
{ {
// This is the start of a new NAL // This is the start of a new NAL
if (nalDataChain != null && nalDataLength != 0) if (avcNalDataChain != null && avcNalDataLength != 0)
{ {
int flags = 0; int flags = 0;
// Check if this is a special NAL unit // Check if this is a special NAL unit
AvBufferDescriptor header = nalDataChain.getFirst(); AvBufferDescriptor header = avcNalDataChain.getFirst();
AvBufferDescriptor specialSeq = H264NAL.getSpecialSequenceDescriptor(header); AvBufferDescriptor specialSeq = NAL.getSpecialSequenceDescriptor(header);
if (specialSeq != null) if (specialSeq != null)
{ {
@ -69,105 +94,98 @@ public class AvDepacketizer {
} }
// Construct the H264 decode unit // Construct the H264 decode unit
AvDecodeUnit du = new AvDecodeUnit(AvDecodeUnit.TYPE_H264, nalDataChain, nalDataLength, flags); AvDecodeUnit du = new AvDecodeUnit(AvDecodeUnit.TYPE_H264, avcNalDataChain, avcNalDataLength, flags);
decodedUnits.add(du); decodedUnits.add(du);
// Clear old state // Clear old state
nalDataChain = null; avcNalDataChain = null;
nalDataLength = 0; avcNalDataLength = 0;
} }
} }
public void addInputData(AvPacket packet) public void addInputData(AvPacket packet)
{ {
AvBufferDescriptor location = packet.getNewPayloadDescriptor(); AvBufferDescriptor location = packet.getNewPayloadDescriptor();
int payloadLength = location.length;
boolean terminateNal = false;
while (location.length != 0) while (location.length != 0)
{ {
// Remember the start of the NAL data in this packet // Remember the start of the NAL data in this packet
int start = location.offset; int start = location.offset;
// Check for the start sequence // Check for a special sequence
AvBufferDescriptor specialSeq = H264NAL.getSpecialSequenceDescriptor(location); AvBufferDescriptor specialSeq = NAL.getSpecialSequenceDescriptor(location);
if (specialSeq != null && H264NAL.isStartSequence(specialSeq)) if (specialSeq != null)
{ {
// Reassemble any pending NAL if (NAL.isAvcStartSequence(specialSeq))
reassembleNal(); {
// We're decoding H264 now
// Setup state for the new NAL currentlyDecoding = AvDecodeUnit.TYPE_H264;
nalDataChain = new LinkedList<AvBufferDescriptor>();
nalDataLength = 0; // Check if it's the end of the last frame
if (NAL.isAvcFrameStart(specialSeq))
{
// Reassemble any pending AVC NAL
reassembleAvcNal();
// Setup state for the new NAL
avcNalDataChain = new LinkedList<AvBufferDescriptor>();
avcNalDataLength = 0;
}
}
else if (NAL.isAacStartSequence(specialSeq))
{
// We're decoding AAC now
currentlyDecoding = AvDecodeUnit.TYPE_AAC;
// Reassemble any pending AAC NAL
reassembleAacNal();
// Setup state for the new NAL
aacNalDataChain = new LinkedList<AvBufferDescriptor>();
aacNalDataLength = 0;
}
else
{
// Not either sequence we want
//currentlyDecoding = AvDecodeUnit.TYPE_UNKNOWN;
}
// Skip the start sequence // Skip the start sequence
location.length -= specialSeq.length; location.length -= specialSeq.length;
location.offset += specialSeq.length; location.offset += specialSeq.length;
} }
// If there's a NAL assembly in progress, add the current data // Move to the next special sequence
if (nalDataChain != null) while (location.length != 0)
{ {
// FIXME: This is a hack to make parsing full packets specialSeq = NAL.getSpecialSequenceDescriptor(location);
// take less time. We assume if they don't start with
// a NAL start sequence, they're full of NAL data // Check if this should end the current NAL
if (payloadLength == 968) if (specialSeq != null)
{ {
location.offset += location.length; break;
location.length = 0;
} }
else else
{ {
//System.out.println("Using slow parsing case"); // This byte is part of the NAL data
while (location.length != 0) location.offset++;
{ location.length--;
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
AvBufferDescriptor data = new AvBufferDescriptor(location.data, start, location.offset-start);
if (currentlyDecoding == AvDecodeUnit.TYPE_H264 && avcNalDataChain != null)
{ {
// Otherwise, skip the data // Add a buffer descriptor describing the NAL data in this packet
location.offset++; avcNalDataChain.add(data);
location.length--; avcNalDataLength += location.offset-start;
}
else if (currentlyDecoding == AvDecodeUnit.TYPE_AAC && aacNalDataChain != null)
{
// Add a buffer descriptor describing the NAL data in this packet
aacNalDataChain.add(data);
aacNalDataLength += location.offset-start;
} }
} }
} }
@ -182,8 +200,13 @@ public class AvDepacketizer {
lastSequenceNumber + 1 != seq) lastSequenceNumber + 1 != seq)
{ {
System.out.println("Received OOS data (expected "+(lastSequenceNumber + 1)+", got "+seq+")"); System.out.println("Received OOS data (expected "+(lastSequenceNumber + 1)+", got "+seq+")");
nalDataChain = null;
nalDataLength = 0; // Reset the depacketizer state
currentlyDecoding = AvDecodeUnit.TYPE_UNKNOWN;
avcNalDataChain = null;
avcNalDataLength = 0;
aacNalDataChain = null;
aacNalDataLength = 0;
} }
lastSequenceNumber = seq; lastSequenceNumber = seq;
@ -199,18 +222,38 @@ public class AvDepacketizer {
} }
} }
class H264NAL { class NAL {
// This assume's 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 isStartSequence(AvBufferDescriptor specialSeq) public static boolean isAvcStartSequence(AvBufferDescriptor specialSeq)
{ {
if (/*specialSeq.length != 3 && */specialSeq.length != 4) if (specialSeq.length != 3 && specialSeq.length != 4)
return false; return false;
// The start sequence is 00 00 01 or 00 00 00 01 // The start sequence is 00 00 01 or 00 00 00 01
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 isAacStartSequence(AvBufferDescriptor specialSeq)
{
if (specialSeq.length != 3)
return false;
// The start sequence is 00 00 03
return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x03);
}
// This assumes that the buffer passed in is already a special sequence
public static boolean isAvcFrameStart(AvBufferDescriptor specialSeq)
{
if (specialSeq.length != 4)
return false;
// The frame start sequence is 00 00 00 01
return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x01);
}
// Returns a buffer descriptor describing the start sequence // Returns a buffer descriptor describing the start sequence
public static AvBufferDescriptor getSpecialSequenceDescriptor(AvBufferDescriptor buffer) public static AvBufferDescriptor getSpecialSequenceDescriptor(AvBufferDescriptor buffer)
{ {