3.1 HI3518+RTMP

前面介绍了HI3518作为服务器实现RTSP传输视频,但HI3518毕竟只是个嵌入式CPU,处理并发能力有限,如果多个客户端同时观看视频就会有性能上的问题,而且实现一些直播功能如暂停,回放功能就有些捉襟见肘,这时就需要把HI3518的视频数据通过一个性能更强大服务器作为中转处理更大的并发数并实现暂停,回放等功能,这时RTMP协议就派上用场了。

Hi3518+RTMP工程文件下载链接:Hi3518+RTMP

我们先将源码编译一遍然后操作一次再作解释。将源代码文件夹"rtmp"放到ubuntu里,按照"编译说明"先编译zlib库、openssl库、librtmp库,然后编译rtmp推流程序生成名字为"rtmp"的可执行程序,将这个程序通过tftp传到开发板上。然后在服务器上打开命令行进入nginx-rtmp-win32-master目录(注意这个程序的路径一定不要包含中文,如果有什么问题可查看日志),用命令start nginx启动,用tasklist /fi "imagename eq nginx.exe" 查看nginx服务是否启动成功,若启动成功会看到PID会话名,如果启动失败可以查看logs目录下 error.log 文件里的错误信息,根据信息百度查询(我碰到过程序路径含中文导致nginx服务启动不成功)。然后再在开发板上输入./rtmp 192.168.1.101(192.168.1.101是nginx服务器的IP地址)运行推流程序(推流程序一定要在nginx服务启动后才运行)。然后在电脑上打开浏览器输入地址http://192.168.1.101/vod.html启动RTMP网页播放器,在下方的拉流地址输入rtmp://192.168.1.101/live/stream,点击右边播放按钮,等个1秒钟左右就可以看到视频了。

作个比较贴切的比喻,RTSP就像记者自己去抓新闻,这新闻只能在通过自己传播给附近几个人了解。而RTMP就是记者去抓新闻,然后将新闻上交给电视台,电视台播放,然后人民打开电视机转到这个电视台频道就可以收看新闻,这样可以有很多个知道这个新闻。在比喻中,HI3518就是记者,专门采集视频资源,服务器就是电视台,浏览器播放器客户端就是普通民众。在这次使用中,我们用普通电脑搭建nginx服务器,对服务器而言,HI3518就是个客户端,专门推送资源的客户端,所以叫推流端。而浏览器播放视频的客户端就是从服务器拉视频,所以叫拉流端,也叫收流器。

同样,运行这个程序时也生成了一些文件有便于我们调试理解,主要有hi3518的调试输出记录文件及wireshark抓包文件。

以下就是个人自画的RTMP协议

可以发现HI3518作为客户端主动发起请求,不像RTSP协议,HI3518作为服务器等待客户端发起请求。按照网上解释的标准RTMP协议有点复杂,一般我们为了通信方便,可以简化成如上图那样,客户端先发C0+C1,服务器回复S0+S1+S2,然后客户端发送C2完成握手。接下来就是控制信息的通信,客户端请求连接直播,服务器回复消息大小和连接成功的消息。然后客户端请求释放控制信息的流并重新创建一条推送流,服务器回复结果,然后客户端申请推流,服务器回复推流开始,客户端开始推送视频数据。

使用librtmp时,解析RTMP地址、握手、建立流媒体链接和AMF编码这块我们都不需要关心,但是数据是如何打包并通过int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) 函数推送的还是得学习一下。

解析RTMP地址主要是用strstr,strchr函数分离字符串,然后字符串指针移位这样就能得到字符串内容,具体见代码注释,而且里面还举例说明了执行某条语句后结果会是怎么样的。类似如下:


   
   
  1. p+= 3; //p="://192.168.1.100/live/stream",移3个,所以p="192.168.1.100/live/stream"
  2. printf( "in RTMP_ParseURL,p2=%s\n",p); //in RTMP_ParseURL,p2=192.168.1.101/live/stream

RTMPPacket类型的结构体定义如下,一个RTMPPacket对应RTMP协议规范里面的一个块(Chunk)。


   
   
  1. typedef struct RTMPPacket
  2. {
  3. uint8_t m_headerType; //块消息头的类型(4种)
  4. uint8_t m_packetType; //消息类型ID(1-7协议控制;8,9音视频;10以后为AMF编码消息)
  5. uint8_t m_hasAbsTimestamp; //时间戳是绝对值还是相对值
  6. int m_nChannel; //块流ID
  7. uint32_t m_nTimeStamp; //时间戳
  8. int32_t m_nInfoField2; //last 4 bytes in a long header,消息流ID
  9. uint32_t m_nBodySize; //消息载荷大小
  10. uint32_t m_nBytesRead; //暂时没用到
  11. RTMPChunk *m_chunk; //暂时没用到
  12. char *m_body; //消息载荷,可分割为多个块载荷
  13. } RTMPPacket;
RTMP_SendPacket函数

   
   
  1. //queue:TRUE为放进发送队列,FALSE是不放进发送队列,直接发送
  2. int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue)
  3. {
  4. const RTMPPacket *prevPacket;
  5. uint32_t last = 0; //上一个块的时间戳
  6. int nSize; //消息载荷大小,可分割为多个块载荷大小
  7. int hSize, cSize; //块头大小,块基本头大小增量
  8. char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c;
  9. //header:指向块头起始位置,hend:指向块头结束位置
  10. uint32_t t; //相对时间戳
  11. char *buffer, *tbuf = NULL, *toff = NULL; //buffer:指向消息载荷
  12. int nChunkSize; //块载荷大小
  13. int tlen;
  14. if (packet->m_nChannel >= r->m_channelsAllocatedOut)
  15. {
  16. int n = packet->m_nChannel + 10;
  17. RTMPPacket **packets = realloc(r->m_vecChannelsOut, sizeof(RTMPPacket*) * n);
  18. if (!packets)
  19. {
  20. free(r->m_vecChannelsOut);
  21. r->m_vecChannelsOut = NULL;
  22. r->m_channelsAllocatedOut = 0;
  23. return FALSE;
  24. }
  25. r->m_vecChannelsOut = packets;
  26. memset(r->m_vecChannelsOut + r->m_channelsAllocatedOut, 0, sizeof(RTMPPacket*) * (n - r->m_channelsAllocatedOut));
  27. r->m_channelsAllocatedOut = n;
  28. }
  29. prevPacket = r->m_vecChannelsOut[packet->m_nChannel];
  30. //不是完整块消息头(即不是11字节的块消息头)
  31. if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE)
  32. {
  33. /* compress a bit by using the prev packet's attributes */
  34. if (prevPacket->m_nBodySize == packet->m_nBodySize
  35. && prevPacket->m_packetType == packet->m_packetType
  36. && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM)
  37. packet->m_headerType = RTMP_PACKET_SIZE_SMALL;
  38. if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp
  39. && packet->m_headerType == RTMP_PACKET_SIZE_SMALL)
  40. packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM;
  41. last = prevPacket->m_nTimeStamp;
  42. }
  43. //非法
  44. if (packet->m_headerType > 3) /* sanity */
  45. {
  46. RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.",
  47. ( unsigned char)packet->m_headerType);
  48. return FALSE;
  49. }
  50. //nSize暂时设置为块头大小;packetSize[] = { 12, 8, 4, 1 }
  51. nSize = packetSize[packet->m_headerType];
  52. hSize = nSize; //块头大小初始化
  53. cSize = 0;
  54. //相对时间戳,当块时间戳与上一个块时间戳的差值
  55. t = packet->m_nTimeStamp - last;
  56. if (packet->m_body)
  57. {
  58. //m_body是指向载荷数据首地址的指针,“-”号用于指针前移
  59. //header:块头起始位置
  60. header = packet->m_body - nSize;
  61. hend = packet->m_body; //hend:块头结束位置
  62. }
  63. else
  64. {
  65. header = hbuf + 6;
  66. hend = hbuf + sizeof(hbuf);
  67. }
  68. if (packet->m_nChannel > 319) //当块流ID大于319时 ,块基本头是3个字节
  69. cSize = 2;
  70. else if (packet->m_nChannel > 63) //当块流ID大于63时,块基本头是2个字节
  71. cSize = 1;
  72. if (cSize)
  73. {
  74. header -= cSize; //header指针指块头起始位置,“-”号用于指针前移
  75. hSize += cSize; //当cSize不为0时,块头需要进行扩展,默认的块基本头为1字节,但是也可能是2字节或3字节
  76. }
  77. //相对时间戳大于0xffffff,此时需要使用ExtendTimeStamp
  78. if (t >= 0xffffff)
  79. {
  80. header -= 4;
  81. hSize += 4;
  82. RTMP_Log(RTMP_LOGWARNING, "Larger timestamp than 24-bit: 0x%x", t);
  83. }
  84. hptr = header;
  85. c = packet->m_headerType << 6; //把块基本头的fmt类型左移6位。
  86. switch (cSize)
  87. {
  88. case 0: //把块基本头的低6位设置成块流ID
  89. c |= packet->m_nChannel;
  90. break;
  91. case 1: //同理,但低6位设置成000000
  92. break;
  93. case 2: //同理,但低6位设置成000001
  94. c |= 1;
  95. break;
  96. }
  97. *hptr++ = c; //可以拆分成两句*hptr=c;hptr++,此时hptr指向第2个字节
  98. if (cSize) //cSize>0,即块基本头大于1字节
  99. {
  100. int tmp = packet->m_nChannel - 64; //将要放到第2字节的内容tmp
  101. *hptr++ = tmp & 0xff; //获取低位存储于第2字节
  102. if (cSize == 2) //块基本头是最大的3字节时
  103. *hptr++ = tmp >> 8; //获取高位存储于第三个字节(注意:排序使用大端序列,和主机相反)
  104. }
  105. //块消息头一共有4种,包含的字段数不同,nSize>1,块消息头存在。
  106. if (nSize > 1)
  107. {
  108. //块消息头的最开始三个字节为时间戳,返回值hptr=hptr+3
  109. hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t);
  110. }
  111. //如果块消息头包括MessageLength+MessageTypeID(4字节)
  112. if (nSize > 4)
  113. {
  114. hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize); //消息长度,为消息载荷AMF编码后的长度
  115. *hptr++ = packet->m_packetType; //消息类型ID
  116. }
  117. if (nSize > 8)
  118. hptr += EncodeInt32LE(hptr, packet->m_nInfoField2);
  119. //相对时间戳大于0xffffff,此时需要使用ExtendTimeStamp
  120. if (t >= 0xffffff)
  121. hptr = AMF_EncodeInt32(hptr, hend, t);
  122. nSize = packet->m_nBodySize; //消息载荷大小
  123. buffer = packet->m_body; //消息载荷指针
  124. nChunkSize = r->m_outChunkSize; //块大小,默认128字节
  125. RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket,nSize);
  126. /* send all chunks in one HTTP request */ //使用HTTP
  127. if (r->Link.protocol & RTMP_FEATURE_HTTP)
  128. {
  129. //nSize:消息载荷大小;nChunkSize:块载荷大小
  130. //例nSize:307,nChunkSize:128;
  131. //可分为(307+128-1)/128=3个
  132. //为什么减1?因为除法会只取整数部分!
  133. int chunks = (nSize+nChunkSize -1) / nChunkSize;
  134. if (chunks > 1)
  135. {
  136. //消息分n块后总的开销:
  137. //n个块基本头,1个块消息头,1个消息载荷,这里是没有扩展时间戳的情况
  138. //实际中只有第一个块是完整的,剩下的只有块基本头
  139. tlen = chunks * (cSize + 1) + nSize + hSize;
  140. tbuf = malloc(tlen);
  141. if (!tbuf)
  142. return FALSE;
  143. toff = tbuf;
  144. }
  145. }
  146. while (nSize + hSize)
  147. {
  148. int wrote;
  149. //消息载荷小于块载荷(不用分块)
  150. if (nSize < nChunkSize)
  151. nChunkSize = nSize;
  152. RTMP_LogHexString(RTMP_LOGDEBUG2, ( uint8_t *)header, hSize);
  153. RTMP_LogHexString(RTMP_LOGDEBUG2, ( uint8_t *)buffer, nChunkSize);
  154. if (tbuf)
  155. {
  156. memcpy(toff, header, nChunkSize + hSize);
  157. toff += nChunkSize + hSize;
  158. }
  159. else
  160. {
  161. wrote = WriteN(r, header, nChunkSize + hSize);
  162. if (!wrote)
  163. return FALSE;
  164. }
  165. nSize -= nChunkSize;
  166. buffer += nChunkSize;
  167. hSize = 0;
  168. //如果消息没有发完
  169. if (nSize > 0)
  170. {
  171. header = buffer - 1;
  172. hSize = 1;
  173. if (cSize)
  174. {
  175. header -= cSize;
  176. hSize += cSize;
  177. }
  178. if (t >= 0xffffff)
  179. {
  180. header -= 4;
  181. hSize += 4;
  182. }
  183. *header = ( 0xc0 | c);
  184. if (cSize)
  185. {
  186. int tmp = packet->m_nChannel - 64;
  187. header[ 1] = tmp & 0xff;
  188. if (cSize == 2)
  189. header[ 2] = tmp >> 8;
  190. }
  191. if (t >= 0xffffff)
  192. {
  193. char* extendedTimestamp = header + 1 + cSize;
  194. AMF_EncodeInt32(extendedTimestamp, extendedTimestamp + 4, t);
  195. }
  196. }
  197. }
  198. if (tbuf)
  199. {
  200. int wrote = WriteN(r, tbuf, toff-tbuf);
  201. free(tbuf);
  202. tbuf = NULL;
  203. if (!wrote)
  204. return FALSE;
  205. }
  206. /* we invoked a remote method */
  207. if (packet->m_packetType == RTMP_PACKET_TYPE_INVOKE)
  208. {
  209. AVal method;
  210. char *ptr;
  211. ptr = packet->m_body + 1;
  212. AMF_DecodeString(ptr, &method);
  213. RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val);
  214. /* keep it in call queue till result arrives */
  215. if ( queue)
  216. {
  217. int txn;
  218. ptr += 3 + method.av_len;
  219. txn = ( int)AMF_DecodeNumber(ptr);
  220. AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn);
  221. }
  222. }
  223. if (!r->m_vecChannelsOut[packet->m_nChannel])
  224. r->m_vecChannelsOut[packet->m_nChannel] = malloc( sizeof(RTMPPacket));
  225. memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket));
  226. return TRUE;
  227. }

一路跟踪RTMP_SendPacket函数下去会发现RTMP_SendPacket-->WriteN-->RTMPSockBuf_Send-->send,最终会调用网络编程的send函数。

整个程序也是生产者消费者模式,SAMPLE_VENC_1080P_CLASSIC线程产生数据放到环形缓冲区,主线程从环形缓冲区取出数据进入rtmp_sender_write_video_frame函数将H264的NALU打包进消息载荷.

rtmp_sender_write_video_frame函数如下:


   
   
  1. // @brief send video frame, now only H264 supported
  2. // @param [in] rtmp_sender handler
  3. // @param [in] data : video data, (Full frames are required)
  4. // @param [in] size : video data size
  5. // @param [in] dts_us : decode timestamp of frame
  6. // @param [in] key : key frame indicate, [0: non key] [1: key]
  7. //rtmp_sender_write_video_frame(prtmp,ringinfo.buffer, ringinfo.size,timeCount, 0,start_time);
  8. /*
  9. 功能:按FLV格式填充内容并发送出去
  10. 参数:
  11. handle: in/our,rtmp结构体,根据rtmp结构体里的配置来进行不同的处理
  12. data: in,要发送的视频数据
  13. size: in,要发送的视频数据的大小
  14. dts_us: in,时间戳
  15. key: 是否是关键帧,这里不需要,因为在函数里通过读取nalu[0]来判断是否是关键帧
  16. start_time: 时间戳的起始时间
  17. 返回值:成功返回0,失败返回1
  18. */
  19. int rtmp_sender_write_video_frame(void *handle,uint8_t *data,int size,uint64_t dts_us,int key,uint32_t start_time)
  20. {
  21. uint8_t * buf;
  22. uint8_t * buf_offset;
  23. int total;
  24. uint32_t ts;
  25. uint32_t nal_len;
  26. uint32_t nal_len_n;
  27. uint8_t *nal;
  28. uint8_t *nal_n;
  29. char *output ;
  30. uint32_t offset = 0;
  31. uint32_t body_len;
  32. uint32_t output_len;
  33. RTMP_XIECC *rtmp_xiecc;
  34. RTMP *rtmp;
  35. buf = data;
  36. buf_offset = data;
  37. total = size;
  38. ts = ( uint32_t)dts_us;
  39. rtmp_xiecc = (RTMP_XIECC *)handle;
  40. if ((data == NULL) || (rtmp_xiecc == NULL))
  41. {
  42. return 1;
  43. }
  44. rtmp = rtmp_xiecc->rtmp;
  45. //printf("ts is %d\n",ts);
  46. while ( 1)
  47. {
  48. //ts = RTMP_GetTime() - start_time; //
  49. //by ssy
  50. offset = 0;
  51. nal = get_nal(&nal_len, &buf_offset, buf, total);
  52. if (nal == NULL)
  53. break;
  54. if (nal[ 0] == 0x67) //打包sps,pps这里没有if (nal[0] == 0x68),但这里直接处理了两个nal即把nal[0] == 0x68也处理了
  55. {
  56. if (rtmp_xiecc->video_config_ok > 0)
  57. {
  58. continue; //only send video sequence set one time
  59. }
  60. nal_n = get_nal(&nal_len_n, &buf_offset, buf, total); //get pps//sps后面一般是紧跟pps 0x68
  61. if (nal_n == NULL)
  62. {
  63. RTMP_Log(RTMP_LOGERROR, "No Nal after SPS");
  64. break;
  65. }
  66. /*
  67. FLVtag:其实就是消息=消息头flvtag header+载荷
  68. */
  69. body_len = nal_len + nal_len_n + 16; //h264封装成rtmp包:16字节信息头+数据
  70. output_len = body_len + FLV_TAG_HEAD_LEN + FLV_PRE_TAG_LEN;
  71. //0x67+0x68两帧的长度+16字节+FLV_TAG_HEAD_LEN+FLV_PRE_TAG_LEN
  72. output = malloc(output_len);
  73. /// flv tag header
  74. //消息头=标志消息类型的Message Type ID(1byte)+标志载荷长度的Payload Length(3byte)+标识时间戳的Timestamp(4byte)+标识消息所属媒体流的Stream ID(3byte)
  75. output[offset++] = 0x09; //tagtype video
  76. //output[0]=0x09消息类型,9代表视频,1字节
  77. output[offset++] = ( uint8_t)(body_len >> 16); //data len
  78. output[offset++] = ( uint8_t)(body_len >> 8); //data len
  79. output[offset++] = ( uint8_t)(body_len); //data len
  80. //output[1-3]有效数据长度,3字节
  81. output[offset++] = ( uint8_t)(ts >> 16); //time stamp
  82. output[offset++] = ( uint8_t)(ts >> 8); //time stamp
  83. output[offset++] = ( uint8_t)(ts); //time stamp
  84. output[offset++] = ( uint8_t)(ts >> 24); //time stamp
  85. //output[4-7]时间戳,4字节
  86. output[offset++] = 0x00; //stream id 0
  87. output[offset++] = 0x00; //stream id 0
  88. output[offset++] = 0x00; //stream id 0
  89. //output[8-10]媒体流ID,3字节
  90. //
  91. ///flv VideoTagHeader///
  92. ///flvvideotagheader/
  93. //sps,pps填0x17 00,composit time无效填0
  94. output[offset++] = 0x17; //key frame, AVC
  95. //output[11]高4位表示帧类型,这里是关键帧 1,低4位表示编码模式,这里是AVC,7
  96. output[offset++] = 0x00; //avc sequence header
  97. //output[12]
  98. output[offset++] = 0x00; //composit time ??????????
  99. output[offset++] = 0x00; // composit time
  100. output[offset++] = 0x00; //composit time
  101. //output[13-15]AVC时,composit time没有意义,所以全填为0
  102. /
  103. /flvvideotagbody/
  104. //flv VideoTagBody --AVCDecoderCOnfigurationRecord
  105. output[offset++] = 0x01; //configurationversion//sps,pps专用,填版本号1
  106. output[offset++] = nal[ 1]; //avcprofileindication
  107. output[offset++] = nal[ 2]; //profilecompatibilty
  108. output[offset++] = nal[ 3]; //avclevelindication
  109. output[offset++] = 0xff; //reserved + lengthsizeminusone//一般填ff
  110. output[offset++] = 0xe1; //numofsequenceset//一般填e1
  111. //sps len
  112. output[offset++] = ( uint8_t)(nal_len >> 8); //sequence parameter set length high 8 bits
  113. output[offset++] = ( uint8_t)(nal_len); //sequence parameter set length low 8 bits
  114. //sps data
  115. memcpy(output + offset, nal, nal_len); //H264 sequence parameter set
  116. offset += nal_len;
  117. //pps一个数,一般为1
  118. output[offset++] = 0x01; //numofpictureset
  119. //pps len
  120. output[offset++] = ( uint8_t)(nal_len_n >> 8); //picture parameter set length high 8 bits
  121. output[offset++] = ( uint8_t)(nal_len_n); //picture parameter set length low 8 bits
  122. //pps data
  123. memcpy(output + offset, nal_n, nal_len_n); //H264 picture parameter set
  124. /
  125. //
  126. //no need set pre_tag_size ,RTMP NO NEED
  127. // flv test
  128. /*
  129. offset += nal_len_n;
  130. uint32_t fff = body_len + FLV_TAG_HEAD_LEN;
  131. output[offset++] = (uint8_t)(fff >> 24); //data len
  132. output[offset++] = (uint8_t)(fff >> 16); //data len
  133. output[offset++] = (uint8_t)(fff >> 8); //data len
  134. output[offset++] = (uint8_t)(fff); //data len
  135. */
  136. RTMP_Write(rtmp, output, output_len);
  137. //RTMP Send out
  138. free(output);
  139. rtmp_xiecc->video_config_ok = 1;
  140. continue;
  141. }
  142. if (nal[ 0] == 0x65)
  143. {
  144. body_len = nal_len + 5 + 4; //flv VideoTagHeader + NALU length
  145. output_len = body_len + FLV_TAG_HEAD_LEN + FLV_PRE_TAG_LEN;
  146. output = malloc(output_len);
  147. // flv tag header
  148. output[offset++] = 0x09; //tagtype video
  149. output[offset++] = ( uint8_t)(body_len >> 16); //data len
  150. output[offset++] = ( uint8_t)(body_len >> 8); //data len
  151. output[offset++] = ( uint8_t)(body_len); //data len
  152. output[offset++] = ( uint8_t)(ts >> 16); //time stamp
  153. output[offset++] = ( uint8_t)(ts >> 8); //time stamp
  154. output[offset++] = ( uint8_t)(ts); //time stamp
  155. output[offset++] = ( uint8_t)(ts >> 24); //time stamp
  156. output[offset++] = 0x00; //stream id 0
  157. output[offset++] = 0x00; //stream id 0
  158. output[offset++] = 0x00; //stream id 0
  159. //flv VideoTagHeader
  160. output[offset++] = 0x17; //key frame, AVC
  161. output[offset++] = 0x01; //avc NALU unit
  162. output[offset++] = 0x00; //composit time ??????????
  163. output[offset++] = 0x00; // composit time
  164. output[offset++] = 0x00; //composit time
  165. output[offset++] = ( uint8_t)(nal_len >> 24); //nal length
  166. output[offset++] = ( uint8_t)(nal_len >> 16); //nal length
  167. output[offset++] = ( uint8_t)(nal_len >> 8); //nal length
  168. output[offset++] = ( uint8_t)(nal_len); //nal length
  169. memcpy(output + offset, nal, nal_len);
  170. //no need set pre_tag_size ,RTMP NO NEED
  171. /*
  172. offset += nal_len;
  173. uint32_t fff = body_len + FLV_TAG_HEAD_LEN;
  174. output[offset++] = (uint8_t)(fff >> 24); //data len
  175. output[offset++] = (uint8_t)(fff >> 16); //data len
  176. output[offset++] = (uint8_t)(fff >> 8); //data len
  177. output[offset++] = (uint8_t)(fff); //data len
  178. */
  179. RTMP_Write(rtmp, output, output_len);
  180. //RTMP Send out
  181. free(output);
  182. continue;
  183. }
  184. if ((nal[ 0] & 0x1f) == 0x01)
  185. {
  186. body_len = nal_len + 5 + 4; //flv VideoTagHeader + NALU length
  187. output_len = body_len + FLV_TAG_HEAD_LEN + FLV_PRE_TAG_LEN;
  188. output = malloc(output_len);
  189. // flv tag header
  190. output[offset++] = 0x09; //tagtype video
  191. output[offset++] = ( uint8_t)(body_len >> 16); //data len
  192. output[offset++] = ( uint8_t)(body_len >> 8); //data len
  193. output[offset++] = ( uint8_t)(body_len); //data len
  194. output[offset++] = ( uint8_t)(ts >> 16); //time stamp
  195. output[offset++] = ( uint8_t)(ts >> 8); //time stamp
  196. output[offset++] = ( uint8_t)(ts); //time stamp
  197. output[offset++] = ( uint8_t)(ts >> 24); //time stamp
  198. output[offset++] = 0x00; //stream id 0
  199. output[offset++] = 0x00; //stream id 0
  200. output[offset++] = 0x00; //stream id 0
  201. //flv VideoTagHeader
  202. output[offset++] = 0x27; //not key frame, AVC
  203. output[offset++] = 0x01; //avc NALU unit
  204. output[offset++] = 0x00; //composit time ??????????
  205. output[offset++] = 0x00; // composit time
  206. output[offset++] = 0x00; //composit time
  207. output[offset++] = ( uint8_t)(nal_len >> 24); //nal length
  208. output[offset++] = ( uint8_t)(nal_len >> 16); //nal length
  209. output[offset++] = ( uint8_t)(nal_len >> 8); //nal length
  210. output[offset++] = ( uint8_t)(nal_len); //nal length
  211. memcpy(output + offset, nal, nal_len);
  212. //no need set pre_tag_size ,RTMP NO NEED
  213. /*
  214. offset += nal_len;
  215. uint32_t fff = body_len + FLV_TAG_HEAD_LEN;
  216. output[offset++] = (uint8_t)(fff >> 24); //data len
  217. output[offset++] = (uint8_t)(fff >> 16); //data len
  218. output[offset++] = (uint8_t)(fff >> 8); //data len
  219. output[offset++] = (uint8_t)(fff); //data len
  220. */
  221. RTMP_Write(rtmp, output, output_len);
  222. //RTMP Send out
  223. free(output);
  224. continue;
  225. }
  226. }
  227. return 0;
  228. }

rtmp_sender_write_video_frame这个函数主要是先get_nal取出H264的nal单元,然后根据nal单元是sps/pps、0x65的关键帧还是0x61的其它帧对output数组作不同的填充,最后RTMP_Write函数发送出去。RTMP_Write函数会对output数组进行AMF编码然后利用上面提到的RTMP_SendPacket函数(最终会调用网络编程send函数)将数据发送出去。

现在要解决的是如何给结构体RTMPPacket中的消息载荷m_body赋值,即如何将H264的NALU打包进消息载荷。为了说明打包过程,大家请看下面3张图,结合代码分析:

      

                                                            图1

 

                                                                图2


                                                表1

由于RTMP推送的音视频流的封装形式和FLV格式相似,向FMS等流媒体服务器推送H264和AAC直播流时,需要首先发送"AVC sequence header"和"AAC sequence header"这两项数据包含的是重要的编码信息,没有它们,解码器将无法解码),因此这里的"AVC sequence header"就是用来打包sps和pps的。所以要按FLV格式来把H264内容封装成FLV包。而FLV格式如下:
FLV=FLVheader(9字节)+FLVbody
FLVheader(9字节):是“FLV”开头的9个字节,一般固定
FLVbody=pre_tag_size(4字节,上一个tag的大小)+FLVtag
pre_tag_size(4字节,上一个tag的大小):程序好像没用
FLVtag:其实就是消息=消息头flvtag header+载荷flvxxxtag///程序里填的就是FLVtag
消息头flvtag header=消息类型1byte+载荷长度3byte+时间戳4byte+媒体流ID3byte///sps,pps、关键帧,非关键帧填的都是一样
载荷flvxxxtag:分为flvaudiotag,flvvideotag等,对视频而言就是flvvideotag///sps,pps、关键帧,非关键帧填的都是不同
flvvideotag=flvvideotagheader+flvvideotagbody具体因H264帧不同而不同,见如下分析:

flvvideotag如下:

flvvideotag=flvvideotagheader+flvvideotagbody


sps0x67,pps0x68封装:
载荷flvvideotag
看图1的红框1,对sps,pps而言,帧类型为关键帧=1,编码类型为AVC=7,所以output[offset++] = 0x17; //key frame, AVC
看图1的红框2,因为CodecID=7,所以要看AVCVIDEOPACKET即图2
看图2的红框A,因为现在是sps,pps,所以是AVCPacketType=AVC sequence header=0,output[offset++] = 0x00; //avc sequence header
看图2的红框B,因为AVCPacketType=AVC sequence header=0,所以CompositionTime=0,所以output接下来三个都为0
看图2的红框C,因为AVCPacketType=AVC sequence header=0,所以要找AVCDecoderConfigurationRecord,即图3
看图3,是个表格,看第一行是版本号,填1,所以填output[offset++]=0x01
看表格第2-4行填sps,所以output[offset++] = nal[1]; output[offset++] = nal[2]; output[offset++] = nal[3]; 
看表格第5行为保留,5位全是1
看表格第6行,sps长度,一般为3(11),与第5行的5位凑成0xff,所以output[offset++] = 0xff;
看表格第7行为保留,3位为1
看表格第8行为pps个数,一般为1(00001),与第7行凑成0xe1,所以output[offset++] = 0xe1;
看表格第9行为sps大小和sps的内容,所以output[offset++] = (uint8_t)(nal_len >> 8);output[offset++] = (uint8_t)(nal_len);
sps内容为memcpy(output + offset, nal, nal_len); 
然后偏移sps帧的大小offset += nal_len;
看表格第10行填pps个数,一般为1,output[offset++] = 0x01; 
看表格第11行填pps大小和pps内容,所以output[offset++] = (uint8_t)(nal_len_n >> 8);output[offset++] = (uint8_t)(nal_len_n); 
填pps内容memcpy(output + offset, nal_n, nal_len_n);
最后发送出去RTMP_Write(rtmp, output, output_len);并标记配置完成rtmp_xiecc->video_config_ok = 1;

关键帧0x65封装:
看图1的红框1,对关键帧0x65而言,帧类型为关键帧=1,编码类型为AVC=7,所以output[offset++] = 0x17; //key frame, AVC
看图1的红框2,因为CodecID=7,所以要看AVCVIDEOPACKET即图2
看图2的红框A,因为现在是关键帧0x65,所以是AVCPacketType=AVC NALU=1,output[offset++] = 0x01; //avc NALU unit
看图2的红框B,因为AVCPacketType=AVC NALU=1,我们不想填时间,所以CompositionTime=0,所以output接下来三个都为0
看图2的红框C,因为AVCPacketType=AVC NALU=1,所以要找NALU,而NALU就是4个字节表示NALU长度,后面字节表示NALU内容
所以output[offset++] = (uint8_t)(nal_len >> 24); //nal length 
output[offset++] = (uint8_t)(nal_len >> 16); //nal length 
output[offset++] = (uint8_t)(nal_len >> 8); //nal length 
output[offset++] = (uint8_t)(nal_len); //nal length 
表示NALU长度
memcpy(output + offset, nal, nal_len);表示NALU内容
最后发送出去RTMP_Write(rtmp, output, output_len);


非关键帧nal[0] & 0x1f) == 0x01封装:
看图1的红框1,对关键帧0x01而言,帧类型为非关键帧=2,编码类型为AVC=7,所以output[offset++] = 0x27;  //not key frame, AVC
看图1的红框2,因为CodecID=7,所以要看AVCVIDEOPACKET即图2
看图2的红框A,因为现在是非关键帧0x01,还是AVCPacketType=AVC NALU=1,output[offset++] = 0x01; //avc NALU unit
看图2的红框B,因为AVCPacketType=AVC NALU=1,我们不想填时间,所以CompositionTime=0,所以output接下来三个都为0
看图2的红框C,因为AVCPacketType=AVC NALU=1,所以要找NALU,而NALU就是4个字节表示NALU长度,后面字节表示NALU内容
所以output[offset++] = (uint8_t)(nal_len >> 24); //nal length 
output[offset++] = (uint8_t)(nal_len >> 16); //nal length 
output[offset++] = (uint8_t)(nal_len >> 8); //nal length 
output[offset++] = (uint8_t)(nal_len); //nal length 
表示NALU长度
memcpy(output + offset, nal, nal_len);表示NALU内容

最后发送出去RTMP_Write(rtmp, output, output_len);

Hi3518+RTMP工程文件:Hi3518+RTMP


  •                     <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#csdnc-thumbsup"></use>
                        </svg><span class="name">点赞</span>
                        <span class="count">1</span>
                        </a></li>
                        <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-Collection-G"></use>
                        </svg><span class="name">收藏</span></a></li>
                        <li class="tool-item tool-active is-share"><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;1582594662_002&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-fenxiang"></use>
                        </svg>分享</a></li>
                        <!--打赏开始-->
                                                <!--打赏结束-->
                                                <li class="tool-item tool-more">
                            <a>
                            <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                            </a>
                            <ul class="more-box">
                                <li class="item"><a class="article-report">文章举报</a></li>
                            </ul>
                        </li>
                                            </ul>
                </div>
                            </div>
            <div class="person-messagebox">
                <div class="left-message"><a href="https://blog.csdn.net/zhanshenrui">
                    <img src="https://profile.csdnimg.cn/0/C/B/3_zhanshenrui" class="avatar_pic" username="zhanshenrui">
                                            <img src="https://g.csdnimg.cn/static/user-reg-year/1x/12.png" class="user-years">
                                    </a></div>
                <div class="middle-message">
                                        <div class="title"><span class="tit"><a href="https://blog.csdn.net/zhanshenrui" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">zhanshenrui</a></span>
                                            </div>
                    <div class="text"><span>发布了6 篇原创文章</span> · <span>获赞 15</span> · <span>访问量 1万+</span></div>
                </div>
                                <div class="right-message">
                                            <a href="https://im.csdn.net/im/main.html?userName=zhanshenrui" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
                        </a>
                                                            <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                    </div>
                            </div>
                    </div>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值