/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.laifeng.sopcastsdk.stream.sender.rtmp.packets;
import android.util.Log;
import com.laifeng.sopcastsdk.stream.sender.rtmp.Util;
import com.laifeng.sopcastsdk.stream.sender.rtmp.io.SessionInfo;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
*
* @author francois, leoma
*/
public class ChunkHeader {
private static final String TAG = "ChunkHeader";
private ChunkType chunkType;
private int chunkStreamId;
private int absoluteTimestamp;
private int timestampDelta = -1;
private int packetLength;
private MessageType messageType;
private int messageStreamId;
private int extendedTimestamp;
public ChunkHeader() {
}
public ChunkHeader(ChunkType chunkType, int chunkStreamId, MessageType messageType) {
this.chunkType = chunkType;
this.chunkStreamId = chunkStreamId;
this.messageType = messageType;
}
public static ChunkHeader readHeader(InputStream in, SessionInfo sessionInfo) throws IOException {
ChunkHeader rtmpHeader = new ChunkHeader();
rtmpHeader.readHeaderImpl(in, sessionInfo);
return rtmpHeader;
}
private void readHeaderImpl(InputStream in, SessionInfo sessionInfo) throws IOException {
int basicHeaderByte = in.read();
if (basicHeaderByte == -1) {
throw new EOFException("Unexpected EOF while reading RTMP packet basic header");
}
// Read byte 0: chunk type and chunk stream ID
parseBasicHeader((byte) basicHeaderByte);
switch (chunkType) {
case TYPE_0_FULL: { // b00 = 12 byte header (full header)
// Read bytes 1-3: Absolute timestamp
absoluteTimestamp = Util.readUnsignedInt24(in);
timestampDelta = 0;
// Read bytes 4-6: Packet length
packetLength = Util.readUnsignedInt24(in);
// Read byte 7: Message type ID
messageType = MessageType.valueOf((byte) in.read());
// Read bytes 8-11: Message stream ID (apparently little-endian order)
byte[] messageStreamIdBytes = new byte[4];
Util.readBytesUntilFull(in, messageStreamIdBytes);
messageStreamId = Util.toUnsignedInt32LittleEndian(messageStreamIdBytes);
// Read bytes 1-4: Extended timestamp
extendedTimestamp = absoluteTimestamp >= 0xffffff ? Util.readUnsignedInt32(in) : 0;
if (extendedTimestamp != 0) {
absoluteTimestamp = extendedTimestamp;
}
sessionInfo.putPreReceiveChunkHeader(chunkStreamId, this);
break;
}
case TYPE_1_LARGE: { // b01 = 8 bytes - like type 0. not including message stream ID (4 last bytes)
// Read bytes 1-3: Timestamp delta
timestampDelta = Util.readUnsignedInt24(in);
// Read bytes 4-6: Packet length
packetLength = Util.readUnsignedInt24(in);
// Read byte 7: Message type ID
messageType = MessageType.valueOf((byte) in.read());
// Read bytes 1-4: Extended timestamp delta
extendedTimestamp = timestampDelta >= 0xffffff ? Util.readUnsignedInt32(in) : 0;
ChunkHeader prevHeader = sessionInfo.getPreReceiveChunkHeader(chunkStreamId);
if (prevHeader != null) {
messageStreamId = prevHeader.messageStreamId;
absoluteTimestamp = extendedTimestamp != 0 ? extendedTimestamp : prevHeader.absoluteTimestamp + timestampDelta;
} else {
messageStreamId = 0;
absoluteTimestamp = extendedTimestamp != 0 ? extendedTimestamp : timestampDelta;
}
sessionInfo.putPreReceiveChunkHeader(chunkStreamId, this);
break;
}
case TYPE_2_TIMESTAMP_ONLY: { // b10 = 4 bytes - Basic Header and timestamp (3 bytes) are included
// Read bytes 1-3: Timestamp delta
timestampDelta = Util.readUnsignedInt24(in);
// Read bytes 1-4: Extended timestamp delta
extendedTimestamp = timestampDelta >= 0xffffff ? Util.readUnsignedInt32(in) : 0;
ChunkHeader prevHeader = sessionInfo.getPreReceiveChunkHeader(chunkStreamId);
packetLength = prevHeader.packetLength;
messageType = prevHeader.messageType;
messageStreamId = prevHeader.messageStreamId;
absoluteTimestamp = extendedTimestamp != 0 ? extendedTimestamp : prevHeader.absoluteTimestamp + timestampDelta;
sessionInfo.putPreReceiveChunkHeader(chunkStreamId, this);
break;
}
case TYPE_3_NO_BYTE: { // b11 = 1 byte: basic header only
ChunkHeader prevHeader = sessionInfo.getPreReceiveChunkHeader(chunkStreamId);
// Read bytes 1-4: Extended timestamp
extendedTimestamp = prevHeader.timestampDelta >= 0xffffff ? Util.readUnsignedInt32(in) : 0;
timestampDelta = extendedTimestamp != 0 ? 0xffffff : prevHeader.timestampDelta;
packetLength = prevHeader.packetLength;
messageType = prevHeader.messageType;
messageStreamId = prevHeader.messageStreamId;
absoluteTimestamp = extendedTimestamp != 0 ? extendedTimestamp : prevHeader.absoluteTimestamp + timestampDelta;
sessionInfo.putPreReceiveChunkHeader(chunkStreamId, this);
break;
}
default:
Log.e(TAG, "readHeaderImpl(): Invalid chunk type; basic header byte was: " + Util.toHexString((byte) basicHeaderByte));
throw new IOException("Invalid chunk type; basic header byte was: " + Util.toHexString((byte) basicHeaderByte));
}
}
public void writeTo(OutputStream out, ChunkType chunkType, final SessionInfo sessionInfo) throws IOException {
// Write basic header byte
out.write(((byte) (chunkType.getValue() << 6) | chunkStreamId));
switch (chunkType) {
case TYPE_0_FULL: { // b00 = 12 byte header (full header)
absoluteTimestamp = (int) sessionInfo.markAbsoluteTimestampTx();
Util.writeUnsignedInt24(out, absoluteTimestamp >= 0xffffff ? 0xffffff : absoluteTimestamp);
Util.writeUnsignedInt24(out, packetLength);
out.write(messageType.getValue());
Util.writeUnsignedInt32LittleEndian(out, messageStreamId);
if (absoluteTimestamp >= 0xffffff) {
extendedTimestamp = absoluteTimestamp;
Util.writeUnsignedInt32(out, extendedTimestamp);
}
break;
}
case TYPE_1_LARGE: { // b01 = 8 bytes - like type 0. not including message ID (4 last bytes)
absoluteTimestamp = (int) sessionInfo.markAbsoluteTimestampTx();
ChunkHeader preChunkHeader = sessionInfo.getPreSendChunkHeader(chunkStreamId);
timestampDelta = absoluteTimestamp - preChunkHeader.absoluteTimestamp;
Util.writeUnsignedInt24(out, absoluteTimestamp >= 0xffffff ? 0xffffff : timestampDelta);
Util.writeUnsignedInt24(out, packetLength);
out.write(messageType.getValue());
if (absoluteTimestamp >= 0xffffff) {
extendedTimestamp = absoluteTimestamp;
Util.writeUnsignedInt32(out, absoluteTimestamp);
}
break;
}
case TYPE_2_TIMESTAMP_ONLY: { // b10 = 4 bytes - Basic Header and timestamp (3 bytes) are included
absoluteTimestamp = (int) sessionInfo.markAbsoluteTimestampTx();
ChunkHeader preChunkHeader = sessionInfo.getPreSendChunkHeader(chunkStreamId);
timestampDelta = absoluteTimestamp - preChunkHeader.absoluteTimestamp;
Util.writeUnsignedInt24(out, (absoluteTimestamp >= 0xffffff) ? 0xffffff : timestampDelta);
if (absoluteTimestamp >= 0xffffff) {
extendedTimestamp = absoluteTimestamp;
Util.writeUnsignedInt32(out, extendedTimestamp);
}
break;
}
case TYPE_3_NO_BYTE: { // b11 = 1 byte: basic header only
absoluteTimestamp = (int) sessionInfo.markAbsoluteTimestampTx();
if (absoluteTimestamp >= 0xffffff) {
extendedTimestamp = absoluteTimestamp;
Util.writeUnsignedInt32(out, extendedTimestamp);
}
break;
}
default:
throw new IOException("Invalid chunk type: " + chunkType);
}
}
private void parseBasicHeader(byte basicHeaderByte) {
chunkType = ChunkType.valueOf((byte) ((0xff & basicHeaderByte) >>> 6)); // 2 most significant bits define the chunk type
chunkStreamId = basicHeaderByte & 0x3F; // 6 least significant bits define chunk stream ID
}
/** @return the RTMP chunk stream ID (channel ID) for this chunk */
public int getChunkStreamId() {
return chunkStreamId;
}
public int getPacketLength() {
return packetLength;
}
public MessageType getMessageType() {
return messageType;
}
/** Sets the RTMP chunk stream ID (channel ID) for this chunk */
public void setChunkStreamId(int channelId) {
this.chunkStreamId = channelId;
}
public void setMessageStreamId(int messageStreamId) {
this.messageStreamId = messageStreamId;
}
public void setPacketLength(int packetLength) {
this.packetLength = packetLength;
}
@Override
public String toString() {
return "ChunkType:" + chunkType + " ChunkStreamId:" + chunkStreamId + " absoluteTimestamp:" + absoluteTimestamp + " timestampDelta:" + timestampDelta +
" messageLength:" + packetLength + " messageType:" + messageType +
" messageStreamId:" + messageStreamId + " extendedTimestamp:" + extendedTimestamp;
}
}