/** * Decodes packet header. * * @param in Input IoBuffer * @param lastHeader Previous header * @return Decoded header */ public static Header decodeHeader(IoBuffer in, Header lastHeader) { if (log.isTraceEnabled()) { log.trace("decodeHeader - lastHeader: {} buffer: {}", lastHeader, in); } byte headerByte = in.get(); int headerValue; int byteCount = 1; if ((headerByte & 0x3f) == 0) { // Two byte header headerValue = (headerByte & 0xff) << 8 | (in.get() & 0xff); byteCount = 2; } else if ((headerByte & 0x3f) == 1) { // Three byte header headerValue = (headerByte & 0xff) << 16 | (in.get() & 0xff) << 8 | (in.get() & 0xff); byteCount = 3; } else { // Single byte header headerValue = headerByte & 0xff; byteCount = 1; } final int channelId = RTMPUtils.decodeChannelId(headerValue, byteCount); final int headerSize = RTMPUtils.decodeHeaderSize(headerValue, byteCount); Header header = new Header(); header.setChannelId(channelId); if (headerSize != HEADER_NEW && lastHeader == null) { log.error("Last header null not new, headerSize: {}, channelId {}", headerSize, channelId); // this will trigger an error status, which in turn will disconnect the "offending" flash // player // preventing a memory leak and bringing the whole server to its knees return null; } int timeValue; switch (headerSize) { case HEADER_NEW: // an absolute time value timeValue = RTMPUtils.readUnsignedMediumInt(in); header.setSize(RTMPUtils.readUnsignedMediumInt(in)); header.setDataType(in.get()); header.setStreamId(RTMPUtils.readReverseInt(in)); if (timeValue == 0xffffff) { timeValue = (int) (in.getUnsignedInt() & Integer.MAX_VALUE); header.setExtendedTimestamp(timeValue); } header.setTimerBase(timeValue); header.setTimerDelta(0); break; case HEADER_SAME_SOURCE: // a delta time value timeValue = RTMPUtils.readUnsignedMediumInt(in); header.setSize(RTMPUtils.readUnsignedMediumInt(in)); header.setDataType(in.get()); header.setStreamId(lastHeader.getStreamId()); if (timeValue == 0xffffff) { timeValue = (int) (in.getUnsignedInt() & Integer.MAX_VALUE); header.setExtendedTimestamp(timeValue); } else if (timeValue == 0 && header.getDataType() == TYPE_AUDIO_DATA) { // header.setIsGarbage(true); log.trace( "Audio with zero delta; setting to garbage; ChannelId: {}; DataType: {}; HeaderSize: {}", new Object[] {header.getChannelId(), header.getDataType(), headerSize}); } header.setTimerBase(lastHeader.getTimerBase()); header.setTimerDelta(timeValue); break; case HEADER_TIMER_CHANGE: // a delta time value timeValue = RTMPUtils.readUnsignedMediumInt(in); header.setSize(lastHeader.getSize()); header.setDataType(lastHeader.getDataType()); header.setStreamId(lastHeader.getStreamId()); if (timeValue == 0xffffff) { timeValue = (int) (in.getUnsignedInt() & Integer.MAX_VALUE); header.setExtendedTimestamp(timeValue); } else if (timeValue == 0 && header.getDataType() == TYPE_AUDIO_DATA) { // header.setIsGarbage(true); log.trace( "Audio with zero delta; setting to garbage; ChannelId: {}; DataType: {}; HeaderSize: {}", new Object[] {header.getChannelId(), header.getDataType(), headerSize}); } header.setTimerBase(lastHeader.getTimerBase()); header.setTimerDelta(timeValue); break; case HEADER_CONTINUE: header.setSize(lastHeader.getSize()); header.setDataType(lastHeader.getDataType()); header.setStreamId(lastHeader.getStreamId()); header.setTimerBase(lastHeader.getTimerBase()); header.setTimerDelta(lastHeader.getTimerDelta()); if (lastHeader.getExtendedTimestamp() != 0) { timeValue = (int) (in.getUnsignedInt() & Integer.MAX_VALUE); header.setExtendedTimestamp(timeValue); log.trace("HEADER_CONTINUE with extended timestamp: {}", timeValue); } break; default: log.error("Unexpected header size: {}", headerSize); return null; } log.trace("CHUNK, D, {}, {}", header, headerSize); return header; }