📄 depacketizer.java
字号:
expect += b.getLength() - JpegRTPHeader.HEADER_SIZE; } return true; } /** * Total length of all fragments. Does not include JPEG header. * Assumes that complete() has been called and returns true. */ public int frameLength() { if (!rtpMarker) throw new IllegalStateException(); if (list.size() <= 0) throw new IllegalStateException(); // calculate from offset and length of last buffer: final Buffer b = (Buffer) list.get(list.size() - 1); final JpegRTPHeader jpegRtpHeader = parseJpegRTPHeader(b); // Observed: the frame with the marker has valid offset, return jpegRtpHeader.getFragmentOffset() + b.getLength() - JpegRTPHeader.HEADER_SIZE; } /** * Assumes that complete() has been called and returns true. */ public int copyToBuffer(Buffer bDest) { if (!rtpMarker) throw new IllegalStateException(); if (list.size() <= 0) throw new IllegalStateException(); // TODO: perhaps what we should do is copy the header if there is not one. // The test samples coming from JMStudio had headers in them, so the JPEG could not be // parsed if (another) header was prepended. final Buffer bFirst = (Buffer) list.get(0); final boolean prependHeader = !hasJPEGHeaders((byte []) bFirst.getData(), bFirst.getOffset() + JpegRTPHeader.HEADER_SIZE, bFirst.getLength() - JpegRTPHeader.HEADER_SIZE); final int MAX_HEADER = prependHeader ? 1024 : 0; // TODO: what is actual max size for the header? Seems to be fixed size, 600-700 bytes. final int MAX_TRAILER = 2; final int frameLength = frameLength(); final byte[] data; if (bDest.getData() != null && ((byte []) bDest.getData()).length >= (frameLength + MAX_HEADER + MAX_TRAILER)) { data = (byte []) bDest.getData(); // reuse existing byte array // zero out: zeroData(data); } else { data = new byte[frameLength + MAX_HEADER + MAX_TRAILER]; // allocate new one - existing one not there or too short } int offsetAfterHeaders = 0; if (prependHeader) { // put initial SOI marker manually, we tell RFC2035.MakeHeaders not to do it: data[offsetAfterHeaders++] = (byte) 0xff; data[offsetAfterHeaders++] = (byte) 0xd8; // part of the header with "JFIF" in it, not generated by the code in RFC2035. offsetAfterHeaders = buildJFIFHeader(data, offsetAfterHeaders); final JpegRTPHeader jpegRtpHeaderFirst = parseJpegRTPHeader(bFirst); offsetAfterHeaders = RFC2035.MakeHeaders(false, data, offsetAfterHeaders, jpegRtpHeaderFirst.getType(), jpegRtpHeaderFirst.getQ(), jpegRtpHeaderFirst.getWidthInBlocks(), jpegRtpHeaderFirst.getHeightInBlocks()); } if (TRACE) System.out.println("offsetAfterHeaders=" + offsetAfterHeaders); for (int i = 0; i < list.size(); ++i) { final Buffer b = (Buffer) list.get(i); final JpegRTPHeader jpegRtpHeader = parseJpegRTPHeader(b);// // if (TRACE) System.out.println("Copying, length=" + (b.getLength() - JpegRTPHeader.HEADER_SIZE) + ":");// if (TRACE) System.out.println(dump((byte[]) b.getData(), b.getOffset() + JpegRTPHeader.HEADER_SIZE, (b.getLength() - JpegRTPHeader.HEADER_SIZE) > MAX_DUMP_SIZE ? MAX_DUMP_SIZE : (b.getLength() - JpegRTPHeader.HEADER_SIZE)));// if (TRACE) System.out.println("End copying."); System.arraycopy((byte []) b.getData(), b.getOffset() + JpegRTPHeader.HEADER_SIZE, data, offsetAfterHeaders + jpegRtpHeader.getFragmentOffset(), b.getLength() - JpegRTPHeader.HEADER_SIZE); } final boolean appendEOI = !hasJPEGTrailer(data, offsetAfterHeaders + frameLength, MAX_TRAILER); // no need to append if it is already there. int trailing = 0; if (appendEOI) { data[offsetAfterHeaders + frameLength + trailing++] = (byte) 0xff; data[offsetAfterHeaders + frameLength + trailing++] = (byte) 0xd9; } bDest.setData(data); bDest.setLength(offsetAfterHeaders + frameLength + trailing); bDest.setOffset(0); bDest.setTimeStamp(bFirst.getTimeStamp()); // TODO: is the source buffer timestamp in same units? return offsetAfterHeaders; } } private static void zeroData(byte[] data) { int len = data.length; for (int i = 0; i < len; ++i) { data[i] = 0; } } /** * Checks to see if the data begins with SOI. Refers to JPEG headers, not JPEGRTP headers. */ private static boolean hasJPEGHeaders(byte[] data, int offset, int len) { if (len < 2) throw new IllegalArgumentException(); if (data[offset++] != (byte) 0xff) return false; if (data[offset++] != (byte) 0xd8) return false; return true; // starts with SOI } private static boolean hasJPEGTrailer(byte[] data, int offset, int len) { if (len < 2) throw new IllegalArgumentException(); if (data[offset++] != (byte) 0xff) return false; if (data[offset++] != (byte) 0xd9) return false; return true; // ends with EOI } // /** * info on JFIF header at http://www.obrador.com/essentialjpeg/headerinfo.htm * @return new offset */ private static int buildJFIFHeader(byte[] data, int offset) { // example:// ffe000104a46494600010100000100010000 // JFIF marker (0xFFE0) data[offset++] = (byte) 0xff; data[offset++] = (byte) 0xe0; // * length -- two bytes data[offset++] = (byte) 0x00; data[offset++] = (byte) 0x10; // * identifier -- five bytes: 4A, 46, 49, 46, 00 (the ASCII code equivalent of a zero terminated "JFIF" string) data[offset++] = (byte) 0x4a; data[offset++] = (byte) 0x46; data[offset++] = (byte) 0x49; data[offset++] = (byte) 0x46; data[offset++] = (byte) 0x00; // * version -- two bytes: often 01, 02// o the most significant byte is used for major revisions// o the least significant byte for minor revisions// data[offset++] = (byte) 0x01; data[offset++] = (byte) 0x01; // * units -- one byte: Units for the X and Y densities// o 0 => no units, X and Y specify the pixel aspect ratio// o 1 => X and Y are dots per inch// o 2 => X and Y are dots per cm data[offset++] = (byte) 0x00; // * Xdensity -- two bytes data[offset++] = (byte) 0x00; data[offset++] = (byte) 0x01; // * Ydensity -- two bytes data[offset++] = (byte) 0x00; data[offset++] = (byte) 0x01; // * Xthumbnail -- one byte: 0 = no thumbnail data[offset++] = (byte) 0x00; // * Ythumbnail -- one byte: 0 = no thumbnail data[offset++] = (byte) 0x00; return offset; } private static final BufferFragmentOffsetComparator bufferFragmentOffsetComparator = new BufferFragmentOffsetComparator(); /** * Compares buffers by the fragment offset. Assumes buffers have * enough data in them for a JpegRTPHeader. * @author Ken Larson * */ private static class BufferFragmentOffsetComparator implements Comparator { public int compare(Object a, Object b) { if (a == null && b == null) return 0; if (a == null) // then a < b, return -1 return -1; if (b == null) return 1; // a > b final Buffer aCast = (Buffer) a; final Buffer bCast = (Buffer) b; final JpegRTPHeader jpegRtpHeaderA = JpegRTPHeader.parse((byte[]) aCast.getData(), aCast.getOffset()); final JpegRTPHeader jpegRtpHeaderB = JpegRTPHeader.parse((byte[]) bCast.getData(), bCast.getOffset()); return jpegRtpHeaderA.getFragmentOffset() - jpegRtpHeaderB.getFragmentOffset(); } } /** * Keeps track of multiple FrameAssemblers for different timestamps. * This is needed because packets may arrive out of order. * @author Ken Larson * */ private static class FrameAssemblerCollection { private Map frameAssemblers = new HashMap(); // FrameAssembler keyed by long - timestamp. public FrameAssembler findOrAdd(long timestamp) { FrameAssembler result = (FrameAssembler) frameAssemblers.get(new Long(timestamp)); if (result == null) { result = new FrameAssembler(); frameAssemblers.put(new Long(timestamp), result); } return result; } public void remove(FrameAssembler a) { frameAssemblers.remove(a); } public void removeOlderThan(long timestamp) { final Iterator i = frameAssemblers.entrySet().iterator(); while (i.hasNext()) { final Entry e = (Entry) i.next(); final Long entryTimestamp = (Long) e.getKey(); if (entryTimestamp.longValue() < timestamp) { if (TRACE) System.out.println("Discarding incomplete frame older than " + timestamp + ", ts=" + entryTimestamp); i.remove(); } } } public void removeAllButNewestN(int n) { while (frameAssemblers.size() > n) { final long oldestTimestamp = getOldestTimestamp(); if (oldestTimestamp < 0) throw new RuntimeException(); Long key = new Long(oldestTimestamp); FrameAssembler a = (FrameAssembler) frameAssemblers.get(key); String completeIncomplete = a.complete() ? "complete" : "incomplete"; if (TRACE) System.out.println("Discarding " + completeIncomplete + " frame (not in newest " + n + ", ts=" + oldestTimestamp); frameAssemblers.remove(key); } } public long getOldestTimestamp() { long oldestSoFar = -1; final Iterator i = frameAssemblers.keySet().iterator(); while (i.hasNext()) { final Long ts = (Long) i.next(); if (oldestSoFar < 0 || ts.longValue() < oldestSoFar) oldestSoFar = ts.longValue(); } return oldestSoFar; } public void clear() { frameAssemblers.clear(); } } private static final int MAX_DUMP_SIZE = 200000; /** * Debugging only. In this version, len is the length to dump to the string, not the * length minus offset as in StringUtils. Returns the string instead of pringing to std out. */ private static String dump(byte[] data, int offset, int len) { return StringUtils.dump(data, offset, offset + len); } /** * Debugging only. Dumps as hex to std out. */ private static void dump(Buffer b, String name) { if (TRACE) System.out.println(name + ", length=" + b.getLength() + " :"); if (TRACE) System.out.println(dump((byte[]) b.getData(), b.getOffset(), b.getLength() > MAX_DUMP_SIZE ? MAX_DUMP_SIZE : b.getLength())); }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -