📄 depacketizer.java
字号:
package net.sf.fmj.media.codec.video.jpeg;import java.awt.Dimension;import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Map.Entry;import javax.media.Buffer;import javax.media.Codec;import javax.media.Format;import javax.media.ResourceUnavailableException;import javax.media.format.JPEGFormat;import javax.media.format.VideoFormat;import net.sf.fmj.codegen.MediaCGUtils;import net.sf.fmj.media.AbstractCodec;import net.sf.fmj.utility.StringUtils;/** * FMJ's functional equivalent of com.sun.media.codec.video.jpeg.DePacketizer. * Reassembles JPEG RTP packets into JPEG frames, as per * RFC 2035 - RTP Payload Format for JPEG Video. See http://www.rfc-archive.org/getrfc.php?rfc=2035 * TODO: support restart markers * TODO: support q table headers * TODO: lunarphases.mov: when received, JMF puts extra stuff on the end that we don't. * * @author Ken Larson * */public class DePacketizer extends AbstractCodec implements Codec{ private static final boolean COMPARE_WITH_BASELINE = false; private static final boolean TRACE = false; private static final boolean EXIT_AFTER_ONE_FRAME = false; // for testing only. private static final int MAX_ACTIVE_FRAME_ASSEMBLERS = 3; private final Format[] supportedInputFormats = new Format[] { new VideoFormat(VideoFormat.JPEG_RTP, null, -1, Format.byteArray, -1.0f), }; private final Format[] supportedOutputFormats = new Format[] { new VideoFormat(VideoFormat.JPEG, null, -1, Format.byteArray, -1.0f), }; private Codec baselineCodec; // when debugging/testing, we can set this to an instance of com.sun.media.codec.video.jpeg.DePacketizer and compare the results. /** * Because packets can come out of order, it is possible that some packets for a newer frame * may arrive while an older frame is still incomplete. However, in the case where we get nothing * but incomplete frames, we don't want to keep all of them around forever. */ public DePacketizer() { if (COMPARE_WITH_BASELINE) { try { baselineCodec = (Codec) Class.forName("com.sun.media.codec.video.jpeg.DePacketizer").newInstance(); } catch (Exception e) { System.out.println("Unable to instantiate com.sun.media.codec.video.jpeg.DePacketizer"); // will happen if JMF not in classpath. } } } public void close() { if (baselineCodec != null) baselineCodec.close(); super.close(); frameAssemblers.clear(); } public Object getControl(String controlType) { if (baselineCodec != null) { return baselineCodec.getControl(controlType); } else { return super.getControl(controlType); } } public Object[] getControls() { if (baselineCodec != null) return baselineCodec.getControls(); else return super.getControls(); } public String getName() { return "JPEG DePacketizer"; } public Format[] getSupportedInputFormats() { return supportedInputFormats; } public Format[] getSupportedOutputFormats(Format input) { if (input == null) return supportedOutputFormats; VideoFormat inputCast = (VideoFormat) input; final Dimension HARD_CODED_SIZE = new java.awt.Dimension(320, 240); final Format[] result = new Format[] { new JPEGFormat(HARD_CODED_SIZE, -1, Format.byteArray, -1.0f, -1, -1)}; if (baselineCodec != null) { final Format[] baselineResult = baselineCodec.getSupportedOutputFormats(input); System.out.println("input: " + MediaCGUtils.formatToStr(input)); for (int i = 0; i < baselineResult.length; ++i) System.out.println("output: " + MediaCGUtils.formatToStr(baselineResult[0])); } // TODO: JMF returns a format with dimensions of 320x200 - not sure where this comes from, // seems like it might be hard-coded. We'll do the same, otherwise we can get exceptions // downstream. return result; } public void open() throws ResourceUnavailableException { if (baselineCodec != null) baselineCodec.open(); super.open(); } public void reset() { if (baselineCodec != null) baselineCodec.reset(); super.reset(); frameAssemblers.clear(); } public Format setInputFormat(Format format) { if (baselineCodec != null) { super.setInputFormat(format); return baselineCodec.setInputFormat(format); } else { return super.setInputFormat(format); } } public Format setOutputFormat(Format format) { if (baselineCodec != null) { super.setOutputFormat(format); return baselineCodec.setOutputFormat(format); } else { return super.setOutputFormat(format); } } public int process(Buffer input, Buffer output) { /* * Flags to look out for: * public static final int FLAG_RTP_MARKER = 2048; public static final int FLAG_RTP_TIME = 4096; // TODO: what does this mean? */ if (!input.isDiscard()) { if (baselineCodec != null) { final int baselineResult = baselineCodec.process(input, output); //if (TRACE) System.out.println("result=" + baselineResult + " output.getFlags()=" + Integer.toHexString(output.getFlags()) + " output.getLength()=" + output.getLength()); } final JpegRTPHeader jpegRtpHeader = input.getLength() >= JpegRTPHeader.HEADER_SIZE ? JpegRTPHeader.parse((byte[]) input.getData(), input.getOffset()) : null; final long timestamp = input.getTimeStamp(); final boolean rtpMarker = (input.getFlags() & Buffer.FLAG_RTP_MARKER) != 0; if (TRACE) System.out.println("ts=" + input.getTimeStamp() + " flags=" + Integer.toHexString(input.getFlags()) + " offset=" + input.getOffset() + " length=" + input.getLength() + " jpegRtpHeader: " + jpegRtpHeader); FrameAssembler assembler = frameAssemblers.findOrAdd(timestamp); assembler.put((Buffer) input.clone()); dump(input, "Input"); if (assembler.complete()) { Buffer bComplete = baselineCodec == null ? output : new Buffer(); final int offsetAfterHeaders = assembler.copyToBuffer(bComplete); frameAssemblers.remove(assembler); frameAssemblers.removeOlderThan(timestamp); // we have a complete frame, so any earlier fragments are not needed, as they are for older (incomplete) frames. if (TRACE) System.out.println ("COMPLETE: ts=" + timestamp + " bComplete.getLength()=" + bComplete.getLength()); dump(bComplete, "bComplete"); dump(output, "output"); // TODO: the length of the FMJ buffer is generally shorter than that of the JMF buffer. This is probably because // there is trailing garbage, which JMF knows how to remove, and FMJ does not currently. // flags 0x12 // TODO: JMF is setting these flags, should we? or are they residual data? // bComplete.setDiscard(false); // not necessary, this flag should be clear on entry. if (EXIT_AFTER_ONE_FRAME) System.exit(0); return BUFFER_PROCESSED_OK; } else { frameAssemblers.removeAllButNewestN(MAX_ACTIVE_FRAME_ASSEMBLERS); // weed out incomplete frames that build up. output.setDiscard(true); return OUTPUT_BUFFER_NOT_FILLED; } } else { output.setDiscard(true); return OUTPUT_BUFFER_NOT_FILLED; } // TODO: copy over other flags, like EOM? } private final FrameAssemblerCollection frameAssemblers = new FrameAssemblerCollection(); /** * Used to assemble fragments with the same timestamp into a single frame. * @author Ken Larson * */ static class FrameAssembler { private final List list = new ArrayList(); // of Buffer private boolean rtpMarker; // have we received the RTP marker that signifies the end of a frame? /** * Add the buffer (which contains a fragment) to the assembler. Should be a clone of a real buffer, since the buffer will * be kept around. */ public void put(Buffer buffer) { if (!rtpMarker) { rtpMarker = (buffer.getFlags() & Buffer.FLAG_RTP_MARKER) != 0; } if (buffer.getLength() <= JpegRTPHeader.HEADER_SIZE) return; // no actual data in buffer, no need to keep. Typically happens when RTP marker is set. // TODO: interestingly, when JMF sends the RTP marker, it occurs in a fragment with no data - not even // a header. However, looking at the buffer, the header is there, but buffer.getLength() returns zero. // the header has the correct offset of the "end" of the frame. This would be useful since we can then // determine whether we have missing trailing fragments. list.add(buffer); Collections.sort(list, bufferFragmentOffsetComparator); // TODO: incremental sort, or bubble sort - since the list is probably already sorted. } /** * Is the frame complete? */ public boolean complete() { if (!rtpMarker) return false; // need an rtp marker to signify end if (list.size() <= 0) return false; // need at least one fragments with data beyond the header if (!contiguous()) return false; // missing fragments. TODO: theoretically we could display a degraded image, but for now we'll only display complete ones. // TODO: if some of the last ones come in after the marker, we'll see blank squares in the lower right. return true; } /** * Convenience method. */ private JpegRTPHeader parseJpegRTPHeader(Buffer b) { return JpegRTPHeader.parse((byte[]) b.getData(), b.getOffset()); } /** * @return false if any fragments are missing. Does not detect fragments missing at the end. */ private boolean contiguous() { int expect = 0; // next expected offset. for (int i = 0; i < list.size(); ++i) { final Buffer b = (Buffer) list.get(i); final JpegRTPHeader jpegRtpHeader = parseJpegRTPHeader(b); if (jpegRtpHeader.getFragmentOffset() != expect) return false;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -