毕设系列之JrtpLib H264(裸视频数据) 实时视频传输(发送与接受)

2024-06-16 07:08

本文主要是介绍毕设系列之JrtpLib H264(裸视频数据) 实时视频传输(发送与接受),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

#PS:这个只是 《 我自己 》理解,如果和你的

#原则相冲突,请谅解,勿喷
开发环境: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

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 pkgint k=0,l=0;    k = NaluSize / MAX_RTP_PKT_LENGTH;  //k>=1l = NaluSize % MAX_RTP_PKT_LENGTH;  //l>=0int t=0;//用指示当前发送的是第几个分片RTP包int SendLen;
#ifdef CODE_Mwhile( (t < k) || ((t == k) && (l > 0)) ){if ( t < k - 1){//0~k-2,total k - 1 pkgsmemcpy(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 pkgif ( (t == k-1) && ( l == 0 ) || (l > 0) && ( t == k)){this->sess.SetDefaultMark(true);if ( l > 0 )SendLen = l;elseSendLen = 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++;}
#elsewhile( 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_FILEint 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 pkgint k=0,l=0;k = NaluSize / MAX_RTP_PKT_LENGTH;  //k>=1l = NaluSize % MAX_RTP_PKT_LENGTH;  //l>=0int t=0;//用指示当前发送的是第几个分片RTP包int SendLen;
#ifdef CODE_Mwhile( (t < k) || ((t == k) && (l > 0)) ){if ( t < k - 1){//0~k-2,total k - 1 pkgsmemcpy(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 pkgif ( (t == k-1) && ( l == 0 ) || (l > 0) && ( t == k)){this->sess.SetDefaultMark(true);if ( l > 0 )SendLen = l;elseSendLen = 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++;}
#elsewhile( 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为1this->sess.SetDefaultMark(true);int iSendLen;if ( l > 0){iSendLen = NaluSize - t*MAX_RTP_PKT_LENGTH;}elseiSendLen = 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);  //设置使用预先定义的SSRCsessparams.SetPredefinedSSRC(SSRC);     //定义SSRCtransparams.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)))
#endifusing 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:要转载请注明出处,本人版权所有.

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

这篇关于毕设系列之JrtpLib H264(裸视频数据) 实时视频传输(发送与接受)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1065777

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi