前面的部分讲了关于RTSP连接的交互过程,在RTSP推流的过程中,RTSP协议只是做一个控制作用,底层真正进行传输的流媒体协议还是RTP协议。做这一部分主要是要先了解RTP协议的封装格式,这里我不详细讲了,网上有很多博客都有讲,我这里主要是讲一下实现方式。
在建立了RTSP连接之后,就是在客户端发回PLAY指令之后,在setup阶段被设置的回调函数会被调用,下面是回调函数的代码

unsigned int RtpSend(unsigned int u32Rtp, unsigned char *pData, int s32DataSize, unsigned int u32TimeStamp)
{int s32NalSize = 0;int nalhead_pos = 0,naltail_pos = 0;HndRtp hRtp = (HndRtp)u32Rtp;unsigned char * nalubuffer = NULL;hRtp->u32TimeStampCurr = u32TimeStamp;while(nalhead_pos<s32DataSize){if(pData[nalhead_pos++] == 0x00 && pData[nalhead_pos++] == 0x00) {  if(pData[nalhead_pos++] == 0x00 && pData[nalhead_pos++] == 0x01 ){goto gotnal_head;}elsecontinue;}else continue;gotnal_head:naltail_pos = nalhead_pos;  while (naltail_pos<s32DataSize)  {  if(pData[naltail_pos++] == 0x00 && pData[naltail_pos++] == 0x00 ){  if(pData[naltail_pos++] == 0x00 &&pData[naltail_pos++] == 0x01){    s32NalSize = (naltail_pos-4)-nalhead_pos;break;}}  }if(nalhead_pos<s32DataSize && naltail_pos<s32DataSize){nalubuffer=(unsigned char*)malloc(s32NalSize);memset(nalubuffer,0,s32NalSize);memcpy(nalubuffer,pData+nalhead_pos,s32NalSize);//SendNalu264(hRtp, nalubuffer,s32NalSize);test_SendNalu264(hRtp, nalubuffer,s32NalSize);free(nalubuffer);nalhead_pos = naltail_pos-4;}if(nalhead_pos<s32DataSize && naltail_pos>=s32DataSize){s32NalSize = s32DataSize -nalhead_pos;nalubuffer=(unsigned char*)malloc(s32NalSize);memset(nalubuffer,0,s32NalSize);memcpy(nalubuffer,pData+nalhead_pos,s32NalSize);//SendNalu264(hRtp, nalubuffer,s32NalSize);test_SendNalu264(hRtp, nalubuffer,s32NalSize);free(nalubuffer);nalhead_pos = naltail_pos;}}//whilereturn 0;
}

这个函数主要是把每帧里面的00 00 00 01开始标志去掉,然后把数据放到test_SendNalu264()这个函数进行封装。码流数据是从底层取出来放在一个环形缓冲区里面的,这个我单独写了一个线程,实现方式比较简单,这边不单独介绍了,主要说一下test_SendNalu264()这个封包函数吧

static int test_SendNalu264(HndRtp hRtp, unsigned char *pNalBuf, int s32NalBufSize)
{unsigned char *nalu_buf;nalu_buf = pNalBuf;unsigned char *pSendBuf;int ret = 0;int len_sendbuf;unsigned char FirstNaluBytes;int fu_pack_num;        /* nalu 需要分片发送时分割的个数 */int last_fu_pack_size;  /* 最后一个分片的大小 */int fu_seq;             /* fu-A 序号 */struct timeval now;unsigned long long ts;pSendBuf = (unsigned char *)calloc(MAX_RTP_PKT_LENGTH + 100, sizeof(unsigned char));if(NULL == pSendBuf){ret = -1;goto cleanup;}gettimeofday(&now,NULL);ts = (now.tv_sec*1000 + now.tv_usec/1000);if(firstflag){F_ts = ts;firstflag = 0;ts = ts *90;printf("Run in set firstflag !!!!!\n");}//毫秒elsets = (ts - F_ts)*90;//ts_current += (90000 / 25);  /* 90000 / 25 = 3600 */hRtp->pRtpFixedHdr = (StRtpFixedHdr *)pSendBuf;hRtp->pRtpFixedHdr->u4CSrcLen   = 0;hRtp->pRtpFixedHdr->u1Externsion  = 0;hRtp->pRtpFixedHdr->u1Padding   =0;hRtp->pRtpFixedHdr->u7Payload   = H264;hRtp->pRtpFixedHdr->u2Version   = 2;hRtp->pRtpFixedHdr->u32TimeStamp  = BigLittleSwap32(ts);hRtp->pRtpFixedHdr->u32SSrc = hRtp->u32SSrc;FirstNaluBytes = *pNalBuf;if(s32NalBufSize < 1 ||(FirstNaluBytes&0x1f) == 0x08 ||(FirstNaluBytes&0x1f) == 0x07){//ts = ts -2*(90000 / 25); goto cleanup;}/* if(S_ts){ts = S_ts;S_ts = 0;}*//* if((FirstNaluBytes&0x1f) == 0x07){hRtp->pRtpFixedHdr->u1Marker    = 0;//hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16((hRtp->u16SeqNum++));S_ts = ts;if(sps_len>0){   hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);hRtp->u16SeqNum++;memcpy(pSendBuf+12, sps_tmp, sps_len);if(sendto(hRtp->s32Sock, pSendBuf, sps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0){ret = -1;goto cleanup;}//printf("Send sps success!!\n");//printf("send sps u16SeqNum = %u\n",hRtp->u16SeqNum);}if(pps_len>0){        hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);hRtp->u16SeqNum++;memcpy(pSendBuf+12, pps_tmp, pps_len);if(sendto(hRtp->s32Sock, pSendBuf, pps_len+12, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0){ret = -1;goto cleanup;}//printf("Send pps success !!\n");//printf("send pps u16SeqNum = %u\n",hRtp->u16SeqNum);}goto cleanup;}*/if(s32NalBufSize <= MAX_RTP_PKT_LENGTH){hRtp->pRtpFixedHdr->u1Marker    = 1;hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);hRtp->u16SeqNum++;hRtp->pNaluHdr                  = (StNaluHdr *)(pSendBuf + 12);hRtp->pNaluHdr->u1F             = (FirstNaluBytes & 0x80) >> 7;hRtp->pNaluHdr->u2Nri           = (FirstNaluBytes & 0x60) >> 5;hRtp->pNaluHdr->u5Type          = FirstNaluBytes & 0x1f;memcpy(pSendBuf + 13, nalu_buf + 1, s32NalBufSize - 1);len_sendbuf = 12 + s32NalBufSize;if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0){ret = -1;goto cleanup;}//printf("send one packet u16SeqNum = %u\n",hRtp->u16SeqNum);goto cleanup;}else{fu_pack_num = s32NalBufSize % MAX_RTP_PKT_LENGTH ? (s32NalBufSize / MAX_RTP_PKT_LENGTH + 1) : s32NalBufSize / MAX_RTP_PKT_LENGTH;last_fu_pack_size = s32NalBufSize % MAX_RTP_PKT_LENGTH ? s32NalBufSize % MAX_RTP_PKT_LENGTH : MAX_RTP_PKT_LENGTH;fu_seq = 0;//printf("fu_pack_num = %d \n",fu_pack_num);//printf("Send for FU-A !!!\n");for (fu_seq = 0; fu_seq < fu_pack_num; fu_seq++) {if (fu_seq == 0) {hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);hRtp->u16SeqNum++;hRtp->pRtpFixedHdr->u1Marker    = 0;//指定fu indicator位置hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);hRtp->pFuInd->u1F       = (FirstNaluBytes & 0x80) >> 7;hRtp->pFuInd->u2Nri     = (FirstNaluBytes & 0x60) >> 5;hRtp->pFuInd->u5Type    = 28;//指定fu header位置hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);hRtp->pFuHdr->u1S       = 1;hRtp->pFuHdr->u1E       = 0;hRtp->pFuHdr->u1R       = 0;hRtp->pFuHdr->u5Type    = FirstNaluBytes & 0x1f;memcpy(pSendBuf + 14, nalu_buf + 1, MAX_RTP_PKT_LENGTH - 1);len_sendbuf = 12 + 2 + (MAX_RTP_PKT_LENGTH - 1);if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0){ret = -1;goto cleanup;}//printf("send FU-A First packet u16SeqNum = %u\n",hRtp->u16SeqNum);//printf("Send FU-A First packet!! TimeStamp = %ld fu_seq = %d ts_current = %d\n",hRtp->pRtpFixedHdr->u32TimeStamp,fu_seq,ts_current);}else if (fu_seq < fu_pack_num - 1) { hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);hRtp->u16SeqNum++;hRtp->pRtpFixedHdr->u1Marker    = 0;//指定fu indicator位置hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);hRtp->pFuInd->u1F       = (FirstNaluBytes & 0x80) >> 7;hRtp->pFuInd->u2Nri     = (FirstNaluBytes & 0x60) >> 5;hRtp->pFuInd->u5Type    = 28;//指定fu header位置hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);hRtp->pFuHdr->u1S       = 0;hRtp->pFuHdr->u1E       = 0;hRtp->pFuHdr->u1R       = 0;hRtp->pFuHdr->u5Type    = FirstNaluBytes & 0x1f;memcpy(pSendBuf + 14, nalu_buf + MAX_RTP_PKT_LENGTH * fu_seq, MAX_RTP_PKT_LENGTH); len_sendbuf = 12 + 2 + MAX_RTP_PKT_LENGTH;if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0){ret = -1;goto cleanup;}//printf("send FU-A mid packet u16SeqNum = %u\n",hRtp->u16SeqNum);//printf("Send FU-A mid Packet !!! TimeStamp = %ld fu_seq = %d ts_current = %d\n",hRtp->pRtpFixedHdr->u32TimeStamp,fu_seq,ts_current);}else{hRtp->pRtpFixedHdr->u16SeqNum   = BigLittleSwap16(hRtp->u16SeqNum);hRtp->u16SeqNum++;hRtp->pRtpFixedHdr->u1Marker    = 1;//指定fu indicator位置hRtp->pFuInd            = (StFuIndicator *)(pSendBuf + 12);hRtp->pFuInd->u1F       = (FirstNaluBytes & 0x80) >> 7;hRtp->pFuInd->u2Nri     = (FirstNaluBytes & 0x60) >> 5;hRtp->pFuInd->u5Type    = 28;//指定fu header位置hRtp->pFuHdr            = (StFuHdr *)(pSendBuf + 13);hRtp->pFuHdr->u1S       = 0;hRtp->pFuHdr->u1E       = 1;hRtp->pFuHdr->u1R       = 0;hRtp->pFuHdr->u5Type    = FirstNaluBytes & 0x1f;memcpy(pSendBuf + 14, nalu_buf + MAX_RTP_PKT_LENGTH * fu_seq, last_fu_pack_size);len_sendbuf = 12 + 2 + last_fu_pack_size;if(sendto(hRtp->s32Sock, pSendBuf, len_sendbuf, 0, (struct sockaddr *)&hRtp->stServAddr, sizeof(hRtp->stServAddr)) < 0){ret = -1;goto cleanup;}//printf("Send FU-A last packet u16SeqNum = %u\n",hRtp->u16SeqNum);//printf("Send FU-A last Packet !!! TimeStamp = %ld fu_seq = %d ts_current = %d\n",hRtp->pRtpFixedHdr->u32TimeStamp,fu_seq,ts_current);}}goto cleanup;}cleanup:if(pSendBuf){free((void *)pSendBuf);pSendBuf = NULL;}//printf("\n");//fflush(stdout);return ret;
}

因为一次性发送的数据长度有限制,所以长度大于1400的帧采用分片FU-A的方式来封装,小于1400的以单包来封,上面的代码是根据官方的封包要求来写的,单包封装只需要给原始码流打上NALU头之后就可以发送了,分片比较复杂,在打了NALU头之后还要打上FU indicator和FU header,要注意的主要是FU header头的S E 标志位,S代表这一片是这一帧的第一片,E代表这一片是这一帧的最后一片,我代码里面是把第一片,中间片,和最后一片分来来写的,这样清楚一点。
有两点要注意一下

  1. 时间戳
    时间戳一定要打对,不然很有可能播不出来,两个时间戳之间应该差采样率/帧率,最好获取系统时间来打,我试过固定的递增时间戳也是不行的
  2. 关于 SPS PPS的发送
    上面函数有一部分注释掉了,是因为我在RTSP连接的时候,已经将BASE64编码后的SPS PPS发送到客户端的,所以后面不需要再发,SPS PPS只需要发一次,有一点要注意,如果你是在这个函数发SPS PPS,这两包的序列号要一致。

这一套RTSP直播的DEMO我已经放在我的下载资源里面了,有兴趣可以看一下。在Realtek底层下运行的,要换底层的话只需要将初始化及获取底层数据那一块的接口进行更换就可以了,注意不同的底层编码芯片出来的数据结构有可能不一样,可能需要调整封包逻辑。

网络摄像头RTSP直播方案(三)相关推荐

  1. 网络摄像头Rtsp直播方案(一)

    前段时间写完了RTMP的直播方案,因为是基于librtmp的库来实现的,所以比较简单.之后花了一个月吧,参照海思的rtsp推流框架,慢慢的写了一个基于RealTek为底层的网络摄像头Rtsp直播功能的 ...

  2. 网络摄像头Rtsp直播方案(二)

    上一部分说了Socket通讯的一些东西,这部分就结合代码来说说RTSP交互的过程. 在放实现代码之前先说说概念上的东西吧,关于RTSP这个流媒体网络协议.RTSP作为一个应用层协议,提供了一个可供扩展 ...

  3. ffmpeg api推流,谷歌浏览器播放大华、海康威视网络摄像头rtsp视频流方案(hls、m3u8、flv、webrtc、srs、nginx、nginx-rtmp、rtmp)比较

    ffmpeg api推流,谷歌浏览器播放大华.海康威视网络摄像头rtsp视频流方案(hls.m3u8.flv.webrtc.srs.nginx.nginx-rtmp.rtmp)比较 将网络摄像头视频流 ...

  4. 摄像头网页服务器,网络摄像头实现直播的方法 在网页浏览器播放等于可以在网页传播...

    网络摄像头实现直播的方法,可以在网页浏览器播放,可以发送给你的朋友,可以放到你的官网去增加一条播放链接,可以在网页文章里增加一条播放链接.怎么实现呢? 需要的准备如下: 1.网络摄像头一个 2.电脑一 ...

  5. 安防摄像头互联网直播方案LiveGBS设计文档

    LiveGBS设计文档 一.介绍 28181协议全称为GB/T28181<安全防范视频监控联网系统信息传输.交换.控制技术要求>,是由公安部科技信息化局提出,由全国安全防范报警系统标准化技 ...

  6. 网络摄像头RTSP视频流WEB端实时播放实现方案

    IPC视频流怎么实时在WEB浏览器播放,视频流格式是RTSP. 下面我整理了自己实现的方案以及网上看到的一些方案 一.FFmpeg + nginx 将转 hls 通过 video.js 在支持h5浏览 ...

  7. 摄像头视频直播方案比较之方案三:好望云

    前面给大家分享了两家传统安防大厂的视频接入云平台,这次本想继续分享一下宇视的相关产品,结果找了一下,发现宇视竟然没有同类产品,真是有点意外.不过没有关系,今天我们来分享一下一位重量级选手的视频云平台, ...

  8. 网络摄像头RTSP拉流协议网页无插件视频直播平台EasyNVR为什么无法获取通道接口数据?

    TSINGSEE青犀视频的技术支持最近给我反馈了一个问题,关于代理EasyNVR获取通道接口返回为空的问题.代理EasyNVR的过程也是将EasyNVR集成进其他平台的过程,这个问题在集成过程中还是比 ...

  9. 用ffmpeg+nginx+海康威视网络摄像头rtsp在手机端和电脑端实现直播

    原料:海康威视摄像头,nginx服务器,ffmpeg. 首先海康威视摄像头, 它的rtsp数据流的地址为:rtsp://[username]:[password]@[ip]:[port]/[codec ...

最新文章

  1. tkinter button 一个按钮第二次回复_python-tkinter使用方法
  2. 深度 | 量子计算技术的研究现状与未来
  3. nagios自写插件—check_file
  4. 微软发起Java on Azure调查,呼吁Java社区积极参与
  5. linux文件的操作原理简介 以及 实现linux cp命令的代码
  6. 排序算法-C++实现
  7. Nginx 使用try_files遇到的问题
  8. 数学天桥之中值定理|北京有文化的天桥
  9. (操作系统题目题型总结)第六章:文件管理
  10. Java数组在内存中的分配
  11. oracle学习笔记(十三) 查询练习(三) 子查询查询
  12. Android中自定义水球
  13. 列表查询数据交互简写形式
  14. entity framework 调用 oracle 序列_Weblogic T3 反序列化漏洞(CVE20192890 )分析
  15. 《老罗Android开发视频教程》更新
  16. 【数学建模】历年数学建模国赛评价类题目汇总
  17. 【知识产权之专利权】选择题题库
  18. python猜数游戏续_python实现猜数游戏
  19. AWSome Day 2019 线上云技术课堂(2)
  20. 威纶触摸屏485通信控制多台台达变频器程序

热门文章

  1. Truffle框架的初使用
  2. Python函数返回多个值的方法
  3. 什么是同城双活、异地双活、异地多活
  4. oracle缓冲区闩锁类型,等待缓冲区闩锁时出现超时 -- 类型 4
  5. 强烈推荐几个超厉害的公众号!
  6. 重写虫虫项目犯的低级错误
  7. 恒达高停车场信息管理系统的分析与设计
  8. calcite parser
  9. sql查询语句_多字段分类汇总_多表合并
  10. 计算机本科毕业论文要求,计算机科学与技术学院本科毕业设计(论文)规范(试行)...