mirror of
https://github.com/zhangpeihao/gortmp
synced 2025-09-26 20:01:11 +08:00
364 lines
11 KiB
Go
364 lines
11 KiB
Go
// Copyright 2013, zhangpeihao All rights reserved.
|
||
|
||
package gortmp
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"errors"
|
||
"github.com/zhangpeihao/log"
|
||
)
|
||
|
||
// RTMP Chunk Header
|
||
//
|
||
// The header is broken down into three parts:
|
||
//
|
||
// | Basic header|Chunk Msg Header|Extended Time Stamp| Chunk Data |
|
||
//
|
||
// Chunk basic header: 1 to 3 bytes
|
||
//
|
||
// This field encodes the chunk stream ID and the chunk type. Chunk
|
||
// type determines the format of the encoded message header. The
|
||
// length depends entirely on the chunk stream ID, which is a
|
||
// variable-length field.
|
||
//
|
||
// Chunk message header: 0, 3, 7, or 11 bytes
|
||
//
|
||
// This field encodes information about the message being sent
|
||
// (whether in whole or in part). The length can be determined using
|
||
// the chunk type specified in the chunk header.
|
||
//
|
||
// Extended timestamp: 0 or 4 bytes
|
||
//
|
||
// This field MUST be sent when the normal timsestamp is set to
|
||
// 0xffffff, it MUST NOT be sent if the normal timestamp is set to
|
||
// anything else. So for values less than 0xffffff the normal
|
||
// timestamp field SHOULD be used in which case the extended timestamp
|
||
// MUST NOT be present. For values greater than or equal to 0xffffff
|
||
// the normal timestamp field MUST NOT be used and MUST be set to
|
||
// 0xffffff and the extended timestamp MUST be sent.
|
||
type Header struct {
|
||
// Basic Header
|
||
Fmt uint8
|
||
ChunkStreamID uint32
|
||
|
||
// Chunk Message Header
|
||
Timestamp uint32
|
||
MessageLength uint32
|
||
MessageTypeID uint8
|
||
MessageStreamID uint32
|
||
|
||
// Extended Timestamp
|
||
ExtendedTimestamp uint32
|
||
}
|
||
|
||
// Read Base Header from io.Reader
|
||
// High level protocol can use chunk stream ID to query the previous header instance.
|
||
func ReadBaseHeader(rbuf Reader) (n int, fmt uint8, csi uint32, err error) {
|
||
var b byte
|
||
b, err = ReadByteFromNetwork(rbuf)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n = 1
|
||
fmt = uint8(b >> 6)
|
||
b = b & 0x3f
|
||
switch b {
|
||
case 0:
|
||
// Chunk stream IDs 64-319 can be encoded in the 2-byte version of this
|
||
// field. ID is computed as (the second byte + 64).
|
||
b, err = ReadByteFromNetwork(rbuf)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
csi = uint32(64) + uint32(b)
|
||
case 1:
|
||
// Chunk stream IDs 64-65599 can be encoded in the 3-byte version of
|
||
// this field. ID is computed as ((the third byte)*256 + the second byte
|
||
// + 64).
|
||
b, err = ReadByteFromNetwork(rbuf)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
csi = uint32(64) + uint32(b)
|
||
b, err = ReadByteFromNetwork(rbuf)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
csi += uint32(b) * 256
|
||
default:
|
||
// Chunk stream IDs 2-63 can be encoded in the 1-byte version of this
|
||
// field.
|
||
csi = uint32(b)
|
||
}
|
||
return
|
||
}
|
||
|
||
// Read new chunk stream header from io.Reader
|
||
func (header *Header) ReadHeader(rbuf Reader, vfmt uint8, csi uint32, lastheader *Header) (n int, err error) {
|
||
header.Fmt = vfmt
|
||
header.ChunkStreamID = csi
|
||
var b byte
|
||
tmpBuf := make([]byte, 4)
|
||
switch header.Fmt {
|
||
case HEADER_FMT_FULL:
|
||
// Chunks of Type 0 are 11 bytes long. This type MUST be used at the
|
||
// start of a chunk stream, and whenever the stream timestamp goes
|
||
// backward (e.g., because of a backward seek).
|
||
//
|
||
// 0 1 2 3
|
||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// | timestamp |message length |
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// | message length (cont) |message type id| msg stream id |
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// | message stream id (cont) |
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// Figure 9 Chunk Message Header – Type 0
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf[1:], 3)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 3
|
||
header.Timestamp = binary.BigEndian.Uint32(tmpBuf)
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf[1:], 3)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 3
|
||
header.MessageLength = binary.BigEndian.Uint32(tmpBuf)
|
||
b, err = ReadByteFromNetwork(rbuf)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
header.MessageTypeID = uint8(b)
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf, 4)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 4
|
||
header.MessageStreamID = binary.LittleEndian.Uint32(tmpBuf)
|
||
case HEADER_FMT_SAME_STREAM:
|
||
// Chunks of Type 1 are 7 bytes long. The message stream ID is not
|
||
// included; this chunk takes the same stream ID as the preceding chunk.
|
||
// Streams with variable-sized messages (for example, many video
|
||
// formats) SHOULD use this format for the first chunk of each new
|
||
// message after the first.
|
||
//
|
||
// 0 1 2 3
|
||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// | timestamp delta |message length |
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// | message length (cont) |message type id|
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// Figure 10 Chunk Message Header – Type 1
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf[1:], 3)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 3
|
||
header.Timestamp = binary.BigEndian.Uint32(tmpBuf)
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf[1:], 3)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 3
|
||
header.MessageLength = binary.BigEndian.Uint32(tmpBuf)
|
||
b, err = ReadByteFromNetwork(rbuf)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
header.MessageTypeID = uint8(b)
|
||
|
||
case HEADER_FMT_SAME_LENGTH_AND_STREAM:
|
||
// Chunks of Type 2 are 3 bytes long. Neither the stream ID nor the
|
||
// message length is included; this chunk has the same stream ID and
|
||
// message length as the preceding chunk. Streams with constant-sized
|
||
// messages (for example, some audio and data formats) SHOULD use this
|
||
// format for the first chunk of each message after the first.
|
||
//
|
||
// 0 1 2
|
||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// | timestamp delta |
|
||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
// Figure 11 Chunk Message Header – Type 2
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf[1:], 3)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 3
|
||
header.Timestamp = binary.BigEndian.Uint32(tmpBuf)
|
||
|
||
case HEADER_FMT_CONTINUATION:
|
||
// Chunks of Type 3 have no header. Stream ID, message length and
|
||
// timestamp delta are not present; chunks of this type take values from
|
||
// the preceding chunk. When a single message is split into chunks, all
|
||
// chunks of a message except the first one, SHOULD use this type. Refer
|
||
// to example 2 in section 6.2.2. Stream consisting of messages of
|
||
// exactly the same size, stream ID and spacing in time SHOULD use this
|
||
// type for all chunks after chunk of Type 2. Refer to example 1 in
|
||
// section 6.2.1. If the delta between the first message and the second
|
||
// message is same as the time stamp of first message, then chunk of
|
||
// type 3 would immediately follow the chunk of type 0 as there is no
|
||
// need for a chunk of type 2 to register the delta. If Type 3 chunk
|
||
// follows a Type 0 chunk, then timestamp delta for this Type 3 chunk is
|
||
// the same as the timestamp of Type 0 chunk.
|
||
}
|
||
// [Extended Timestamp]
|
||
// This field is transmitted only when the normal time stamp in the
|
||
// chunk message header is set to 0x00ffffff. If normal time stamp is
|
||
// set to any value less than 0x00ffffff, this field MUST NOT be
|
||
// present. This field MUST NOT be present if the timestamp field is not
|
||
// present. Type 3 chunks MUST NOT have this field.
|
||
// !!!!!! crtmpserver set this field in Type 3 !!!!!!
|
||
// Todo: Test with FMS
|
||
if (header.Fmt != HEADER_FMT_CONTINUATION && header.Timestamp >= 0xffffff) ||
|
||
(header.Fmt == HEADER_FMT_CONTINUATION && lastheader != nil && lastheader.ExtendedTimestamp > 0) {
|
||
_, err = ReadAtLeastFromNetwork(rbuf, tmpBuf, 4)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 4
|
||
header.ExtendedTimestamp = binary.BigEndian.Uint32(tmpBuf)
|
||
logger.ModulePrintf(logHandler, log.LOG_LEVEL_TRACE,
|
||
"Extened timestamp: %d, timestamp: %d, fmt: %d\n", header.ExtendedTimestamp, header.Timestamp, header.Fmt)
|
||
header.Dump("Extended timestamp")
|
||
} else {
|
||
header.ExtendedTimestamp = 0
|
||
}
|
||
return
|
||
}
|
||
|
||
// Encode header into io.Writer
|
||
func (header *Header) Write(wbuf Writer) (n int, err error) {
|
||
// Write fmt & Chunk stream ID
|
||
switch {
|
||
case header.ChunkStreamID <= 63:
|
||
err = wbuf.WriteByte(byte((header.Fmt << 6) | byte(header.ChunkStreamID)))
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
case header.ChunkStreamID <= 319:
|
||
err = wbuf.WriteByte(header.Fmt << 6)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
err = wbuf.WriteByte(byte(header.ChunkStreamID - 64))
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
case header.ChunkStreamID <= 65599:
|
||
err = wbuf.WriteByte((header.Fmt << 6) | 0x01)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
tmp := uint16(header.ChunkStreamID - 64)
|
||
err = binary.Write(wbuf, binary.BigEndian, &tmp)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 2
|
||
default:
|
||
return n, errors.New("Unsupport chunk stream ID large then 65599")
|
||
}
|
||
tmpBuf := make([]byte, 4)
|
||
var m int
|
||
switch header.Fmt {
|
||
case HEADER_FMT_FULL:
|
||
// Timestamp
|
||
binary.BigEndian.PutUint32(tmpBuf, header.Timestamp)
|
||
m, err = wbuf.Write(tmpBuf[1:])
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += m
|
||
// Message Length
|
||
binary.BigEndian.PutUint32(tmpBuf, header.MessageLength)
|
||
m, err = wbuf.Write(tmpBuf[1:])
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += m
|
||
// Message Type
|
||
err = wbuf.WriteByte(header.MessageTypeID)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
// Message Stream ID
|
||
err = binary.Write(wbuf, binary.LittleEndian, &(header.MessageStreamID))
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 4
|
||
case HEADER_FMT_SAME_STREAM:
|
||
// Timestamp
|
||
binary.BigEndian.PutUint32(tmpBuf, header.Timestamp)
|
||
m, err = wbuf.Write(tmpBuf[1:])
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += m
|
||
// Message Length
|
||
binary.BigEndian.PutUint32(tmpBuf, header.MessageLength)
|
||
m, err = wbuf.Write(tmpBuf[1:])
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += m
|
||
// Message Type
|
||
err = wbuf.WriteByte(header.MessageTypeID)
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 1
|
||
case HEADER_FMT_SAME_LENGTH_AND_STREAM:
|
||
// Timestamp
|
||
binary.BigEndian.PutUint32(tmpBuf, header.Timestamp)
|
||
m, err = wbuf.Write(tmpBuf[1:])
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += m
|
||
case HEADER_FMT_CONTINUATION:
|
||
}
|
||
|
||
// Type 3 chunks MUST NOT have Extended timestamp????
|
||
// Todo: Test with FMS
|
||
// if header.Timestamp >= 0xffffff && header.Fmt != HEADER_FMT_CONTINUATION {
|
||
if header.Timestamp >= 0xffffff {
|
||
// Extended Timestamp
|
||
err = binary.Write(wbuf, binary.BigEndian, &(header.ExtendedTimestamp))
|
||
if err != nil {
|
||
return
|
||
}
|
||
n += 4
|
||
}
|
||
return
|
||
}
|
||
|
||
func (header *Header) RealTimestamp() uint32 {
|
||
if header.Timestamp >= 0xffffff {
|
||
return header.ExtendedTimestamp
|
||
}
|
||
return header.Timestamp
|
||
}
|
||
|
||
func (header *Header) Dump(name string) {
|
||
logger.ModulePrintf(logHandler, log.LOG_LEVEL_DEBUG,
|
||
"Header(%s){Fmt: %d, ChunkStreamID: %d, Timestamp: %d, MessageLength: %d, MessageTypeID: %d, MessageStreamID: %d, ExtendedTimestamp: %d}\n", name,
|
||
header.Fmt, header.ChunkStreamID, header.Timestamp, header.MessageLength,
|
||
header.MessageTypeID, header.MessageStreamID, header.ExtendedTimestamp)
|
||
}
|