PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=037)

  本文发布于 2017-08-21 17:00:39,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=037)

环境说明

  Linux 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

前言


  无

JRtp


  1. 首先直接下载源代码,查看其中的example1和2,里面有关于字符串的发送和接收的例子。先研究一下这个例子中JRTPlib的基本用法。然后就可以接着看下面的部分。(了解的忽略这一部分)

  2. JRTPLib的使用心得。

    1. 首先我实现的所有的主要功能都来至于这些类,那就是RTPSession ,RTPSessionParams。其中RTPSession 是核心,所有的其他类都是为这个服务的。它的主要作用是管理一个rtp会话,而另外一个是设置这个会话的参数。

    2. 这个库使用的主要流程是建立一个会话对象,为这个会话设置参数,创建会话,循环等待响应和处理会话(也就是发送和接收)。所以这个库的核心就在循环等待这一块,其他的都是约定好的东西,照搬就可以。

    3. 现在我们进入正题,也就是循环等待部分(JRTPLib的核心)

      • 发送部分

        RTPSession .SendPacket(buf,len)用来发送数据
      • 接收部分

        RTPSession .BeginDataAccess();RTPSession .EndDataAccess();之间的部分就是接收和处理RTP数据。
  3. 正题,H264裸视频的传输,以实际代码为例。

    a. 首先是H264相关是设定,如果对RTP协议不太了解,可去找找相关资料来看一看就OK了。

    this->sess.SetDefaultPayloadType(96);//设置传输类型 ,来至于rtp协议规范
this->sess.SetDefaultMark(true); //设置位,true标志此分包是最后一个包,false反之
this->sess.SetTimestampUnit(1.0/9000.0); //设置采样间隔
this->sess.SetDefaultTimestampIncrement(10);//设置时间戳增加间隔

    b. 然后是传输参数的设定

	sessparams.SetOwnTimestampUnit(1.0/10.0); //时间戳单位
sessparams.SetAcceptOwnPackets(true); //接收自己发送的数据包,这里必须设定为true,否则不能够接收到相关的包。
sessparams.SetUsePredefinedSSRC(true); //设置使用预先定义的SSRC,同步源ID,不明白的去看看相关资料
sessparams.SetPredefinedSSRC(SSRC); //定义SSRC

    c. h264裸视频数据的实时传输(这里有一个非常重要的东西,叫做拆包,学过《计算机网络》的人都应该知道这么一个东西吧),宏CODE_M部分的拆包算法是我写的,另外一部分的是网上传播最宽的一个拆包算法,我没有找到最开始的出处,所以就见谅啦,那位大神,我用来做对照。此外对于H264的传输,一定要知道什么是NALU(header,data),如果不知道去查资料,同时最好去看看RTP协议发送相关的知识。这部分传入的参数为Libx264编码之后生成的Nalu

//Internet 对 pkg-len>1400丢包概率很大(出之于某一篇论文),RTP header 12bytes,RTP pkg-max-len <= 1388
//MTU 48~1500, UDP data max len = 1500 - 20(IP header) - 8(UDP header)
//上面的东西看不懂,请回去翻翻计算机网络
if ( NaluSize <= MAX_RTP_PKT_LENGTH )
{
memcpy(Sbuf,p_Nalu,NaluSize);
status = this->sess.SendPacket((void *)Sbuf,NaluSize); CheckError(status);
}
else if (NaluSize > MAX_RTP_PKT_LENGTH){ this->sess.SetDefaultMark(false);//mark that this is not finall pkg
int k=0,l=0;
k = NaluSize / MAX_RTP_PKT_LENGTH; //k>=1
l = NaluSize % MAX_RTP_PKT_LENGTH; //l>=0
int t=0;//用指示当前发送的是第几个分片RTP包
int SendLen;
#ifdef CODE_M
while( (t < k) || ((t == k) && (l > 0)) ){ if ( t < k - 1){//0~k-2,total k - 1 pkgs memcpy(Sbuf,(p_Nalu+( t * MAX_RTP_PKT_LENGTH)),MAX_RTP_PKT_LENGTH);
status = this->sess.SendPacket((void *)Sbuf,MAX_RTP_PKT_LENGTH);
CheckError(status); }
else {//last pkg, t == k,l > 0, or (k -1)'th pkg if ( (t == k-1) && ( l == 0 ) || (l > 0) && ( t == k)){ this->sess.SetDefaultMark(true); if ( l > 0 )
SendLen = l;
else
SendLen = MAX_RTP_PKT_LENGTH; memcpy(Sbuf,(p_Nalu+( t * MAX_RTP_PKT_LENGTH)),SendLen);
status = this->sess.SendPacket((void *)Sbuf,SendLen);
CheckError(status); }
else {//(t == k-1) && ( l > 0 ) memcpy(Sbuf,(p_Nalu+( t * MAX_RTP_PKT_LENGTH)),MAX_RTP_PKT_LENGTH);
status = this->sess.SendPacket((void *)Sbuf,MAX_RTP_PKT_LENGTH);
CheckError(status); }
}
t++; }
#else
while( t < k || ( t==k && l>0 ) )
{
if( (0 == t ) || ( t<k && 0!=t ) )//第一包到最后包的前一包
{
/*sendbuf[0] = (nalHeader & 0x60)|28;
sendbuf[1] = (nalHeader & 0x1f);
if ( 0 == t )
{
sendbuf[1] |= 0x80;
}
memcpy(sendbuf+2,&pSendbuf[t*MAX_RTP_PKT_LENGTH],MAX_RTP_PKT_LENGTH);
status = this->SendPacket((void *)sendbuf,MAX_RTP_PKT_LENGTH+2);*/
memcpy(Sbuf,&p_Nalu[t*MAX_RTP_PKT_LENGTH],MAX_RTP_PKT_LENGTH);
status = this->sess.SendPacket((void *)Sbuf,MAX_RTP_PKT_LENGTH);
CheckError(status);
t++;
}
//最后一包
else if( ( k==t && l>0 ) || ( t== (k-1) && l==0 ))
{
//设置标志位Mark为1
this->sess.SetDefaultMark(true); int iSendLen;
if ( l > 0)
{
iSendLen = NaluSize - t*MAX_RTP_PKT_LENGTH;
}
else
iSendLen = MAX_RTP_PKT_LENGTH; //sendbuf[0] = (nalHeader & 0x60)|28;
//sendbuf[1] = (nalHeader & 0x1f);
//sendbuf[1] |= 0x40; //memcpy(sendbuf+2,&pSendbuf[t*MAX_RTP_PKT_LENGTH],iSendLen);
//status = this->SendPacket((void *)sendbuf,iSendLen+2); memcpy(Sbuf,&p_Nalu[t*MAX_RTP_PKT_LENGTH],iSendLen);
status = this->sess.SendPacket((void *)Sbuf,iSendLen); CheckError(status);
t++;
} }
#endif
}

    d. h264数据的接收部分,就是通过一个函数来对包进行组包操作,然后得到了传输数据,这部分代码和传输时拆包部分,息息相关。

	if(pack.GetPayloadType() == H264)
{
//std::cout<<"Got H264 packet:êo " << rtppack.GetExtendedSequenceNumber() << " from SSRC " << srcdat.GetSSRC() <<std::endl;
if(pack.HasMarker())//如果是最后一包则进行组包
{ //printf("Got a nal unit \n");
memcpy(this->rframe->pframe + this->cur_size,pack.GetPayloadData(),pack.GetPayloadLength());
this->cur_size += pack.GetPayloadLength();
this->rframe->use_len = this->cur_size; //***************************
#define DO_WRITE_FILE
#ifdef DO_WRITE_FILE
int fd_out = open("rec.h264", O_CREAT | O_RDWR,S_IRWXU|S_IRWXO|S_IRWXG);
lseek(fd_out, 0, SEEK_END);
write(fd_out, this->rframe->pframe,this->rframe->use_len);
close(fd_out);
#endif
//*************************** while(1){
if ( cir_buf.reserve() > this->rframe->use_len){ for (long i = 0; i < this->rframe->use_len; i++){ cir_buf.push_back( *(this->rframe->pframe + i) );
}
break;
}
} memset(this->rframe->pframe,0,this->rframe->use_len);//清空缓存,为下次做准备 this->cur_size = 0;
}
else//放入缓冲区,在此必须确保有序
{
//unsigned char* p = rtppack.GetPayloadData(); memcpy(this->rframe->pframe + this->cur_size,pack.GetPayloadData(),pack.GetPayloadLength());
this->cur_size += pack.GetPayloadLength();
}
}

  这里只提供发送部分的代码,我的接收部分的代码和服务端写在一起的不好抽离。

y_jrtp.cpp

#include "y_jrtp.h"

static bool CheckError(int rtperr)
{
if (rtperr < 0)
{
std::cout<<"ERROR: "<<RTPGetErrorString(rtperr)<<std::endl;
return false;
}
return true;
} yJRTPLIB::yJRTPLIB(void){ }
yJRTPLIB::~yJRTPLIB(void){ } void yJRTPLIB::SendX264NalUnit(uint8_t * const p_payload, const int i_payload){ uint8_t *p_Nalu;
int NaluSize;
uint8_t Sbuf[MAX_RTP_PKT_LENGTH];
int status; p_Nalu = p_payload;
NaluSize = i_payload;
CLEAR_MEM(Sbuf); printf("Nal unit length is %d \n",NaluSize);
//去除前导码0x000001 或者0x00000001
//if( 0x01 == m_h264Buf[2] )
//{
// pSendbuf = &m_h264Buf[3];
// buflen -= 3;
//}
//else
//{
// pSendbuf = &m_h264Buf[4];
// buflen -= 4;
//} if ( NaluSize <= MAX_RTP_PKT_LENGTH )
{
memcpy(Sbuf,p_Nalu,NaluSize);
status = this->sess.SendPacket((void *)Sbuf,NaluSize); CheckError(status);
}
else if (NaluSize > MAX_RTP_PKT_LENGTH){ this->sess.SetDefaultMark(false);//mark that this is not finall pkg
int k=0,l=0;
k = NaluSize / MAX_RTP_PKT_LENGTH; //k>=1
l = NaluSize % MAX_RTP_PKT_LENGTH; //l>=0
int t=0;//用指示当前发送的是第几个分片RTP包
int SendLen;
#ifdef CODE_M
while( (t < k) || ((t == k) && (l > 0)) ){ if ( t < k - 1){//0~k-2,total k - 1 pkgs memcpy(Sbuf,(p_Nalu+( t * MAX_RTP_PKT_LENGTH)),MAX_RTP_PKT_LENGTH);
status = this->sess.SendPacket((void *)Sbuf,MAX_RTP_PKT_LENGTH);
CheckError(status); }
else {//last pkg, t == k,l > 0, or (k -1)'th pkg if ( (t == k-1) && ( l == 0 ) || (l > 0) && ( t == k)){ this->sess.SetDefaultMark(true); if ( l > 0 )
SendLen = l;
else
SendLen = MAX_RTP_PKT_LENGTH; memcpy(Sbuf,(p_Nalu+( t * MAX_RTP_PKT_LENGTH)),SendLen);
status = this->sess.SendPacket((void *)Sbuf,SendLen);
CheckError(status); }
else {//(t == k-1) && ( l > 0 ) memcpy(Sbuf,(p_Nalu+( t * MAX_RTP_PKT_LENGTH)),MAX_RTP_PKT_LENGTH);
status = this->sess.SendPacket((void *)Sbuf,MAX_RTP_PKT_LENGTH);
CheckError(status); }
}
t++; }
#else
while( t < k || ( t==k && l>0 ) )
{
if( (0 == t ) || ( t<k && 0!=t ) )//第一包到最后包的前一包
{
/*sendbuf[0] = (nalHeader & 0x60)|28;
sendbuf[1] = (nalHeader & 0x1f);
if ( 0 == t )
{
sendbuf[1] |= 0x80;
}
memcpy(sendbuf+2,&pSendbuf[t*MAX_RTP_PKT_LENGTH],MAX_RTP_PKT_LENGTH);
status = this->SendPacket((void *)sendbuf,MAX_RTP_PKT_LENGTH+2);*/
memcpy(Sbuf,&p_Nalu[t*MAX_RTP_PKT_LENGTH],MAX_RTP_PKT_LENGTH);
status = this->sess.SendPacket((void *)Sbuf,MAX_RTP_PKT_LENGTH);
CheckError(status);
t++;
}
//最后一包
else if( ( k==t && l>0 ) || ( t== (k-1) && l==0 ))
{
//设置标志位Mark为1
this->sess.SetDefaultMark(true); int iSendLen;
if ( l > 0)
{
iSendLen = NaluSize - t*MAX_RTP_PKT_LENGTH;
}
else
iSendLen = MAX_RTP_PKT_LENGTH; //sendbuf[0] = (nalHeader & 0x60)|28;
//sendbuf[1] = (nalHeader & 0x1f);
//sendbuf[1] |= 0x40; //memcpy(sendbuf+2,&pSendbuf[t*MAX_RTP_PKT_LENGTH],iSendLen);
//status = this->SendPacket((void *)sendbuf,iSendLen+2); memcpy(Sbuf,&p_Nalu[t*MAX_RTP_PKT_LENGTH],iSendLen);
status = this->sess.SendPacket((void *)Sbuf,iSendLen); CheckError(status);
t++;
} }
#endif
}
} void yJRTPLIB::SetX264Parm()
{
this->sess.SetDefaultPayloadType(H264);//设置传输类型
this->sess.SetDefaultMark(true); //设置位
this->sess.SetTimestampUnit(1.0/9000.0); //设置采样间隔
this->sess.SetDefaultTimestampIncrement(10);//设置时间戳增加间隔
}
int yJRTPLIB::SetRtpParm(std::string &d_ip){ int status;
std::string ipstr = d_ip;
uint32_t destip; if ( (destip = inet_addr(ipstr.c_str())) == INADDR_NONE)
{
std::cerr << "Bad IP address specified" << std::endl;
return -1;
} RTPUDPv4TransmissionParams transparams;
RTPSessionParams sessparams; //sessparams.SetOwnTimestampUnit(1.0/9000.0); //时间戳单位
sessparams.SetOwnTimestampUnit(1.0/10.0); //时间戳单位
sessparams.SetAcceptOwnPackets(true); //接收自己发送的数据包
sessparams.SetUsePredefinedSSRC(true); //设置使用预先定义的SSRC
sessparams.SetPredefinedSSRC(SSRC); //定义SSRC transparams.SetPortbase(PORTBASE); status = sess.Create(sessparams,&transparams);
CheckError(status); destip = ntohl(destip);
RTPIPv4Address addr(destip,DESTPORT);
status = sess.AddDestination(addr);
CheckError(status);
}

y_jrtp.h

#ifndef Y_JRTP_H
#define Y_JRTP_H #include <rtpsession.h>
#include <rtpudpv4transmitter.h>
#include <rtpipv4address.h>
#include <rtpsessionparams.h>
#include <rtperrors.h>
#include <rtplibraryversion.h>
#include <iostream> //port必须是偶数
#define PORTBASE 6000
#define DESTPORT 6002
#define DESTIP "127.0.0.1"
#define H264 96 /*
SSRC:同步源标识。
*/ #define SSRC 1 //Internet 对 pkg-len>1400丢包概率很大,RTP header 12bytes,RTP pkg-max-len <= 1388
//MTU 48~1500, UDP data max len = 1500 - 20(IP header) - 8(UDP header)
#define MAX_RTP_PKT_LENGTH 1300 #ifndef CLEAR_MEM
#define CLEAR_MEM(mem) memset((mem),0,sizeof((mem)))
#endif using namespace jrtplib; class yJRTPLIB{ public:
yJRTPLIB(void);
~yJRTPLIB(void);
void SendX264NalUnit(uint8_t * const p_payload, const int i_payload);
void SetX264Parm(void);
int SetRtpParm(std::string &d_ip);
private:
RTPSession sess;
}; #endif // Y_JRTP_H

  首先,这里面的一些代码我直接使用的网上的某些教程,每一处用了一点,然后自己整理成这样的,这里再次说明,拆包部分的宏CODE_M是我的,另外一部分是网友的,不知道是谁,找不到最开始的出处。

  好了JrtpLib就这些东西了,没有啥新鲜的,对于我这种小白用户,我也只需要了解这么多,如果想要优化RTP传输或者其他的想法,你就得好好的去读读这些代码,并深入的了解RTP协议。

后记


  无

参考文献


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

毕设系列之JrtpLib H264(裸视频数据) 实时视频传输(发送与接受)的更多相关文章

  1. 基于RTP的H264视频数据打包解包类

    from:http://blog.csdn.net/dengzikun/article/details/5807694 最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打 ...

  2. (转)基于RTP的H264视频数据打包解包类

    最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...

  3. 【FFMPEG】基于RTP的H264视频数据打包解包类

    最近考虑使用RTP替换原有的高清视频传输协议,遂上网查找有关H264视频RTP打包.解包的文档和代码.功夫不负有心人,找到不少有价值的文档和代码.参考这些资料,写了H264 RTP打包类.解包类,实现 ...

  4. 利用FFmpeg 将 rtsp 获取H264裸流并保存到文件中

    既然已经可以通过 RTSP 获取h264 裸流了.那么通过 FFmpeg 将其保存到文件中怎么做呢? 一.首先RTSP获取 h264 裸流 我们上面两篇文章主要讲的是通过 rtsp://Your ip ...

  5. H264/AVC视频解码时AVC1和H264的区别

    AVC1与H264的区别 http://blog.csdn.net/qiuchangyong/article/details/6660253 H.264 Video Types The followi ...

  6. vlc-android对于通过Live555接收到音视频数据包后的处理分析

    通过ndk-gdb跟踪调试vlc-android来分析从连接到RTSP服务器并接收到音视频数据包后的处理过程. 首先,从前面的文章有分析过vlc-android的处理过程通过线程函数Run()(Src ...

  7. FFmpeg开发实战(五):FFmpeg 抽取音视频的视频数据

    如何使用FFmpeg抽取音视频的视频数据,代码如下: // FFmpegTest.cpp : 此文件包含 "main" 函数.程序执行将在此处开始并结束. // #include ...

  8. ffmpeg学习(二) 通过rtsp获取H264裸流并保存到mp4文件

    本篇将使用上节http://www.cnblogs.com/wenjingu/p/3977015.html中编译好的库文件通过rtsp获取网络上的h264裸流并保存到mp4文件中. 1.VS2010建 ...

  9. FFmpeg采集音视频数据命令

    文章转自:https://www.jianshu.com/p/4709ccbda3f9 1.ffmpeg 把文件当做直播推送至服务器 (RTMP + FLV) ffmpeg - re -i demo. ...

  10. 【转】C#播放H264裸码流

    原文地址:https://www.cnblogs.com/cangyue080180/p/5873351.html 要播放H264裸码流,可以分拆为以下三个工作: 1.解码H264裸码流获取YUV数据 ...

随机推荐

  1. 如何基于 spdlog 在编译期提供类 logrus 的日志接口

    如何基于 spdlog 在编译期提供类 logrus 的日志接口 实现见 Github,代码简单,只有一个头文件. 前提 几年前看到戈君在知乎上的一篇文章,关于打印日志的一些经验总结: 实践下来很受用 ...

  2. Python 中global 关键字理解

    Python中的global关键字,你了解吗? 前言 今天来了解下 Python 中的 global 关键字. Python变量的作用域 实战案例演示之前,先要了解下 Python 的作用域. Pyt ...

  3. lombok-ex 编译时注解框架,性能完爆 AOP

    lombok-ex lombok-ex 是一款类似于 lombok 的编译时注解框架. 主要补充一些 lombok 没有实现,且自己会用到的常见工具. 编译时注解性能无任何损失,一个注解搞定一切,无三 ...

  4. Spring Boot+Eureka+Spring Cloud微服务快速上手项目实战

    说明 我看了一些教程要么写的太入门.要么就是写的太抽象.真正好的文章应该是快速使人受益的而不是浪费时间.本文通过一个包括组织.部门.员工等服务交互的案例让刚接触spring cloud微服务的朋友快速 ...

  5. P1536 村村通(并查集)

    村村通 题目描述 某市调查城镇交通状况,得到现有城镇道路统计表.表中列出了每条道路直接连通的城镇.市政府 "村村通工程" 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接 ...

  6. 麒麟系统开发笔记(八):在国产麒麟系统上使用linuxdeployqt发布qt程序

    前言   在ubuntu上发布qt程序相对还好,使用脚本,但是在麒麟上发布的时候,因为银河麒麟等不同版本,使用脚本就不太兼容,同时为了实现直接点击应用可以启动应用的效果,使用linuxdeployqt ...

  7. libmatio开发笔记(一):matlab文件操作libmatio库介绍,编译和基础Demo

    前言   Qt可通过matlab的库对mat文件进行读写,第三方库matio也可以对mat文件进行读写,其已经支持mat文件的7.3版本.   libmatio库介绍   matio软件包含一个用于读 ...

  8. pwd模块

    # pwd模块提供了获取UNIX平台用户的账户与密码信息(通过文件/etc/passwd),在所有的UNIX版本平台都可以用. # pwd模块返回的是一个类似元组的对象,该对象的各个属性对应于pass ...

  9. 【复盘#01】myh笔试

    存疑 1.http响应体中版本和缓存是哪个字段(Etga) http和https的区别 2.mysql同一个表中有多个相同字段但搜索的时候只搜得出某一个,要怎么修改(inner ..) mysql如何 ...

  10. 【LeetCode排序专题02】最小k个数,关于快速排序的讨论

    最小k个数 https://leetcode.cn/problems/smallest-k-lcci/ 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个 ...