RTP协议接收和播放

前面讲过RTP协议的重要性,说过发送RTP协议,RTP协议重要性下面我们开始写发送和接收程序,不依赖于jrtplib等RTP库,自行接收。这样,有利于以后将RTP直接转到Webrtc上

RTP接收

首先定义RTPFrame结构体,一般来讲,我们都是12字节头部,但不是一定的,自行扩展是允许的,具体看RTP的头部字节含义。maker是代表结束,为1,没有结束为0,还有一个更简单的方法,当一帧结束时,timstamp一定会改变,也可以这样来断定,具体来说,也没有一帧的概念,或者称为一个nalu视频的切换。ok,show me the code,注意,像时间戳,seq no,以及ssrc等都是网络字节序,是大端格式,读者必须将这些字节序理解。seq no 为16位两字节,达到最大值又从零开始。

struct RTPFrame {RTPFrame(const unsigned char * frame, int frameLen) {_frame = (unsigned char*) frame;_frameLen = frameLen;};RTPFrame(unsigned char * frame, int frameLen, unsigned char payloadType) {_frame = frame;_frameLen = frameLen;if (_frameLen > 0)_frame [0] = 0x80;SetPayloadType(payloadType);}unsigned GetPayloadSize() const {return (_frameLen - GetHeaderSize());}void SetPayloadSize(int size) {_frameLen = size + GetHeaderSize();}int GetFrameLen () const {return (_frameLen);}unsigned char * GetPayloadPtr() const {return (_frame + GetHeaderSize());}int GetHeaderSize() const {int size;size = 12;if (_frameLen < 12) return 0;size += (_frame[0] & 0x0f) * 4;if (!(_frame[0] & 0x10))return size;if ((size + 4) < _frameLen) return (size + 4 + (_frame[size + 2] << 8) + _frame[size + 3]);return 0;}bool GetMarker() const {if (_frameLen < 2) {return false;}return (_frame[1] & 0x80);}unsigned GetSequenceNumber() const {if (_frameLen < 4)return 0;return (_frame[2] << 8) + _frame[3];}void SetMarker(bool set) {if (_frameLen < 2) return;_frame[1] = _frame[1] & 0x7f;if (set) _frame[1] = _frame[1] | 0x80;}void SetPayloadType(unsigned char type) {if (_frameLen < 2) return;_frame[1] = _frame [1] & 0x80;_frame[1] = _frame [1] | (type & 0x7f);}unsigned char GetPayloadType() const{if (_frameLen < 1)return 0xff;return _frame[1] & 0x7f;}unsigned long GetTimestamp() const {if (_frameLen < 8)return 0;return ((_frame[4] << 24) + (_frame[5] << 16) + (_frame[6] << 8) + _frame[7]);}void SetTimestamp(unsigned long timestamp) {if (_frameLen < 8)return;_frame[4] = (unsigned char) ((timestamp >> 24) & 0xff);_frame[5] = (unsigned char) ((timestamp >> 16) & 0xff);_frame[6] = (unsigned char) ((timestamp >> 8) & 0xff);_frame[7] = (unsigned char) (timestamp & 0xff);};protected:unsigned char* _frame;int _frameLen;
};

下面分为两种包解码h264,h265是同理的,后面会贴上来

//单时间聚合包(STAP)包含的是同一帧的数据
//实际测试时ffmpeg发送的RTP就是有这种包
bool c_h264frame::DeencapsulateSTAP (RTPFrame & frame)
{uint8_t* curSTAP = frame.GetPayloadPtr() + 1;uint32_t curSTAPLen = frame.GetPayloadSize() - 1; while (curSTAPLen > 0){// first, theres a 2 byte length fielduint32_t len = (curSTAP[0] << 8) | curSTAP[1];curSTAP += 2;//then the header, followed by the body.  We'll add the header//in the AddDataToEncodedFrame - that's why the nal body is dptr + 1AddDataToEncodedFrame(curSTAP + 1,  len - 1, *curSTAP, 1);curSTAP += len;if ((len + 2) > curSTAPLen){curSTAPLen = 0;return false;}else{curSTAPLen -= (len + 2);}}return true;
}

这种包其实很简单,一般像ffmpeg发送rtp的时候就是放入sps,pps信息,很好,每个单包前面有两个字节的长度代表单包长度,顺序读取就好,逻辑很简单。

接下来是FU包解包


bool c_h264frame::DeencapsulateFU (RTPFrame & frame)
{//FU指示字节有以下格式://     +---------------+//     |0|1|2|3|4|5|6|7|//     +-+-+-+-+-+-+-+-+//     |F|NRI|  Type   |//     +---------------+//  FU指示字节的类型域的28,29表示FU-A和FU-B。F的使用在5。3描述。NRI域的值必须根据分片NAL单元的NRI域的值设置。//  FU头的格式如下://     +---------------+//     |0|1|2|3|4|5|6|7|//     +-+-+-+-+-+-+-+-+//     |S|E|R|  Type   |//     +---------------+//  S: 1 bit//     当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。//  E: 1 bit//     当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的//     FU荷载不是分片NAL单元的最后分片,结束位设置为0。//  R: 1 bit//     保留位必须设置为0,接收者必须忽略该位。//     //  Type: 5 bits//     NAL单元荷载类型定义在[1]的表7-1.uint8_t* curFUPtr = frame.GetPayloadPtr();uint32_t curFULen = frame.GetPayloadSize(); uint8_t header;//S为1 E不为1 为开始第一个分片if ((curFUPtr[1] & 0x80) && !(curFUPtr[1] & 0x40)){if (_currentFU) {_currentFU=1;}else{_currentFU++;//0xe0 11100000 0x1f 00011111header = (curFUPtr[0] & 0xe0) | (curFUPtr[1] & 0x1f);AddDataToEncodedFrame(curFUPtr + 2, curFULen - 2, header,  1);}} else if (!(curFUPtr[1] & 0x80) && !(curFUPtr[1] & 0x40)){if (_currentFU){_currentFU++;AddDataToEncodedFrame(curFUPtr + 2, curFULen - 2,  0, 0);}else{_currentFU=0;return false;}} else if (!(curFUPtr[1] & 0x80) && (curFUPtr[1] & 0x40)){if (_currentFU) {_currentFU=0;AddDataToEncodedFrame( curFUPtr + 2, curFULen - 2, 0, 0);}else{_currentFU=0;return false;}} else if ((curFUPtr[1] & 0x80) && (curFUPtr[1] & 0x40)){_currentFU=0;return false;} return true;
}void c_h264frame::AddDataToEncodedFrame (uint8_t *data, uint32_t dataLen, uint8_t header, bool addHeader) {uint8_t headerLen= addHeader ? 5 : 0;uint8_t* currentPositionInFrame = _encodedFrame + _encodedFrameLen;// add 00 00 00 01 [headerbyte] headerif (addHeader){*currentPositionInFrame++ = 0;*currentPositionInFrame++ = 0;*currentPositionInFrame++ = 0;*currentPositionInFrame++ = 1;if (_numberOfNALsInFrame + 1 >(_numberOfNALsReserved)){_NALs = (h264_nal_t *)realloc(_NALs, (_numberOfNALsReserved + 1) * sizeof(h264_nal_t));_numberOfNALsReserved++;}if (_NALs){_NALs[_numberOfNALsInFrame].offset = _encodedFrameLen + 4;_NALs[_numberOfNALsInFrame].length = dataLen + 1;_NALs[_numberOfNALsInFrame].type = header & 0x1f;_numberOfNALsInFrame++;}*currentPositionInFrame++ = header;}else{if (_NALs) _NALs[_numberOfNALsInFrame - 1].length += dataLen;}memcpy(currentPositionInFrame, data, dataLen);_encodedFrameLen += dataLen + headerLen;
}bool c_h264frame::IsStartCode (const uint8_t *positionInFrame)
{if (positionInFrame[0] == 0 &&positionInFrame[1] == 0 &&((positionInFrame[2] == 1) ||((positionInFrame[2] == 0) && positionInFrame[3] == 1))) {return true;}return false;
}

图一发送RTP
RTP发送遵循前面所说的方式,编码h264发送

图二 接收RTP

至于绘图,可以使用很多种方法,这里介绍opengl方式,而且,我们可以用三维方式绘制出来。


把视频绘制在三维空间,空间里可以放很多东西,这个空间足够大。

后面会讲解如何制作这种程序。甚至在web里面也可以做到,在三维世界里播放我们的视频和视频会议。

RTP发送和接收(有图为证)相关推荐

  1. Android 短信模块分析(四) MMS之短信的发送与接收

     MMS之短信的发送与接收分析: 一.信息发送: com.android.mms.data.WorkingMessage.java 类 send()函数: public void send() { . ...

  2. 如何发送和接收RTP封包的H264,用FFmpeg解码

    这篇文章给大家介绍怎么打包H264分片到RTP包,并且实现一个播放程序演示接收RTP包和用FFmpeg解码.为了让大家更了解H264打包成RTP的细节,有必要罗嗦一下向大家介绍一些相关的基础知识,下面 ...

  3. java带图形用户的聊天_java:带有图形化界面,可以发送和接收的聊天工具

    /** 根据UDP协议,设计一个聊天程序,可以发送和接收,并有相应的图形化界面 */ import java.net.*; import java.awt.*; import java.awt.eve ...

  4. WebRTC音频系统 音频发送和接收

    文章目录 3.1音频数据流发送流程 3.2 发送中的编码.RTP打包 3.3 AudioSendStream类关系 3.4`webrtc::AudioSendStream` 创建和初始化 3.5 创建 ...

  5. 超声波传感器特性测量:发送与接收传感器之间的差异

    简 介: 本文针对超声波传感器的测量方法,探讨了使用NanoVNA测量过程, 对比发射和接收传感器在特性方面的不同. 对于整个过程编写了APPLE脚本. 关键词: 超声波传感器,发送,接收,NanoV ...

  6. Android中发送和接收短信

    在做Android开发中经常要用到短信的发送和短信的接收,调用Android提供的api实现起来很简单,今天要用到这个功能研究了一下顺便写下来加强一下记忆. 1.首先创建一个Android Proje ...

  7. python socket发送组播数据_python3通过udp实现组播数据的发送和接收操作

    本文主要通过对海康摄像头进行抓包,模拟发送了udp包,并抓取摄像头返回的数据包,解析并提取相关信息. 通过抓包发现,海康摄像头发送.接收数据使用udp协议,后来比较发现,使用python模拟起来比较简 ...

  8. 技巧: 用 JAXM 发送和接收 SOAP 消息—Java API 使许多手工生成和发送消息方面必需的步骤自动化...

    简介: 在本篇技巧文章中,作者兼开发人员 Nicholas Chase 向您演示如何使用用于 XML 消息传递的 Java API(Java API for XML Messaging (JAXM)) ...

  9. as3 访问远程计算机,Flash AS3中数据发送与接收

    Flash AS3中数据发送与接收 互联网   发布时间:2008-10-06 01:24:53   作者:佚名   我要评论 先回顾一下as2中相关的解决办法 相信大部分人用的都是LoadVars类 ...

最新文章

  1. php嵌入html还是html嵌入php,php嵌入html有哪几种方法
  2. python对操作系统的目录和文件操作
  3. JavaScript --- 表单focus,blur,change事件的实现
  4. ASP.NET Core应用程序的参数配置及使用
  5. 设计模式之观察者模式
  6. 苹果要弃用LCD屏,便宜的iPhone XR面临绝版
  7. 【BZOJ3144】[Hnoi2013]切糕 最小割
  8. java模拟安卓get请求,Java模拟HTTP Get Post请求实现论坛自动回帖功能
  9. Julia:V0.3.0 正式发布,好消息!
  10. zynq processing system 参数设置_【正点原子FPGA连载】第六章自定义IP核-呼吸灯实验-领航者 ZYNQ 之嵌入式开发指南...
  11. 如何从PDF文件中提取几页为一个PDF文件?
  12. 软件工程实践 Blog5
  13. Java第四课:用类描述CPU速度和HardDisk容量
  14. Python爬取新浪足球数据(以中超为例)
  15. python点击网页_python模拟点击网页按钮实现方法
  16. 网上赚钱竞争那么激烈你一定要有自己的绝活!
  17. GitHub上常用第三方库
  18. 中望CAD的引线标注格式怎么改_没想到啊,原来CAD命令还可以这样学习
  19. 户外电源品牌之间比较的是什么?
  20. python 基础代谢率计算_Python基础(五) BMR计算器demo

热门文章

  1. 马云给雅虎员工作的精彩演讲:爱迪生欺骗了世界!
  2. python-找Python安装目录,设置环境路径以及在命令行运行python脚本
  3. 中国最让人脸红的节目:爆火14年的湿身诱惑,为何还没被叫停?
  4. 陆正耀神州优车被强制执行超10亿
  5. 阅文启动“2022全球作家孵化项目” 加速网络文学出海
  6. Canalys:尽管面临供应压力 2021年智能手机的出货量仍预计增长12%
  7. 中国最富包租婆!每年坐地收租200亿,身家相当于十个老干妈
  8. 荣耀:目前还在观望鸿蒙,未来的对手是苹果
  9. 离开小米后 周受资将加入字节跳动担任CFO
  10. 苹果2021年WWDC大会可能继续采用线上方式举办