------------------------この記事の主な役割は、契約に反してRTMPソースコードを読んだことを記録し、詳細な承認を行うことです- --------- -----------
注: 契約書を大まかに閲覧するだけで、詳細には読まないでください。契約書を読むためのコードで理解しやすくなります。
プロトコル
ブロック
ハンドシェイク後、接続は1つ以上のブロックストリームの多重化を開始します。各ブロックストリームは、メッセージストリームからのメッセージのタイプを伝送します。作成された各ブロックは、一意のブロックストリームIDに関連付けられています。すべてのブロックはネットワークを介して送信されます。送信中は、次のブロックを送信する前に1つのブロックを送信する必要があります。受信側では、各ブロックがブロックIDに基づいてメッセージに収集されます。
ブロックは、高レベルプロトコルの大きなメッセージを小さなメッセージに分割し、大きな低優先度のメッセージが小さな高優先度のメッセージをブロックしないようにします。
ブロッキングは、ブロックヘッダーのメッセージに元々含まれていた情報を圧縮し、小さなメッセージを送信するオーバーヘッドを削減します。
ブロックサイズは構成可能です。これは、セクション7.1で説明されているブロックメッセージで実行できます。最大のブロックは65535バイトで、最小のブロックは128バイトです。ブロックが大きいほど、CPU使用率は低くなりますが、低帯域幅での書き込みやその他のコンテンツの遅延も大きくなります。ブロックサイズは、各方向で独立しています。
1.ブロックフォーマット
ヘッダーとデータで構成されます。ブロックヘッダーは、次の3つの部分で構成されます。
基本ブロックヘッダー:1〜3バイト
このフィールドには、ブロックストリームIDとブロックタイプが含まれます。ブロックタイプは、エンコードされたメッセージヘッダーの形式を決定します。長さはブロックストリームIDによって異なります。ブロックストリームIDは可変長フィールドです。
ブロックメッセージヘッダー:0、3、7、または11バイト。
このフィールドは、送信されるメッセージの情報をエンコードします。このフィールドの長さは、ブロックヘッダーで指定されたブロックタイプによって異なります。
拡張タイムスタンプ:0または4バイト。
このフィールドは、通常のタイムスタンプ(通常のタイムスタンプはブロックメッセージヘッダーのタイムスタンプを参照)が0xffffffに設定されている場合に送信する必要があり、通常のタイムスタンプに他の値がある場合は送信しないでください。値。通常のタイムスタンプの値が0xffffff未満の場合、このフィールドは表示されませんが、通常のタイムスタンプフィールドを使用する必要があります。
1.1ブロック基本ヘッダー
ブロック基本ヘッダーは、ブロックストリームIDとブロックタイプをエンコードします(下の図ではfmtで示されています)。ブロックタイプ
は、エンコードされたメッセージヘッダーの形式を決定します。基本ブロックヘッダーフィールドは、1、2、または3バイトです。ブロックストリームIDによって異なります。
実装はIDを表すために最小量のデータを使用する必要がありますか?。
このプロトコルは、IDが3〜65599の範囲の65597ストリームをサポートします。ID 0、1、2は予約されています。0は、ID範囲が64-319(2番目のバイト+ 64)であることを意味します。1は、ID範囲が64-65599(3番目のバイト256 + 2
番目のバイト+64 )であることを意味します。2は、低レベルのプロトコルニュースを意味します。ストリームIDを表す他のバイトはありません。3-63は完全なストリームIDを表します。3-63の間の値は、完全なストリームIDを表します。他のバイトはストリームIDを表しません。 ビット0〜5(重要ではない)は、ブロックストリームIDを表します。 ブロックストリーム ID2-63は1バイトのフィールドで表すことができます。ブロックストリームID64-319は2バイトで表すことができます。ID計算は(2バイト目+64) ブロックストリームID64-65599は3バイトで表現できます。IDは、3番目のバイト255 +2番目のバイト
+64 Cs id:6ビットとして計算されます。
このフィールドは、2〜63の範囲のブロックストリームIDを表します。値0および1は、 このフィールドの
2バイトバージョンまたは3バイトバージョンのFmtを示します。2ビット
このフィールドは、ブロックメッセージヘッダーの4つの形式を識別します。各ストリームタイプのブロックヘッダーは、次のセクションに示されています。
Cs id-64:8-16ビット
このフィールドには、ブロックストリームIDから64を引いた値が含まれます。たとえば、365はcs idの1を表し、ここでの16ビットは301を表します。
ブロックストリームIDは64〜319の範囲であり、2バイトバージョンまたは3バイトバージョンで表すことができます。
1.2ブロックメッセージヘッダー
ブロックストリームの基本ヘッダーのfmtフィールドを選択するためのブロックメッセージIDには4つの形式があります。
実装では、最もコンパクトな方法を使用してブロックヘッダーを表す必要があります。
1.2.1タイプ0タイプ0
のブロック長は11バイトです。ブロックストリームの開始時とタイムスタンプが返されるときに、そのようなブロックが存在する必要があります。
タイムスタンプ:
タイプ0のブロックの場合は3バイト。メッセージの絶対タイムスタンプがここに送信されます。タイムスタンプが16777215(16進数の0x00ffffff)以上の場合、値は16777215である必要があり、拡張タイムスタンプが表示される必要があります。それ以外の場合、値はタイムスタンプ全体です。
1.2.2。タイプ1
タイプ1ブロックの長さは7バイトです。メッセージフローIDはこのブロックに含まれていません。ブロックのメッセージフローIDは、前のブロックと同じです。可変サイズのメッセージを含むストリームの場合、最初のメッセージの後の各メッセージの最初のブロックはこの形式を使用する必要があります。
1.2.3。タイプ2
タイプ2ブロックは3バイトを占有します。ストリームIDもメッセージ長も含まれていません。このブロックで使用されるストリームIDとメッセージ長は、前のブロックと同じです。固定サイズのメッセージを含むストリームの場合、最初のメッセージの後の各メッセージの最初のブロックはこの形式を使用する必要があります。
1.2.4タイプ3
タイプ3ブロックにはヘッダーがありません。ストリームID、メッセージの長さ、およびタイムスタンプは表示されません。このタイプのブロックは、前のブロックと同じデータを使用します。メッセージが複数のブロックに分割されている場合、最初のブロックを除くすべてのブロックでこのタイプを使用する必要があります。例については、セクション6.2.2の例2を参照してください。同じサイズ、ストリームID、および時間間隔のストリームは、タイプ2ブロックの後にこのブロックを使用する必要があります。例については、セクション6.2.1の例1を参照してください。
最初のメッセージと2番目のメッセージの時間増分が最初のメッセージのタイムスタンプと同じである場合、タイプ0ブロックの後にタイプ3ブロックを続ける必要があり、時間増分を登録するためにタイプ2ブロックは必要ありません。タイプ3ブロックがタイプ0ブロックの後にある場合、タイプ3のタイムスタンプ増分はタイプ0ブロックのタイムスタンプと同じです。
タイムスタンプの増分:3バイト
タイプ1ブロックとタイプ2ブロックの場合、このフィールドは、前のブロックのタイムスタンプと現在のブロックのタイムスタンプの差を示します。増分が1677215(16進数の0x00ffffff)以上の場合、この値は16777215である必要があり、拡張タイムスタンプが表示される必要があります。それ以外の場合、この値は増分全体です。
メッセージ長:3バイト
タイプ0またはタイプ1ブロックの場合、このフィールドはメッセージの長さを示します。
この値は通常、荷重の長さと同じではないことに注意してください。チャンクペイロードの長さは、最後のチャンクを除くすべてのチャンクの最大サイズであり、最後のチャンクの残り(小さいメッセージの場合は全長になる場合があります)です。
メッセージタイプID:
タイプ0およびタイプ1ブロックの場合は1バイト、このフィールドはメッセージタイプを送信します。
メッセージフローID:4バイト
タイプ0のブロックの場合、このフィールドにはメッセージフローIDが格納されます。通常、ブロックフロー内のメッセージは、同じメッセージフローから送信されます。ただし、異なるメッセージがブロックストリームに多重化される可能性があるため、ヘッダー圧縮を効果的に実装することはできません。ただし、1つのメッセージフローが閉じられ、別のメッセージフローが開かれた場合、タイプ0の新しいブロックを送信して既存のブロックフローを再利用することは不可能ではありません。
1.3。拡張タイムスタンプ
このフィールドは、ブロックメッセージヘッダーの通常のタイムスタンプが0x00ffffffに設定されている場合にのみ送信されます。通常のタイムスタンプの値が0x00ffffff未満の場合、このフィールドは表示されません。タイムスタンプフィールドが表示されない場合、このフィールドは表示されないようにする必要があります。タイプ3ブロックには、このフィールドを含めることはできません。このフィールドは、ブロックメッセージヘッダーの後、ブロック時間の前にあります。
構造解析を含む
typedef struct RTMPPacket
{
uint8_t m_headerType; /* zx块类型,块类型决定了消息头大小 */
uint8_t m_packetType; /* zx消息类型 */
uint8_t m_hasAbsTimestamp; /* zx True表示绝对时间 */
int m_nChannel; /* zx块流id */
uint32_t m_nTimeStamp; /* timestamp */
int32_t m_nInfoField2; /* last 4 bytes in a long header消息流id */
uint32_t m_nBodySize; /* zx 消息长度 */
uint32_t m_nBytesRead; /* zx 表示已读字节数 */
RTMPChunk *m_chunk;
char *m_body; /* zx 申请内存存放消息内容,头数据18字节之后的地址 */
} RTMPPacket;
ソースコード
RTMP_ConnectStreamの実現: RTMP_ReadPacketは、メッセージ内のすべてのブロックデータを周期的に読み取ります。RTMPPacket_IsReadyは、読み取りが完了したと判断した後、RTMP_ClientPacketを呼び出してデータを解析します。
bool RTMP_ConnectStream(RTMP *r, int seekTime)
{
RTMPPacket packet = { 0 };
/* seekTime was already set by SetupStream / SetupURL.
* This is only needed by ReconnectStream.
*/
if (seekTime > 0)
r->Link.seekTime = seekTime;
r->m_mediaChannel = 0;
/* zx RTMP_ReadPacket读取socket传过来的数据但是不做任何处理 */
while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet))
{
if (RTMPPacket_IsReady(&packet))
{
if (!packet.m_nBodySize)
continue;
if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) ||
(packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) ||
(packet.m_packetType == RTMP_PACKET_TYPE_INFO))
{
RTMP_Log(RTMP_LOGWARNING, "Received FLV packet before play()! Ignoring.");
RTMPPacket_Free(&packet);
continue;
}
/* zx开始处理接收到的数据,并作出响应 */
RTMP_ClientPacket(r, &packet);
RTMPPacket_Free(&packet); /* zx处理完之后释放数据 */
}
}
return r->m_bPlaying;
}
RTMP_ReadPacket:
/* zx 函数作用:读取接收下来的chunk
* 参数packet:用来保存读到的数据
*/
bool RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)
{
char hbuf[RTMP_MAX_HEADER_SIZE] = { 0 }, *header = hbuf;
int nSize, hSize, nToRead, nChunk;
bool didAlloc = false;
RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d", __FUNCTION__, r->m_sb.sb_socket);
if (ReadN(r, hbuf, 1) == 0)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);
return false;
}
packet->m_headerType = (hbuf[0] & 0xc0) >> 6; /* zx 块类型 */
packet->m_nChannel = (hbuf[0] & 0x3f); /* zx 块流id */
header++;
/* zx 下面根据协议计算块流id */
if (packet->m_nChannel == 0) /* zx 等于0是一个字节 */
{
if (ReadN(r, &hbuf[1], 1) != 1)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte", __FUNCTION__);
return false;
}
packet->m_nChannel = (unsigned)hbuf[1];
packet->m_nChannel += 64;
header++;
}
else if (packet->m_nChannel == 1)
{
int tmp;
if (ReadN(r, &hbuf[1], 2) != 2)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte", __FUNCTION__);
return false;
}
tmp = (((unsigned)hbuf[2]) << 8) + (unsigned)hbuf[1];
packet->m_nChannel = tmp + 64;
RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);
header += 2;
}
/* zx 根据块类型得到后面块消息长度 */
nSize = packetSize[packet->m_headerType];
if (nSize == RTMP_LARGE_HEADER_SIZE) /* if we get a full header the timestamp is absolute */
{
packet->m_hasAbsTimestamp = true; /* zx 11字节的完整块,时间戳是绝对时间,3和7都是相对于之前的时间的 */
}
else if (nSize < RTMP_LARGE_HEADER_SIZE) /* zx 小于12表示不是消息的第一个块,使用上一个块的消息头,只修改差异部分 */
{ /* using values from the last message of this channel */
if (r->m_vecChannelsIn[packet->m_nChannel])
memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel], sizeof(RTMPPacket));
}
nSize--;
if (nSize > 0 && ReadN(r, header, nSize) != nSize)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header. type: %x", __FUNCTION__, (unsigned int)hbuf[0]);
return false;
}
hSize = nSize + (header - hbuf);
if (nSize >= 3)
{
/* zx 无论消息头占几个字节,前三个字节都是时间戳,类型0是绝对时间戳,类型1和2是相对于上一次的时间戳 */
packet->m_nTimeStamp = AMF_DecodeInt24(header);
/*RTMP_Log(RTMP_LOGDEBUG, "%s, reading RTMP packet chunk on channel %x, headersz %i, timestamp %i, abs timestamp %i", __FUNCTION__, packet.m_nChannel, nSize, packet.m_nTimeStamp, packet.m_hasAbsTimestamp); */
if (nSize >= 6)
{
packet->m_nBodySize = AMF_DecodeInt24(header + 3); /* zx 消息长度 */
packet->m_nBytesRead = 0;
RTMPPacket_Free(packet);
if (nSize > 6)
{
packet->m_packetType = header[6]; /* zx 此消息的消息类型 */
if (nSize == 11)
packet->m_nInfoField2 = DecodeInt32LE(header + 7); /* zx 消息流id */
}
}
/* zx 扩展时间戳,只要当块消息头中的普通时间戳为0xffffff时,才会有扩展时间戳 */
if (packet->m_nTimeStamp == 0xffffff)
{
if (ReadN(r, header + nSize, 4) != 4)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read extended timestamp", __FUNCTION__);
return false;
}
packet->m_nTimeStamp = AMF_DecodeInt32(header + nSize);
hSize += 4;
}
}
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)hbuf, hSize);
/* zx 消息长度大于0时,申请内存存放消息内容+18字节最大头数据 */
if (packet->m_nBodySize > 0 && packet->m_body == NULL)
{
if (!RTMPPacket_Alloc(packet, packet->m_nBodySize))
{
RTMP_Log(RTMP_LOGDEBUG, "%s, failed to allocate packet", __FUNCTION__);
return false;
}
didAlloc = true;
packet->m_headerType = (hbuf[0] & 0xc0) >> 6;
}
nToRead = packet->m_nBodySize - packet->m_nBytesRead;
nChunk = r->m_inChunkSize; /* zx 这个是默认值128 */
if (nToRead < nChunk)
nChunk = nToRead;
/* Does the caller want the raw chunk? */
if (packet->m_chunk)
{
packet->m_chunk->c_headerSize = hSize;
memcpy(packet->m_chunk->c_header, hbuf, hSize);
packet->m_chunk->c_chunk = packet->m_body + packet->m_nBytesRead;
packet->m_chunk->c_chunkSize = nChunk;
}
/* zx 累加的往后读取数据块内容,packet->m_nBytesRead第一次为0 */
if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk)
{
RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet body. len: %lu", __FUNCTION__, packet->m_nBodySize);
return false;
}
RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)packet->m_body + packet->m_nBytesRead, nChunk);
packet->m_nBytesRead += nChunk;
/* zx 保存packet,供其他作用在这个通道的packet使用 */
if (!r->m_vecChannelsIn[packet->m_nChannel])
r->m_vecChannelsIn[packet->m_nChannel] = malloc(sizeof(RTMPPacket));
memcpy(r->m_vecChannelsIn[packet->m_nChannel], packet, sizeof(RTMPPacket));
/* zx 判断是否读取完成,读取完成之后,读取完的字节数packet->m_nBytesR等于块消息头中读取到的字节数 */
if (RTMPPacket_IsReady(packet))
{
/* make packet's timestamp absolute */
if (!packet->m_hasAbsTimestamp)
packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel]; /* timestamps seem to be always relative!! */
r->m_channelTimestamp[packet->m_nChannel] = packet->m_nTimeStamp;
/* reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel */
/* arrives and requests to re-use some info (small packet header) */
r->m_vecChannelsIn[packet->m_nChannel]->m_body = NULL;
r->m_vecChannelsIn[packet->m_nChannel]->m_nBytesRead = 0;
r->m_vecChannelsIn[packet->m_nChannel]->m_hasAbsTimestamp = false; /* can only be false if we reuse header */
}
else
{
/* zx 没读完不进行拷贝,下一次进来之后会memcpy(packet, r->m_vecChannelsIn[packet->m_nChannel], sizeof(RTMPPacket)); */
packet->m_body = NULL; /* so it won't be erased on free */
}
return true;
}