技术标签: RTSP转RTMP RTMP推流 EasyRTMP说明 EasyRTSPLive EasyRTSPClient RTSP拉流
说到RTSP H.265(HEVC)转RTMP,首先要对RTMP协议有所了解,RTMP协议里面原本是没有H.265的定义的,国内的CDN厂家联合起来制定了一个关于RTMP/FLV扩展H.265(HEVC)的方案,这是为了适应国内视频互联网高速发展而来的,目前国内开发者基本上都按这个扩展协议做的。
RTMP头部信息封装并没有定义HEVC,我们采用CDN联盟的HEVC扩展标准,将HEVC的VideoTagHeader定义为12,详见下图:
协议层已经确定,下一步的关键问题是要实现RTMP H.265(HEVC)推送模块,在视频推送上面,我们可以选择ffmpeg或者EasyRTMP,两者都可以支持H.265(HEVC)的RTMP推流,但是ffmpeg是需要做一些小小的修改和重新编译的:
typedef struct HVCCNALUnitArray {
uint8_t array_completeness;
uint8_t NAL_unit_type;
uint16_t numNalus;
uint16_t *nalUnitLength;
uint8_t **nalUnit;
} HVCCNALUnitArray;
typedef struct HEVCDecoderConfigurationRecord {
uint8_t configurationVersion;
uint8_t general_profile_space;
uint8_t general_tier_flag;
uint8_t general_profile_idc;
uint32_t general_profile_compatibility_flags;
uint64_t general_constraint_indicator_flags;
uint8_t general_level_idc;
uint16_t min_spatial_segmentation_idc;
uint8_t parallelismType;
uint8_t chromaFormat;
uint8_t bitDepthLumaMinus8;
uint8_t bitDepthChromaMinus8;
uint16_t avgFrameRate;
uint8_t constantFrameRate;
uint8_t numTemporalLayers;
uint8_t temporalIdNested;
uint8_t lengthSizeMinusOne;
uint8_t numOfArrays;
HVCCNALUnitArray *array;
} HEVCDecoderConfigurationRecord;
//需要注意的是,该结构其他参数我们其实可以不特别关心
//我们只需要在HVCCNALUnitArray数组中把HEVC的VPS,SPS和PPS信息填入即可。
static void hvcc_init(HEVCDecoderConfigurationRecord *hvcc)
{
memset(hvcc, 0, sizeof(HEVCDecoderConfigurationRecord));
hvcc->configurationVersion = 1;
hvcc->lengthSizeMinusOne = 3; // 4 bytes
/*
* The following fields have all their valid bits set by default,
* the ProfileTierLevel parsing code will unset them when needed.
*/
hvcc->general_profile_compatibility_flags = 0xffffffff;
hvcc->general_constraint_indicator_flags = 0xffffffffffff;
/*
* Initialize this field with an invalid value which can be used to detect
* whether we didn't see any VUI (in which case it should be reset to zero).
*/
hvcc->min_spatial_segmentation_idc = MAX_SPATIAL_SEGMENTATION + 1;
}
int i = 0;
if(bIsKeyFrame)
{
//body[i++] = 0x17;// 2:Pframe 7:AVC
body[i++] = (m_metadata.nVideoCodec == FLV_CODECID_HEVC) ? 0x1C:0x17;// 1:Iframe 7:AVC 12:HEVC
if (m_bWaitingKeyFrame)
{
m_bWaitingKeyFrame = false;
}
}
else
{
//body[i++] = 0x27;// 2:Pframe 7:AVC
body[i++] = (m_metadata.nVideoCodec == FLV_CODECID_HEVC) ? 0x2C:0x27;// 1:Iframe 7:AVC 12:HEVC
}
body[i++] = 0x01;// AVC NALU
body[i++] = 0x00;
body[i++] = 0x00;
body[i++] = 0x00;
// NALU size
body[i++] = size>>24;
body[i++] = size>>16;
body[i++] = size>>8;
body[i++] = size&0xff;
EasyRTMP则可以直接支持H.265(HEVC)的推流,EasyRTMP是一套调用简单、功能完善、运行高效稳定的RTMP功能组件,支持市面上绝大部分的RTMP流媒体服务器,包括Wowza、Red5、ngnix_rtmp、crtmpserver等主流RTMP服务器,能够完美应用于各种行业的直播需求,手机直播、桌面直播、摄像机直播、课堂直播等等方面! Github地址:https://github.com/EasyDSS/EasyRTMP
目前市面上两套相对较好的RTSP协议拉流技术框架,一个是live555,一个是ffmpeg,两套框架各有千秋,各有一系列的应用案例:
live555:非常老牌的RTSP框架,十几年了,还在迭代与维护,作者Ross也以此为业,进行着开源+商业的运营,大家所熟知的VLC播放器,RTSP拉流采用的就是live555;
ffmpeg:ffmpeg就更不用说了,目前国内大部分的播放器,都用这个,RTSP模块自写自带的,兼容性也非常不错;
如果说对于RTSP流程协议不是很熟悉,而且对于ffmpeg的改造或者说编译都是一件非常费时费事,而且成功率不高的事情,那么可以直接采用EasyRTSPClient:https://github.com/tsingsee/EasyRTSPClient ,基于live555进行的优化和开发,大大简化了RTSP协议的调用流程,尤其是再也不用过多关心RTSP的DESCRIBE、SETUP、PLAY的流程,直接回调就能获取到对应的错误信息和数据信息,包括能将H.264/H.265的sps、pps、vps等数据信息;
实际以上描述的基本都是组件,而如何能将整套过程串联起来,进行一整套的、多路并行的RTSP拉流转RTMP推流,还是需要费很多事情的,于是,我们整合了一个EasyRTSPLive项目,能非常方便地将整个过程串联,支持H.264、H.265流的RTSP拉转RTMP推:https://github.com/EasyDarwin/EasyRTSPLive
#define _CRTDBG_MAP_ALLOC
#include <stdio.h>
#ifdef _WIN32
#include "windows.h"
#else
#include <string.h>
#include <unistd.h>
#endif
#include "getopt.h"
#include <stdio.h>
#include <iostream>
#include <time.h>
#include <stdlib.h>
//#include <vector>
#include <list>
#include "EasyRTSPClientAPI.h"
#include "EasyRTMPAPI.h"
#include "ini.h"
#include "trace.h"
#ifdef _WIN32
#pragma comment(lib,"libEasyRTSPClient.lib")
#pragma comment(lib,"libeasyrtmp.lib")
#endif
#define MAX_RTMP_URL_LEN 256
#define BUFFER_SIZE 1024*1024
#define MAX_CHANNEL_INDEX 1024
#define CONF_FILE_PATH "Config.ini"
typedef struct _channel_cfg_struct_t
{
int channelId;
int option;
char channelName[64];
char srcRtspAddr[512];
char destRtmpAddr[512];
}_channel_cfg;
typedef struct _rtmp_pusher_struct_t
{
Easy_Handle rtmpHandle;
unsigned int u32AudioCodec;
unsigned int u32AudioSamplerate;
unsigned int u32AudioChannel;
}_rtmp_pusher;
typedef struct _channel_info_struct_t
{
_channel_cfg fCfgInfo;
_rtmp_pusher fPusherInfo;
Easy_Handle fNVSHandle;
FILE* fLogHandle;
bool fHavePrintKeyInfo;
EASY_MEDIA_INFO_T fMediainfo;
}_channel_info;
static std::list <_channel_info*> gChannelInfoList;
int __EasyRTMP_Callback(int _frameType, char *pBuf, EASY_RTMP_STATE_T _state, void *_userPtr)
{
_channel_info* pChannel = (_channel_info*)_userPtr;
switch(_state)
{
case EASY_RTMP_STATE_CONNECTING:
TRACE_LOG(pChannel->fLogHandle, "Connecting...\n");
break;
case EASY_RTMP_STATE_CONNECTED:
TRACE_LOG(pChannel->fLogHandle, "Connected\n");
break;
case EASY_RTMP_STATE_CONNECT_FAILED:
TRACE_LOG(pChannel->fLogHandle, "Connect failed\n");
break;
case EASY_RTMP_STATE_CONNECT_ABORT:
TRACE_LOG(pChannel->fLogHandle, "Connect abort\n");
break;
case EASY_RTMP_STATE_DISCONNECTED:
TRACE_LOG(pChannel->fLogHandle, "Disconnect.\n");
break;
default:
break;
}
return 0;
}
/* EasyRTSPClient callback */
int Easy_APICALL __RTSPSourceCallBack( int _chid, void *_chPtr, int _mediatype, char *pbuf, EASY_FRAME_INFO *frameinfo)
{
if (NULL != frameinfo)
{
if (frameinfo->height==1088) frameinfo->height=1080;
else if (frameinfo->height==544) frameinfo->height=540;
}
Easy_Bool bRet = 0;
int iRet = 0;
_channel_info* pChannel = (_channel_info*)_chPtr;
if (_mediatype == EASY_SDK_VIDEO_FRAME_FLAG)
{
if(frameinfo && frameinfo->length)
{
if( frameinfo->type == EASY_SDK_VIDEO_FRAME_I)
{
if(pChannel->fPusherInfo.rtmpHandle == 0)
{
pChannel->fPusherInfo.rtmpHandle = EasyRTMP_Create();
if (pChannel->fPusherInfo.rtmpHandle == NULL)
{
TRACE_LOG(pChannel->fLogHandle, "Fail to rtmp create failed ...\n");
return -1;
}
EasyRTMP_SetCallback(pChannel->fPusherInfo.rtmpHandle, __EasyRTMP_Callback, pChannel);
bRet = EasyRTMP_Connect(pChannel->fPusherInfo.rtmpHandle, pChannel->fCfgInfo.destRtmpAddr);
if (!bRet)
{
TRACE_LOG(pChannel->fLogHandle, "Fail to rtmp connect failed ...\n");
}
EASY_MEDIA_INFO_T mediaInfo;
memset(&mediaInfo, 0, sizeof(EASY_MEDIA_INFO_T));
mediaInfo.u32VideoFps = pChannel->fMediainfo.u32VideoFps;
mediaInfo.u32AudioSamplerate = 8000;
iRet = EasyRTMP_InitMetadata(pChannel->fPusherInfo.rtmpHandle, &mediaInfo, 1024);
if (iRet < 0)
{
TRACE_LOG(pChannel->fLogHandle, "Fail to Init Metadata ...\n");
}
}
EASY_AV_Frame avFrame;
memset(&avFrame, 0, sizeof(EASY_AV_Frame));
avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
avFrame.u32AVFrameLen = frameinfo->length;
avFrame.pBuffer = (unsigned char*)pbuf;
avFrame.u32VFrameType = EASY_SDK_VIDEO_FRAME_I;
//avFrame.u32TimestampSec = frameinfo->timestamp_sec;
//avFrame.u32TimestampUsec = frameinfo->timestamp_usec;
//
iRet = EasyRTMP_SendPacket(pChannel->fPusherInfo.rtmpHandle, &avFrame);
if (iRet < 0)
{
TRACE_LOG(pChannel->fLogHandle, "Fail to Send H264 Packet(I-frame) ...\n");
}
else
{
if(!pChannel->fHavePrintKeyInfo)
{
TRACE_LOG(pChannel->fLogHandle, "I\n");
pChannel->fHavePrintKeyInfo = true;
}
}
}
else
{
if(pChannel->fPusherInfo.rtmpHandle)
{
EASY_AV_Frame avFrame;
memset(&avFrame, 0, sizeof(EASY_AV_Frame));
avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
avFrame.u32AVFrameLen = frameinfo->length-4;
avFrame.pBuffer = (unsigned char*)pbuf+4;
avFrame.u32VFrameType = EASY_SDK_VIDEO_FRAME_P;
//avFrame.u32TimestampSec = frameinfo->timestamp_sec;
//avFrame.u32TimestampUsec = frameinfo->timestamp_usec;
iRet = EasyRTMP_SendPacket(pChannel->fPusherInfo.rtmpHandle, &avFrame);
if (iRet < 0)
{
TRACE_LOG(pChannel->fLogHandle, "Fail to Send H264 Packet(P-frame) ...\n");
}
else
{
if(!pChannel->fHavePrintKeyInfo)
{
TRACE_LOG(pChannel->fLogHandle, "P\n");
}
}
}
}
}
}
else if (_mediatype == EASY_SDK_MEDIA_INFO_FLAG)
{
if(pbuf != NULL)
{
EASY_MEDIA_INFO_T mediainfo;
memset(&(pChannel->fMediainfo), 0x00, sizeof(EASY_MEDIA_INFO_T));
memcpy(&(pChannel->fMediainfo), pbuf, sizeof(EASY_MEDIA_INFO_T));
TRACE_LOG(pChannel->fLogHandle,"RTSP DESCRIBE Get Media Info: video:%u fps:%u audio:%u channel:%u sampleRate:%u \n",
pChannel->fMediainfo.u32VideoCodec, pChannel->fMediainfo.u32VideoFps, pChannel->fMediainfo.u32AudioCodec, pChannel->fMediainfo.u32AudioChannel, pChannel->fMediainfo.u32AudioSamplerate);
}
}
return 0;
}
bool InitCfgInfo(void)
{
int i = 0;
gChannelInfoList.clear();
for(i = 0; i < MAX_CHANNEL_INDEX; i++)
{
_channel_info* pChannelInfo = new _channel_info();
if(pChannelInfo)
{
memset(pChannelInfo, 0, sizeof(_channel_info));
pChannelInfo->fCfgInfo.channelId = i;
pChannelInfo->fHavePrintKeyInfo = false;
sprintf(pChannelInfo->fCfgInfo.channelName, "channel%d",i);
strcpy(pChannelInfo->fCfgInfo.srcRtspAddr, GetIniKeyString(pChannelInfo->fCfgInfo.channelName, "rtsp", CONF_FILE_PATH));
strcpy(pChannelInfo->fCfgInfo.destRtmpAddr, GetIniKeyString(pChannelInfo->fCfgInfo.channelName, "rtmp", CONF_FILE_PATH));
pChannelInfo->fCfgInfo.option = GetIniKeyInt(pChannelInfo->fCfgInfo.channelName, "option", CONF_FILE_PATH);
if(strlen(pChannelInfo->fCfgInfo.srcRtspAddr) > 0 && strlen(pChannelInfo->fCfgInfo.destRtmpAddr) > 0)
{
gChannelInfoList.push_back(pChannelInfo);
}
}
}
return true;
}
void ReleaseSpace(void)
{
std::list<_channel_info*>::iterator it;
for(it = gChannelInfoList.begin(); it != gChannelInfoList.end(); it++)
{
_channel_info* pChannel = *it;
if (NULL != pChannel->fNVSHandle)
{
EasyRTSP_CloseStream(pChannel->fNVSHandle);
EasyRTSP_Deinit(&(pChannel->fNVSHandle));
pChannel->fNVSHandle = NULL;
}
if (NULL != pChannel->fPusherInfo.rtmpHandle)
{
EasyRTMP_Release(pChannel->fPusherInfo.rtmpHandle);
pChannel->fPusherInfo.rtmpHandle = NULL;
}
if(pChannel->fLogHandle)
{
TRACE_CloseLogFile(pChannel->fLogHandle);
pChannel->fLogHandle = NULL;
}
delete pChannel;
}
gChannelInfoList.clear();
}
int main(int argc, char * argv[])
{
InitCfgInfo();
int iret = 0;
iret = EasyRTMP_Activate(KEY);
if (iret <= 0)
{
printf("RTMP Activate error. ret=%d!!!\n", iret);
getchar();
return -1;
}
#ifdef _WIN32
extern char* optarg;
#endif
int ch;
atexit(ReleaseSpace);
iret = 0;
iret = EasyRTSP_Activate(RTSP_KEY);
if(iret <= 0)
{
printf("rtsp Activate error. ret=%d!!!\n", iret);
return -2;
}
std::list<_channel_info*>::iterator it;
for(it = gChannelInfoList.begin(); it != gChannelInfoList.end(); it++)
{
_channel_info* pChannel = *it;
pChannel->fLogHandle = TRACE_OpenLogFile(pChannel->fCfgInfo.channelName);
TRACE_LOG(pChannel->fLogHandle, "channel[%d] rtsp addr : %s\n", pChannel->fCfgInfo.channelId, pChannel->fCfgInfo.srcRtspAddr);
TRACE_LOG(pChannel->fLogHandle, "channel[%d] rtmp addr : %s\n", pChannel->fCfgInfo.channelId, pChannel->fCfgInfo.destRtmpAddr);
EasyRTSP_Init(&(pChannel->fNVSHandle));
if (NULL == pChannel->fNVSHandle)
{
TRACE_LOG(pChannel->fLogHandle, "%s rtsp init error. ret=%d!!!\n", pChannel->fCfgInfo.channelName , iret);
continue;
}
unsigned int mediaType = EASY_SDK_VIDEO_FRAME_FLAG | EASY_SDK_AUDIO_FRAME_FLAG;
EasyRTSP_SetCallback(pChannel->fNVSHandle, __RTSPSourceCallBack);
EasyRTSP_OpenStream(pChannel->fNVSHandle, pChannel->fCfgInfo.channelId, pChannel->fCfgInfo.srcRtspAddr, EASY_RTP_OVER_TCP, mediaType, 0, 0, pChannel, 1000, 0, pChannel->fCfgInfo.option, 0);
}
while(true)
{
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
return 0;
}
文章浏览阅读565次。本文主要介绍如何使用C#通过OPC方式连接PLC,并提供了相应的程序和学习资料,以便读者学习和使用。OPC服务器是一种软件,可以将PLC的数据转换为标准的OPC格式,允许其他软件通过标准接口读取或控制PLC的数据。此外,本文还提供了一些学习资料,包括OPC和PLC的基础知识,C#编程语言的教程和实例代码。这些资料可以帮助读者更好地理解和应用本文介绍的程序。1.该程序是通讯方式是CSharp通过OPC方式连接PLC,用这种方式连PLC不用考虑什么种类PLC,只要OPC服务器里有的PLC都可以连。_c#opc通信
文章浏览阅读1.6w次,点赞3次,收藏10次。实践环境物理机:Windows10教育版,操作系统版本 17763.914虚拟机:Ubuntu18.04.3桌面版在Hyper-V中的刚安装好Ubuntu虚拟机之后,会发现鼠标滑动很不顺畅,也不能向虚拟机中拖拽文件或者复制内容。在VMware中,可以通过安装VMware tools来使物理机和虚拟机之间达到更好的交互。在Hyper-V中,也有这样的工具。这款工具可以完成更好的鼠标交互,我的..._win10 hyper-v ubuntu18.04 文件拷贝
文章浏览阅读156次。前言互联网时代,瞬息万变。一个小小的走错,就有可能落后于别人。我们没办法去预测任何行业、任何职业未来十年会怎么样,因为未来谁都不能确定。只能说只要有互联网存在,程序员依然是个高薪热门行业。只要跟随着时代的脚步,学习新的知识。程序员是不可能会消失的,或者说不可能会没钱赚的。我们经常可以听到很多人说,程序员是一个吃青春饭的行当。因为大多数人认为这是一个需要高强度脑力劳动的工种,而30岁、40岁,甚至50岁的程序员身体机能逐渐弱化,家庭琐事缠身,已经不能再进行这样高强度的工作了。那么,这样的说法是对的么?_类初始化一个静态属性 为线程池
文章浏览阅读1w次,点赞13次,收藏43次。说来也是惭愧,一直以来,在装环境的时候都会从官网下载Maven。然后再在idea里配置Maven。以为从官网下载的Maven是必须的步骤,直到今天才得知,idea有捆绑的 Maven 我们只需要搞一个配置文件就行了无需再官网下载Maven包以后再在新电脑装环境的时候,只需要下载idea ,网上找一个Maven的配置文件 放到 默认的 包下面就可以了!也省得每次创建项目都要重新配一次Maven了。如果不想每次新建项目都要重新配置Maven,一种方法就是使用默认的配置,另一种方法就是配置 .._安装idea后是不是不需要安装maven了?
文章浏览阅读45次。家是我们一生中最重要的地方,小时候,我们在这里哭、在这里笑、在这里学习走路,在这里有我们最真实的时光,用相机把它记下吧。 很多家庭在拍摄孩子时有一个看法,认为儿童摄影团购必须是在风景秀丽的户外,即便是室内那也是像大酒店一样...
文章浏览阅读429次。Dockerfile介绍Dockerfile是构建镜像的指令文件,由一组指令组成,文件中每条指令对应linux中一条命令,在执行构建Docker镜像时,将读取Dockerfile中的指令,根据指令来操作生成指定Docker镜像。Dockerfile结构:主要由基础镜像信息、维护者信息、镜像操作指令、容器启动时执行指令。每行支持一条指令,每条指令可以携带多个参数。注释可以使用#开头。指令说明FROM 镜像 : 指定新的镜像所基于的镜像MAINTAINER 名字 : 说明新镜像的维护(制作)人,留下_rocker/r-base镜像
文章浏览阅读223次。该系统将提供便捷的信息发布、物业报修、社区互动等功能,为小区居民提供更加便利、高效的服务。引言: 随着城市化进程的加速,小区管理成为一个日益重要的任务。因此,设计一个基于微信小程序的小区管理系统成为了一项具有挑战性和重要性的毕设课题。本文将介绍该小区管理系统的设计思路和功能,以期为小区提供更便捷、高效的管理手段。四、总结与展望: 通过本次毕设项目,我们实现了一个基于微信小程序的小区管理系统,为小区居民提供了更加便捷、高效的服务。通过该系统的设计与实现,能够提高小区管理水平,提供更好的居住环境和服务。_ssm基于微信小程序的公寓生活管理系统
文章浏览阅读635次。文章来源i春秋入坑Ubuntu半年多了记得一开始学的时候基本一星期重装三四次=-= 尴尬了 觉得自己差不多可以的时候 就吧Windows10干掉了 c盘装Ubuntu 专心学习. 这里主要来说一下使用Ubuntu的正确姿势Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的开源GNU/Linux操作系统,Ubuntu 是基于DebianGNU/Linux,支..._ubuntu安装攻击工具包
文章浏览阅读335次。需求:C++中将BYTE型数组传递给Java中,考虑到内存释放问题,未采用通过返回值进行数据传递。public class demoClass{public native boolean getData(byte[] tempData);}JNIEXPORT jboolean JNICALL Java_com_core_getData(JNIEnv *env, jobject thisObj, jbyteArray tempData){ //resultsize为s..._jni引用byte[]
文章浏览阅读2.1k次,点赞5次,收藏30次。本教程代码开源:GitHub 欢迎star文章目录一、平面模型分割1. 代码2. 说明3. 运行二、圆柱模型分割1. 代码2. 说明3. 运行三、欧几里得聚类提取1. 代码2. 说明3. 运行四、区域生长分割1. 代码2. 说明3. 运行五、基于最小切割的分割1. 代码2. 说明3. 运行六、使用 ProgressiveMorphologicalFilter 分割地面1. 代码2. 说明3. 运行一、平面模型分割在本教程中,我们将学习如何对一组点进行简单的平面分割,即找到支持平面模型的点云中的所有._pclpy.pcl.pointcloud.pointxyzi转为numpy
文章浏览阅读141次。一 其实在 skyeye 上移植 arm-linux 并非难事,网上也有不少资料, 只是大都遗漏细节, 以致细微之处卡壳,所以本文力求详实清析, 希望能对大家有点用处。本文旨在将 arm-linux 在 skyeye 上搭建起来,并在 arm-linux 上能成功 mount NFS 为目标, 最终我们能在 arm-linux 里运行我们自己的应用程序. 二 安装 Sky..._nfs启动 arm
文章浏览阅读598次,点赞2次,收藏5次。00为了形成一个体系,想将前面学过的一些东西都拉来放在一起总结总结,方便学习,方便记忆。攻防世界 Pwn 新手攻防世界 Pwn 进阶 第一页01 4-ReeHY-main-100超详细的wp1超详细的wp203 format2栈迁移的两种作用之一:栈溢出太小,进行栈迁移从而能够写入更多shellcode,进行更多操作。栈迁移一篇搞定有个陌生的函数。C 库函数 void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 _pwn snprintf