首页 网站首页 商业信息 推流 查看内容

RTMP推流及协议学习

小红书营销 2022-10-22 20:29 8905人围观 推流

推流工作

整体框架图

Streaming from RTSP -> Get Audio/Video frame -> Convert frame -> RTMP push

利用libtrmp供给的API


librtmp供给了推流的API,可以在rtmp.h文件中检察一切API。我们只需要利用常用的几个API便可以将streaming推送到办事器。
- RTMP_Init()//初始化结构体
- RTMP_Free()
- RTMP_Alloc()
- RTMP_SetupURL()//设备rtmp server地址
- RTMP_EnableWrite()//翻开可写选项,设定为推流状态
- RTMP_Connect()//建立NetConnection
- RTMP_Close()//封闭毗连
- RTMP_ConnectStream()//建立NetStream
- RTMP_DeleteStream()//删除NetStream
- RTMP_SendPacket()//发送数据

Start -> RTMP_Init() -> RTMP_Alloc() -> RTMP_Setup() -> RTMP_EnableWrite() -> RTMP_Connect()

-> RTMP_ConnectStream() -> RTMP_SendPacket() -> End

将streaming封装成为RTMP格式

在发送第一帧Audio和Video的时辰,需要将Audio和Video的信息封装成为RTMP header,发送给rtmp server。
Audio头有4字节,包括:头部标志0xaf 0x00、 profile、channel、bitrate 信息。
Video头有16字节,包括IFrame、PFrame、AVC标识,除此之外,还需要将sps和pps放在header 里面。
RTMP协议界说了message Type,其中Type ID为8,9的消息别离用于传输音频和视频数据:

#define RTMP_PACKET_TYPE_AUDIO 0x08#define RTMP_PACKET_TYPE_VIDEO 0x09
  • 相关进修材料整理在这里,免费分享需要点击自取FFmpegWebRTCRTMPRTSPHLSRTP播放器-音视频流媒体高级开辟

RTMP推流及协议进修

部分材料截图

  • Audio 格式封装的源码:
    AAC header packet:
body = (unsigned char *)malloc(4 + size);memset(body, 0, 4);body[0] = 0xaf;body[1] = 0x00; switch (profile){ case 0:    body[2]|=(1<<3);//main    break; case 1:    body[2]|=(1<<4);//LC    break; case 2:    body[2]|=(1<<3);//SSR    body[2]|=(1<<4);    break; default:    ;}switch(this->channel){ case 1:    body[3]|=(1<<3);//channel1    break; case 2:    body[3]|=(1<<4);//channel2    break; default:    ;}switch(this->rate){ case 48000:    body[2]|=(1);    body[3]|=(1<<7);    break; case 44100:    body[2]|=(1<<1);    break; case 32000:    body[2]|=(1<<1);    body[3]|=(1<<7);    break; default:    ;}sendPacket(RTMP_PACKET_TYPE_AUDIO, body, 4, 0);free(body);
  • Video 格式封装的源码:
    H264 header packet:
body = (unsigned char *)malloc(16 + sps_len + pps_len);this->videoFist = false; memset(body, 0, 16 + sps_len + pps_len);body[i++] = 0x17;   // 1: IFrame, 7: AVC                    // AVC Sequence Headerbody[i++] = 0x00;body[i++] = 0x00;body[i++] = 0x00;body[i++] = 0x00; // AVCDecoderConfigurationRecordbody[i++] = 0x01;body[i++] = sps[1];body[i++] = sps[2];body[i++] = sps[3];body[i++] = 0xff;body[i++] = 0xe1;body[i++] = (sps_len >> 8) & 0xff;body[i++] = sps_len & 0xff;for (size_t j = 0; j < sps_len; j++){    body[i++] = sps[j];}body[i++] = 0x01;body[i++] = (pps_len >> 8) & 0xff;body[i++] = pps_len & 0xff;for (size_t j = 0; j < pps_len; j++){    body[i++] = pps[j];}sendPacket(RTMP_PACKET_TYPE_VIDEO, body, i, nTimeStamp); free(body);

只要第一帧Audio和第一帧video才需要发送header信息。以后就间接发送帧数据。
发送Audio的时辰,只需要在数据帧前面加上2 byte的header信息:

spec_info[0] = 0xAF;spec_info[1] = 0x01;

发送Video的时辰,需要在header里面标识出I P帧的信息,以及视频帧的长度信息:

body = (unsigned char *)malloc(9 + size);memset(body, 0, 9);i = 0;if (bIsKeyFrame== 0) {    body[i++] = 0x17;   // 1: IFrame, 7: AVC}else {    body[i++] = 0x27;   // 2: PFrame, 7: AVC}// AVCVIDEOPACKETbody[i++] = 0x01;body[i++] = 0x00;body[i++] = 0x00;body[i++] = 0x00; // NALUsbody[i++] = size >> 24 & 0xff;body[i++] = size >> 16 & 0xff;body[i++] = size >> 8 & 0xff;body[i++] = size & 0xff;memcpy(&body[i], data, size);

进阶

RTMP client与RTMP server交互流程

1 简要先容

播放一个RTMP协议的流媒体需要经过以下几个步调:握手,建立收集毗连,建立收集流,播放。RTMP毗连都是以握手作为起头的。建立毗连阶段用于建立客户端与办事器之间的“收集毗连”;建立流阶段用于建立客户端与办事器之间的“收集流”;播放阶段用于传输视音频数据。其中,收集毗连代表办事器端利用法式和客户端之间根本的连通关系。收集流代表了发送多媒体数据的通道。办事器和客户端之间只能建立一个收集毗连,可是基于该毗连可以建立很多收集流。他们的关系如图所示:



RTMP推流及协议进修

2 握手(HandShake)

一个RTMP毗连以握手起头,双方别离发送巨细牢固的三个数据块

a) 握手起头于客户端发送C0、C1块。办事器收到C0或C1后发送S0和S1。

b) 当客户端收齐S0和S1后,起头发送C2。当办事器收齐C0和C1后,起头发送S2。

c) 当客户端和办事器别离收到S2和C2后,握手完成。


RTMP推流及协议进修

握手

3建立收集毗连(NetConnection)

a) 客户端发送号令消息中的“毗连”(connect)到办事器,请求与一个办事利用实例建立毗连。

b) 办事器接收到毗连号令消息后,发送确认窗口巨细(Window Acknowledgement Size)协议消息到客户端,同时毗连到毗连号令中提到的利用法式。

c) 办事器发送设备带宽()协议消息到客户端。

d) 客户端处置设备带宽协议消息后,发送确认窗口巨细(Window Acknowledgement Size)协议消息到办事器端。

e) 办事器发送用户控制消息中的“流起头”(Stream Begin)消息到客户端。

f) 办事器发送号令消息中的“成果”(_result),告诉客户端毗连的状态。


RTMP推流及协议进修

建立毗连

4建立收集流(NetStream)

a) 客户端发送号令消息中的“建立流”(createStream)号令到办事器端。

b) 办事器端接收到“建立流”号令后,发送号令消息中的“成果”(_result),告诉客户端流的状态。


RTMP推流及协议进修

建立流

5 播放(Play)

a) 客户端发送号令消息中的“播放”(play)号令到办事器。

b) 接收到播放号令后,办事器发送设备块巨细(ChunkSize)协议消息。

c) 办事器发送用户控制消息中的“streambegin”,奉告客户端流ID。

d) 播放号令成功的话,办事器发送号令消息中的“响应状态” NetStream.Play.Start & NetStream.Play.reset,奉告客户端“播放”号令履行成功。

e) 在此以后办事器发送客户端要播放的音频和视频数据。


RTMP推流及协议进修

播放流

二、RTMP协议剖析

1 消息

消息是RTMP协议中根基的数据单元。分歧品种的消息包括分歧的Message Type ID,代表分歧的功用。RTMP协议中一共规定了十多种消息范例,别离发挥着分歧的感化。例如,Message Type ID在1-7的消息用于协议控制,这些消息通常为RTMP协议本身治理要利用的消息,用户一般情况下无需操纵其中的数据。Message Type ID为8,9的消息别离用于传输音频和视频数据。Message Type ID为15-20的消息用于发送AMF编码的号令,负责用户与办事器之间的交互,比如播放,停息等等。消息首部(Message Header)有四部分组成:标志消息范例的Message Type ID,标志消息长度的Payload Length,标识时候戳的Timestamp,标识消息所属媒体流的Stream ID。消息的报文结构如图3所示。


RTMP推流及协议进修

消息

2 消息块

在收集上传输数据时,消息需要被拆分红较小的数据块,才合适在响应的收集情况上传输。RTMP协议中规定,消息在收集上传输时被拆分红消息块(Chunk)。消息块首部(Chunk Header)有三部分组成:用于标识本块的Chunk Basic Header,用于标识本块负载所属消息的Chunk Message Header,以及那时候戳溢出时才出现的Extended Timestamp。消息块的报文结构如图4所示。


RTMP推流及协议进修

消息块

3 消息分块

在消息被朋分红几个消息块的进程中,消息负载部分(Message Body)被朋分红巨细牢固的数据块(默许是128字节,最初一个数据块可以小于该牢固长度),并在其首部加上消息块首部(Chunk Header),就组成了响应的消息块。消息分块进程如图5所示,一个巨细为307字节的消息被朋分红128字节的消息块(除了最初一个)。


RTMP推流及协议进修

RTMP分块

RTMP传输媒体数据的进程中,发送端首先把媒体数据封装成消息,然后把消息朋分红消息块,最初将朋分后的消息块经过TCP协议发送进来。接收端在经过TCP协议收到数据后,首先把消息块重新组分解消息,然后经过抵消息停止解封装处置便可以规复出媒体数据。

RTMPDump源码分析

握手(HandsShake)

static int HandShake(RTMP * r, int FP9HandShake);

HandShake函数在:/rtmp/rtmplib/handshack.h中。
./rtmp.c:69:#define RTMP_SIG_SIZE 1536

/*client HandShake*/ 695 static int HandShake(RTMP * r, int FP9HandShake){ 709 uint8_t clientbuf[RTMP_SIG_SIZE + 4], *clientsig=clientbuf+4; /*C0 字段已经写入clientsig*/ 721 if (encrypted){   722      clientsig[-1] = 0x06; /* 0x08 is RTMPE as well */   723      offalg = 1;   724 }else      //0x03代表RTMP协议的版本(客户端要求的)      //数组居然能有“-1”下标,由于clientsig指向的是clientbuf+4,所以不存在不法地址    //C0中的字段(1B)   725   clientsig[-1] = 0x03; /*预备C1字段进程略去,C1字段的数据写入clientsig中, clientsig的巨细为1536个字节*/ /*1st part of shakehand .......*//*C ------- S*//*c0 C1-->   *//*  <-- S0 S1*//*C2 -->     *//*send clientsig C0 和 C1一路发送*/ 814   if (!WriteN(r, (char *)clientsig-1, RTMP_SIG_SIZE + 1)) 815     return FALSE; /*get server response->read type, if get response type not match handshake failed*/ 817   if (ReadN(r, (char *)&type, 1) != 1)  /* 0x03 or 0x06 */ 818     return FALSE; /*encrypt type = 0x06*/ /*get server response->read serversig*/ 826 if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) 827     return FALSE; /*假如是加密协议,则需要校验收到的serversig能否和发送的婚配,假如没有加密则间接发送收到的serversig*/ 968   if (!WriteN(r, (char *)reply, RTMP_SIG_SIZE)) 969     return FALSE; /*2nd part of shakehand .....*//*C ----- S*//*   <-- S2*/ 972   if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) 973     return FALSE;/* compare info between serversig and clientsig*/ 1060  if (memcmp(serversig, clientsig, RTMP_SIG_SIZE) != 0)/*假如相称,则握手成功*/}

建立链接(NetConnnet)

RTMP_Connect(RTMP *r, RTMPPacket *cp);

建立毗连的代码位于:librtmp/rtmp.c中,界说函数:RTMP_Connect()。RTMP_Conncet()里面又别离挪用了两个函数:RTMP_Connect0(), RTMP_Connect1()。RTMP_Connect0()首要停止的是socket的毗连,RTMP_Connct1()停止的是RTMP相关的毗连行动。

1031 int RTMP_Connect(RTMP *r, RTMPPacket *cp)1032 {1033   struct sockaddr_in service;1034   if (!r->Link.hostname.av_len)1035     return FALSE;1036 1037   memset(&service, 0, sizeof(struct sockaddr_in));1038   service.sin_family = AF_INET;1039 1040   if (r->Link.socksport)1041     {1042       /* Connect via SOCKS */1043       if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport))1044   return FALSE;1045     }1046   else1047     {1048       /* Connect directly */1049       if (!add_addr_info(&service, &r->Link.hostname, r->Link.port))1050   return FALSE;1051     }1052 1053   if (!RTMP_Connect0(r, (struct sockaddr *)&service))1054     return FALSE;1055 1056   r->m_bSendCounter = TRUE;1057 1058   return RTMP_Connect1(r, cp);1059 }

int RTMP_Connect0(RTMP r, struct sockaddr service);

RTMP_Connect0函数分析:

905 int RTMP_Connect0(RTMP *r, struct sockaddr * service){     /*建立socket*/ 913   r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);     /*经过socket毗连到办事器地址*/ 916   if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0)     /*假如指定了socket端口到,则停止socks Negotiate*/ 928     if (!SocksNegotiate(r)){}     /*毗连成功以后,返回TRUE*/ 956   return TRUE;    }

int RTMP_Connect1(RTMP *r, RTMPPacket *cp);

RTMP_Connect1函数分析:
按照分歧的传输协议,挑选传送数据的方式。之落后行HandShake,最初挪用SendConnectPacket()送Connect packet

intRTMP_Connect1(RTMP *r, RTMPPacket *cp){/*if crypto use tls_conncet*/  if (r->Link.protocol & RTMP_FEATURE_SSL){#if defined(CRYPTO) && !defined(NO_SSL)      TLS_client(RTMP_TLS_ctx, r->m_sb.sb_ssl);      TLS_setfd(r->m_sb.sb_ssl, r->m_sb.sb_socket);      if (TLS_connect(r->m_sb.sb_ssl) < 0){...}#else      return FALSE;#endif    }  /*if no crypto, use http post*/    if (r->Link.protocol & RTMP_FEATURE_HTTP){      HTTP_Post(r, RTMPT_OPEN, "", 1);      if (HTTP_read(r, 1) != 0){...}        ...    }    /*停止HandShake*/  if (!HandShake(r, TRUE)){...}    /*握手成功以后,发送Connect Packet*/  if (!SendConnectPacket(r, cp)){...}  return TRUE;}

SendConnectPacket() 里面首要对RTMP信息停止打包,然后挪用RTMP_SendPacket函数,将内容发送进来。

static intSendConnectPacket(RTMP *r, RTMPPacket *cp){  RTMPPacket packet;  char pbuf[4096], *pend = pbuf + sizeof(pbuf);  char *enc;   if (cp)    return RTMP_SendPacket(r, cp, TRUE);   packet.m_nChannel = 0x03; /* control channel (invoke) */  packet.m_headerType = RTMP_PACKET_SIZE_LARGE;  packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;  packet.m_nTimeStamp = 0;  packet.m_nInfoField2 = 0;  packet.m_hasAbsTimestamp = 0;  packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;   enc = packet.m_body;  enc = AMF_EncodeString(enc, pend, &av_connect);  enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes);  *enc++ = AMF_OBJECT;   /*encrypto 部分省略 首要就是挪用AMF函数停止*/  ...   packet.m_nBodySize = enc - packet.m_body;   return RTMP_SendPacket(r, &packet, TRUE);}

建立流(NetStream)

RTMP_ConnectStream()函数首要用于在NetConnection根本上面建立一个NetStream。

int RTMP_ConnectStream(RTMP *r, int seekTime);

int 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;   // 接收到的现实上是块(Chunk),而不是消息(Message),由于消息在网上传输的时辰要朋分红块.  while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet)){      // 一个消息能够被封装成多个块(Chunk),只要当一切块读取完才处置这个消息包      if (RTMPPacket_IsReady(&packet)){        if (!packet.m_nBodySize)          continue;        // 读取到flv数据包,则继续读取下一个包        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;          }        RTMP_ClientPacket(r, &packet);// 处置收到的数据包        RTMPPacket_Free(&packet);// 处置终了,断根数据      }    }  return r->m_bPlaying;}

简单的一个逻辑判定,重点在while循环里。首先,必必要满足三个条件。其次,进入循环今后只要出错大概建立流(NetStream)完成后,才能退出循环。
有两个重要的函数:

int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet);

块格式:

basic header(1-3字节) chunk msg header(0/3/7/11字节) Extended Timestamp(0/4字节) chunk data
消息格式:

timestamp(3字节) msg length(3字节) msg type id(1字节,小端) msg stream id(4字节)

/** * @brief 读取接收到的消息块(Chunk),寄存在packet中. 对接收到的消息不做任何处置。 块的格式为: * *   | basic header(1-3字节)| chunk msg header(0/3/7/11字节) | Extended Timestamp(0/4字节) | chunk data | * *   其中 basic header还可以分化为:| fmt(2位) | cs id (3 <= id <= 65599) | *   RTMP协议支持65597种流,ID从3-65599。ID 0、1、2作为保存。 *      id = 0,暗示ID的范围是64-319(第二个字节 + 64); *      id = 1,暗示ID范围是64-65599(第三个字节*256 + 第二个字节 + 64); *      id = 2,暗示低层协议消息。 *   没有其他的字节来暗示流ID。3 -- 63暗示完整的流ID。 * *    一个完整的chunk msg header 还可以分化为 : *     | timestamp(3字节) | msg length(3字节) | msg type id(1字节,小端) | msg stream id(4字节) | */int  RTMP_ReadPacket(RTMP *r, RTMPPacket *packet)  {  uint8_t hbuf[RTMP_MAX_HEADER_SIZE] = { 0 };  // Chunk Header长度最大值为3 + 11 + 4 = 18  char *header = (char *)hbuf;  // header指向从socket接收到的数据  int   nSize, hSize, nToRead, nChunk;  // nSize是块消息头长度,hSize是块头长度  int   didAlloc = FALSE;   RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d", __FUNCTION__, r->m_sb.sb_socket);   // 读取1个字节存入 hbuf[0]  if (ReadN(r, (char *)hbuf, 1) == 0)  {    RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header", __FUNCTION__);    return FALSE;  }   packet->m_headerType = (hbuf[0] & 0xc0) >> 6;  // 块范例fmt  packet->m_nChannel    = (hbuf[0] & 0x3f);  // 块流ID(2 - 63)  header++;   // 块流ID第一个字节为0,暗示块流ID占2个字节,暗示ID的范围是64-319(第二个字节 + 64)  if (packet->m_nChannel == 0)  {    // 读取接下来的1个字节寄存在hbuf[1]中    if (ReadN(r, (char *)&hbuf[1], 1) != 1)    {      RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 2nd byte", __FUNCTION__);      return FALSE;    }     // 块流ID = 第二个字节 + 64 = hbuf[1] + 64    packet->m_nChannel = hbuf[1];    packet->m_nChannel += 64;    header++;  }  // 块流ID第一个字节为1,暗示块流ID占3个字节,暗示ID范围是64 -- 65599(第三个字节*256 + 第二个字节 + 64)  else if (packet->m_nChannel == 1){    int tmp;    // 读取2个字节寄存在hbuf[1]和hbuf[2]中    if (ReadN(r, (char *)&hbuf[1], 2) != 2)    {      RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet header 3nd byte", __FUNCTION__);      return FALSE;    }     // 块流ID = 第三个字节*256 + 第二个字节 + 64    tmp = (hbuf[2] << 8) + hbuf[1];    packet->m_nChannel = tmp + 64;    RTMP_Log(RTMP_LOGDEBUG, "%s, m_nChannel: %0x", __FUNCTION__, packet->m_nChannel);    header += 2;  }   // 块消息头(ChunkMsgHeader)有四品种型,巨细别离为11、7、3、0,每个值加1 就获得该数组的值  // 块头 = BasicHeader(1-3字节) + ChunkMsgHeader + ExtendTimestamp(0或4字节)  nSize = packetSize[packet->m_headerType];   // 块范例fmt为0的块,在一个块流的起头和时候戳返回的时辰必须有这类块  // 块范例fmt为1、2、3的块利用与先前块不异的数据  // 关于块范例的界说,可参考官方协议:流的分块 --- 6.1.2节  if (nSize == RTMP_LARGE_HEADER_SIZE)  /* if we get a full header the timestamp is absolute */  {    packet->m_hasAbsTimestamp = TRUE;    // 11个字节的完整ChunkMsgHeader的TimeStamp是绝对时候戳  }else if (nSize < RTMP_LARGE_HEADER_SIZE){    /* 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--;  // 实在的ChunkMsgHeader的巨细,此处减1是由于前面获得包范例的时辰多加了1   // 读取nSize个字节存入header  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;  }   // 今朝已经读取的字节数 = chunk msg header + basic header  hSize = nSize + (header - (char *)hbuf);  // chunk msg header为11、7、3字节,fmt范例值为0、1、2  if (nSize >= 3){    // 首部前3个字节为timestamp    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); */     // chunk msg header为11或7字节,fmt范例值为0或1    if (nSize >= 6)    {      packet->m_nBodySize = AMF_DecodeInt24(header + 3);      packet->m_nBytesRead = 0;      RTMPPacket_Free(packet);       if (nSize > 6)      {        packet->m_packetType = header[6];        // msg type id        if (nSize == 11)          packet->m_nInfoField2 = DecodeInt32LE(header + 7); // msg stream id,小端字节序      }    }   // Extend Tiemstamp,占4个字节    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);   // 假如消息长度非0,且消息数据缓冲区为空,则为之申请空间  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;  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;    // 块巨细  }   // 读取一个块巨细的数据存入块消息数据缓冲区  if (ReadN(r, packet->m_body + packet->m_nBytesRead, nChunk) != nChunk){    RTMP_Log(RTMP_LOGERROR, "%s, failed to read RTMP packet body. len: %u",     __FUNCTION__, packet->m_nBodySize);    return FALSE;  }   RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)packet->m_body + packet->m_nBytesRead, nChunk);   // 更新已读数据字节个数  packet->m_nBytesRead += nChunk;   /* keep the packet as ref for other packets on this channel */  // 将这个包作为通道中其他包的参考  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));   // 包读取终了  if (RTMPPacket_IsReady(packet)){    /* make packet's timestamp absolute,绝对时候戳 = 上一次绝对时候戳 + 时候戳增量 */    if (!packet->m_hasAbsTimestamp)      /* timestamps seem to be always relative!! */      packet->m_nTimeStamp += r->m_channelTimestamp[packet->m_nChannel];     // 当前绝对时候戳保存起来,供下一个包转换时候戳利用    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{  packet->m_body = NULL;  /* so it won't be erased on free */  }   return TRUE;}

int ReadN(RTMP *r, char *buffer, int n);

/** * @brief 从HTTP或SOCKET中读取n个数据寄存在buffer中. */static int ReadN(RTMP *r, char *buffer, int n){    int  nOriginalSize = n;    int  avail;    char *ptr;     r->m_sb.sb_timedout = FALSE;    #ifdef _DEBUG    memset(buffer, 0, n);    #endif     ptr = buffer;    while (n > 0){    int nBytes = 0, nRead;    if (r->Link.protocol & RTMP_FEATURE_HTTP)                   {    while (!r->m_resplen)    {        if (r->m_sb.sb_size < 144)        {            if (!r->m_unackd)            HTTP_Post(r, RTMPT_IDLE, "", 1);            if (RTMPSockBuf_Fill(r, &r->m_sb) < 1){                if (!r->m_sb.sb_timedout)                RTMP_Close(r);                return 0;            }        }         if (HTTP_read(r, 0) == -1){            RTMP_Log(RTMP_LOGDEBUG, "%s, No valid HTTP response found", __FUNCTION__);            RTMP_Close(r);            return 0;        }    }     if (r->m_resplen && !r->m_sb.sb_size)    RTMPSockBuf_Fill(r, &r->m_sb);     avail = r->m_sb.sb_size;    if (avail > r->m_resplen)        avail = r->m_resplen;    }else{        avail = r->m_sb.sb_size;        if (avail == 0){            if (RTMPSockBuf_Fill(r, &r->m_sb) < 1){                if (!r->m_sb.sb_timedout)                    RTMP_Close(r);                return 0;            }            avail = r->m_sb.sb_size;        }    }     nRead = ((n < avail) ? n : avail);    if (nRead > 0){        memcpy(ptr, r->m_sb.sb_start, nRead);        r->m_sb.sb_start += nRead;        r->m_sb.sb_size -= nRead;        nBytes = nRead;        r->m_nBytesIn += nRead;        if (r->m_bSendCounter && r->m_nBytesIn > ( r->m_nBytesInSent + r->m_nClientBW / 10))        if (!SendBytesReceived(r))        return FALSE;    }    /*RTMP_Log(RTMP_LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); */    //#ifdef _DEBUG    //      fwrite(ptr, 1, nBytes, netstackdump_read);    //#endif     if (nBytes == 0){        RTMP_Log(RTMP_LOGDEBUG, "%s, RTMP socket closed by peer", __FUNCTION__);        /*goto again; */        RTMP_Close(r);        break;    }     if (r->Link.protocol & RTMP_FEATURE_HTTP){        r->m_resplen -= nBytes;        n -= nBytes;        ptr += nBytes;    }     return nOriginalSize - n;}

int RTMPSockBuf_Fill(RTMP *r, RTMPSockBuf *sb);

/** * @brief 挪用Socket编程中的recv()函数,接收数据 */int RTMPSockBuf_Fill(RTMP *r, RTMPSockBuf *sb){    int nBytes;    if  (!sb->sb_size)        sb->sb_start = sb->sb_buf;     while (1)    {        // 缓冲区长度:总长-未处置字节-已处置字节          // |-----已处置--------|-----未处置--------|---------缓冲区----------|          // sb_buf        sb_start    sb_size          nBytes = sizeof(sb->sb_buf) - sb->sb_size - (sb->sb_start - sb->sb_buf);        {            // int recv( SOCKET s, char * buf, int len, int flags);              // s    :一个标识已毗连套接口的描写字。              // buf  :用于接收数据的缓冲区。               // len  :缓冲区长度。              // flags:指定挪用方式。              // 从sb_start(待处置的下一字节) + sb_size()还未处置的字节起头buffer为空,可以存储            nBytes = r->m_sock.recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0);        }         if (nBytes != -1){            // 未处置的字节又多了            sb->sb_size += nBytes;        }else{            int sockerr = r->m_sock.getsockerr();            RTMP_Log(RTMP_LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)",             __FUNCTION__, nBytes, sockerr, strerror(sockerr));            if (sockerr == EINTR && !RTMP_ctrlC)                continue;             if (sockerr == EWOULDBLOCK || sockerr == EAGAIN){                sb->sb_timedout = TRUE;                nBytes = 0;            }        }        break;    }    return nBytes;}

高端人脉微信群

高端人脉微信群

人脉=钱脉,我们相信天下没有聚不拢的人脉,扫码进群找到你所需的人脉,对接你所需的资源。

商业合作微信

商业合作微信

本站创始人微信,13年互联网营销经验,擅长引流裂变、商业模式、私域流量,高端人脉资源丰富。

精彩点评

相关推荐

详解RTSP推流实战(1)

详解RTSP推流实战(1)

0.引言本篇文章主要讲解RTSP推流实战,整体推流流程与RTMP推流流程类似。如果对于RTSP

视频号推流直播是什么?怎么玩?(附最全操作流程!)

视频号推流直播是什么?怎么玩?(附最全操作流程!)

随着视频号直播的不断完善,美颜、抽奖、推流直播等功能纷纷上线,【附近的直播和人】

抖音直播间推流机制是什么?怎么做能让直播间一直有流量 ... ...

抖音直播间推流机制是什么?怎么做能让直播间一直有流量 ... ...

很多人直播间没有人,就是因为搞不懂抖音直播间推流机制。不明白推流机制就找不到正确

python利用ffmpeg进行rtmp推流直播

python利用ffmpeg进行rtmp推流直播

思路:opencv读取视频 — 将视频分割为帧 — 将每一帧进行需求加工后 — 将此帧写入pi

OBS推流如何实现多平台推流

OBS推流如何实现多平台推流

500强直播策划,策划过多起直播。经常会遇到需要全平台推流的情况,但是OBS的原生软件

这篇微头条是3个月前写的,最近几天被再次推流,它有何特别之处

这篇微头条是3个月前写的,最近几天被再次推流,它有何特别之处

#头条文章养成计划#根据我400多天创作的亲身体验,文章推荐的时间比较长,而微头条的

头条的推流机制,原来是这样的

头条的推流机制,原来是这样的

#头条创作挑战赛#头条推流的机制是怎样的?在头条写作了31天,小编一直被这个问题困扰

在电脑上使用OBS在各大平台 直播 推流的方法

在电脑上使用OBS在各大平台 直播 推流的方法

Open Broadcaster Software(简称 OBS)是一款好用的第三方开源程序直播流媒体内容制

视频和视频帧:ffmpeg的RTMP推流

视频和视频帧:ffmpeg的RTMP推流

写在前面本文将介绍以下内容:什么是推流?将介绍推流常见的协议RTMP,HLS等。怎么用f

西瓜媒体直播教程:30秒学会推流直播

西瓜媒体直播教程:30秒学会推流直播

头条直播换到西瓜后台啦! 推流还是一样方便快捷! 功能更多更

视频号如何推流直播?推流直播详细教程

视频号如何推流直播?推流直播详细教程

​最近有很多同学问我视频号推流直播怎么做,这种三两句话回答不清楚,今天特意写了详

推流与拉流简概

推流与拉流简概

推流:将直播内容推送至服务器的过程拉流:为服务器已有直播内容,用指定地址进行拉取

一篇文章突然停止推流,我只做了2件事,结果收益200元

一篇文章突然停止推流,我只做了2件事,结果收益200元

我是依伊,一个全职写作的创作人,点击右上角关注,为你分享【新媒体写作变现】和【个

OBS直播多平台同时推流解决方法,简单粗暴

OBS直播多平台同时推流解决方法,简单粗暴

用OBS作为电脑直播推流,是很多人使用的一款开源软件。我们使用OBS时,它默认的是只能

3分钟带你了解抖音推流机制!

3分钟带你了解抖音推流机制!

你是否也遇到过辛辛苦苦拍了视频,但结果却还不如跳舞的小姐姐?为什么别人随便拍的视

千万级直播运营必须掌握的OBS推流直播技能

千万级直播运营必须掌握的OBS推流直播技能

专业直播操盘手必须掌握的OBS推流直播技能私域直播母東東,业绩增长分分钟Hello,各位

如何获取抖音直播的推流地址?

如何获取抖音直播的推流地址?

想在直播间中直播游戏,那就需要用到2个东西,分别是推流码和obs软件。那抖音直播推流

视频号问题系列(一):视频号如何开通推流直播

视频号问题系列(一):视频号如何开通推流直播

缘起: 最近工作比较忙、文章也没怎么更新,不过最近 一段时间过来问我视频号问题的朋

玩转直播,直播推流软件你选对了吗?

玩转直播,直播推流软件你选对了吗?

随着近几年互联网技术高速发展,人们对社交形式多样化的需求不断增加,从一开始的文字

直播推流和拉流方法-VLC 播放器专题

直播推流和拉流方法-VLC 播放器专题

直播目前处于一个风口期,很多直播开始跨平台跨地域直播,如何实现异地直播,跨平台直

商业洽谈 文章投递 寻求报道
电话咨询: 15924191378
关注微信