精华内容
下载资源
问答
  • 事件通知接口对于收方来说是无论如何都要接收的,接收后缓存、处理、生成新的内容传递给下游,比如订单生成系统通知收付款系统,收发双方是强关联的。字典查询接口对于收方来说是可有可无的,收方正常处理自己的业务...

    之前的文章不知道有没有讲过,核间交互从数据获取的方式来说可以分成两类,一类是消息接口,可以理解成事件通知,一类是状态接口,可以理解成字典查询。

    我们来看看这两个接口的特点。事件通知接口对于收方来说是无论如何都要接收的,接收后缓存、处理、生成新的内容传递给下游,比如订单生成系统通知收付款系统,收发双方是强关联的。字典查询接口对于收方来说是可有可无的,收方正常处理自己的业务,遇到需要某个数据的时候去字典里查一下,收方不关心字典里的数据是不是最新,比如购物网站里的根据个人偏好推送的页面,收发双方是弱关联的。这篇文章讨论下状态接口的实现方式。

    一般来说,linux C不同种类的接口的底层实现都是共享内存。

    68e54bc4728f88f2d5e758a2a004c99e.png

    实现这种底层通信机制的团队往往是专业的框架团队,这样的团队倾向于以纯软件技术的角度去实现这种交互机制。这句话什么意思呢?框架团队不希望感知业务细节,实现状态通信的时候一般会实现成下图这样。我们先看StatusHead,包括状态接口的类型comType(软件里可能存在很多种不同业务的状态消息),包括每个状态的内存大小len,包括状态的个数,举个例子描述下:有个软件叫吃苹果,想通过状态接口获取苹果的颜色comType=APPLE_COLOR,苹果呢有100个statusNum=100,颜色状态的存储假设用8个字节len=8。

    2150a0fdcefd3942850b959d51ffb8b1.png

    有了Head,再来看看内存的构成,第一个Head对应的一个Body里存放着100个不同苹果的颜色,第二个Head对应的Body里存放着100个不同桃子的颜色,第三个Head对应的Body里存放着100个不同苹果的大小。

    33f93886d0faa69b3036ecde6df87e2b.png

    当然你也可以选择Head集中在一起,Body集中在一起,区别不大。

    好,talk is cheap,我们已经talk完了。这个方案很通用,有问题吗?有问题。我们知道空间局部性原理讲,连续访问内存距离近的内存将获得更好的软件性能,连续访问这个特性是由业务特性决定的,如果我访问完第一个苹果的颜色,继续访问第二个苹果的颜色,上面的设计就是高效的,但如果我访问完第一个苹果的颜色,继续访问第一个苹果的大小,上面的设计效率就低得吓人。理论上,如果是连续访问的内存距离小于一个cache line,局部性能差百倍,如果大于cache line,可能差数十倍。

    所以对于状态接口,应该支持根据收方读取特性来调整内存排布的功能。方案有二。

    第一种方案是最简单易行的。不需要修改基础设施,注册一个comType,多个状态接口合并成一个大接口。对发方来说,多个接口发送的位置、条件不同,而每次发送都需要进行大接口的memcpy,接口发送开销将变成原来的2倍。实例化解释一下,更新苹果的颜色需要同时拷贝颜色和大小,更新大小又需要重复拷贝一遍。这个可以优化一下,维护一个100个苹果的flag数组,只要颜色或者大小有更新,就flag置1,最后遍历100找flag为1的发送一次。我尝试修改了某业务代码合并了4个状态接口,发方性能恶化了10%,收方IPC(inst per cycle)提升了3%。

    第一种方案对发方来说是不能接受的,必须寻找一种免拷贝的方式,至少拷贝的开销要与之前保持一致。初步思考了下第二种方案,可以用下面结构:

    33bc8e53668d43fe7613c9464049fcc8.png

    叫子状态Head。num存子状态个数,offset存相对于基地址的便宜,先存color则offset[0]为0,offset[1]为8。修改send函数,如果发现有子状态,则拷贝的时候按offset拷。

    第二种方案尚未实践过。实践完后续再更新。

    展开全文
  • 软件项目实训及课程设计指导——如何降低软件系统中程序类之间耦合关系(上篇)1、分离软件应用系统中各个模块类的接口定义和对应接口的具体功能实现面向对象程序类设计的五大原则中的"开放-封闭原则" ( OCP,Open-...

    软件项目实训及课程设计指导——如何降低软件系统中程序类之间耦合关系(上篇)

    1、分离软件应用系统中各个模块类的接口定义和对应接口的具体功能实现

    面向对象程序类设计的五大原则中的"开放-封闭原则" ( OCP,Open-Close Principle)倡导分离接口的定义和接口的具体功能实现的设计原则。这在软件应用系统的需求是多变或者软件应用系统的功能实现存在多样化的软件应用系统的设计中,更应该遵守该设计的原则。

    通过分离软件应用系统中各个模块类的接口定义和接口的具体功能实现,能够减少由于功能实现方式的变化或者多样化所带来的对目标程序类的被动改变;或者即使功能实现发生了变化,由于上层的使用者(在此为服务的请求者)是与功能的抽象接口相互关联,而没有与功能的具体实现程序类直接关联,并不会造成被动的变化修改。

    如下示例图是说明本设计原则的UML类图示例,其中的客户端(也就是软件应用系统中的业务处理层相关功能类中的方法)是通过数据访问接口操作访问目标数据库系统中的数据,而不是直接应用指定的目标数据库系统对应的数据访问实现类来操作访问目标数据库系统中的数据。

    1cec0e3b9f737b69c1ce3c5e59d8259a.png

    这样的设计结果,可以使得软件应用系统的设计和开发人员可以在不修改软件应用系统中原有的功能代码的前提下(不必改动源代码或者二进制代码——二进制的可执行程序、动态链接库DLL或者Java中的JAR包文件等),而实现对软件应用系统中的软件功能进行扩展。

    下图所示为银行账户信息管理系统在开发实现的开发规范要求的文档中,要求本应用系统的开发人员严格遵守将应用系统中的各个功能类分为接口定义和接口的具体功能实现两部分。比如,对于应用系统中的用户信息的数据库表中的数据访问功能的要求是由UserManageDAOInterface接口内定义的各个功能方法体现,而具体的数据访问功能的实现代码则是放在UserManageDAOJDBCImple实现类中(它为UserManageDAOInterface接口的具体实现)。请见下图中黑体所标识的代码或者文件名称。

    7e1e9183547b2226902ceeb921b76c44.png

    2、利用接口类型的对象作为相关层之间或者两个程序类之间的连接点

    (1)深入理解依赖倒置设计原则

    Rober C.Martin(作者注:面向对象设计、模式、UML、敏捷方法学和极限编程领域的资深顾问,他的详细介绍及畅销书等信息读者可以搜索获得。)这样描述面向对象程序类设计五大原则中的依赖倒置原则(DIP, Dependency Inversion Principle):

    1)上层模块不应该直接依赖于下层的模块,它们共同依赖于一个抽象(父类不能依赖子类,它们都要依赖抽象类);

    2)抽象不能依赖于具体,具体应该依赖于抽象——因为抽象层次包含的是软件应用系统中通用的业务逻辑,一般是相对稳定的或者是相对变化不频繁的。而具体功能模块一般都包含有与具体功能实现有关的算法和逻辑等内容,一般是易变的——技术更新、应用环境改变、功能需求发生变化等各种可变因素。

    此外,依赖抽象是实现软件应用系统中代码扩展和运行期内绑定(动态多态)的技术实现基础——只要是该抽象类的子类(在实际编程实现时如果将此处的"抽象"设计为编程语言中的接口,则它就是接口的实现类;而如果将此处的"抽象"设计为某种程序语言中的抽象类,它就为基于抽象类的子类),都可以被类的使用者使用。

    (2)如何合理和正确地应用依赖倒置设计原则

    "依赖倒置原则"的本质是要求软件应用系统的设计和开发实现人员将软件应用系统中程序类之间的关系建立在"抽象接口"的基础上——也就是要求软件应用系统的设计人员为了能够消解软件应用系统中的两个模块之间的依赖关系,应该在两个模块之间定义出一个"抽象接口"、并利用此接口对象作为层之间(比如业务处理层和数据访问层)或者两个程序类之间的连接点;上层模块调用在"抽象接口"中定义的方法(该方法代表具体的功能定义),而下层模块则实现该接口中定义的各个功能方法(该方法代表具体的功能实现)。

    下图中的示例UML类图说明了"依赖倒置原则"的设计原理,应用"抽象接口"连接服务的请求者和服务功能的实现者,并根据应用的具体需要可以提供多个不同形式的功能实现,从而适应软件应用系统的可变性。

    2fd4afaa795c175995bd176609513dd3.png

    因为软件应用系统中的需求的改变,不仅可能是由于功能实现的多样化造成的,也可能是由于程序类之间的依赖关系发生了改变——如软件应用系统的业务逻辑的变化或者对业务流程的调整等因素而造成的被动变化。

    (3)遵守依赖倒置设计原则的应用示例

    作者在下图所示的银行账户管理系统项目的开发实现中,同样也严格遵守将软件应用系统中的各个层之间以及各个功能模块之间都利用接口对象作为连接点。比如,在本软件应用系统的业务层中为了能够达到与软件应用系统中的持久层的各个数据访问对象组件(DAO组件)相互分离,利用在系统中定义的接口UserDAOServiceInterface类型的对象实例userDAOServiceImpleBean作为两者之间的连接,请见示例图中黑体所标识的程序代码。

    8f31b432f5dc73008cb64218a31514b5.png

    基于此设计原则,也要求软件应用系统的设计和开发实现人员在设计程序类中的成员方法的参数(形参和返回参数)时应该尽可能选择为接口类型的对象、而不要选择接口的具体实现类的类型对象。

    比如下面的代码示例中的getUserInfo方法的返回值的对象类型可以改用List接口类型而不要返回ArrayList类型——因为List接口有多种不同形式的实现类,当getUserInfo方法扩展为返回List接口的其它实现类时,不会影响到调用getUserInfo方法的上层程序代码;同样对setUserInfo方法的参数也应该由ArrayList类型改变为用List接口类型的参数。

    因此,如下代码示例中的setUserInfo和getUserInfo方法的返回参数应该尽可能设计为返回接口类型的对象。

    89834384edb6ae91f459b084090c260f.png

    3、在软件应用系统的功能实现中尽可能针对"抽象"编程而不针对具体子类编程

    读者首先要理解作者在此处所提及的"抽象"一般都是面向对象设计方法中的"抽象"而不是某种编程语言(如Java或者C#等)中的抽象类,当将此处的"抽象"转变为某种编程语言的程序实现代码时,可以最终设计为接口(Interface)类型或者抽象类(Abstract Class)类型。

    因此,在软件应用系统的功能实现中尽可能针对"抽象"编程而不针对具体子类编程的设计要求的核心思想也就是要求程序开发人员在具体编程时尽可能声明抽象类型(如Java编程语言中的接口类型interface或者抽象基类abstract)的对象——这包括在方法的参数或者程序类的成员属性声明中,这样就可以充分地应用面向对象OOP技术中的动态多态机制达到灵活地对不同的子类型的对象访问。

    下面所示的程序代码示例体现这种编程要求——对其中的各个对象类型都尽可能声明为抽象类型,并请注意其中黑体标识的代码。

    interface SomeInterface{//接口中的方法定义在此省略}class SomeInterfaceImple implements SomeInterface{//对接口中的方法具体实现代码在此省略}class OtherClientApp{private SomeInterface oneObj;public void someMethod(SomeInterface twoObj){          //方法内的功能实现代码在此省略}}

    因为面向对象程序类设计五大原则中的"依赖倒置原则"倡导将程序类之间的关系建立在抽象接口的基础上,因此上述所示的程序代码也同样遵守了"依赖倒置原则"。但如下示例代码是不符合"依赖倒置"的设计原则,因为OtherClientApp是直接依赖于SomeInterface接口的实现类SomeInterfaceImple而不是依赖于抽象的接口SomeInterface。

    class OtherClientApp{private SomeInterfaceImple oneObj;public void someMethod(SomeInterface twoObj){     //方法内的功能实现代码在此省略}}
    展开全文
  • 一个好的转发模块,首先要低延迟!其次足够稳定、灵活、有状态反馈机制、资源...2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;...

    一个好的转发模块,首先要低延迟!其次足够稳定灵活、有状态反馈机制、资源占用低,跨平台,最好以接口形式提供,便于第三方系统集成。

    以Windows平台为例,我们的考虑的点如下

    1. 拉流:通过RTSP直播播放SDK的数据回调接口,拿到音视频数据;

    2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;

    3. 录像:如果需要录像,借助RTSP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;

    4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。

    5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;

    6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;

    7. 转推RTMP实时静音:只需要在传audio数据的地方,加个判断即可;

    8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;

    9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器。

    系统设计架构图

    Windows转发demo分析

    大牛直播SDK的转发demo,Windows平台,对应C++ demo工程:WIN-RelaySDK-CPP-Demo,如需下载demo源码,参看 Github

    1. 拉流:拉流和播放有些类似,但不需要播放(也就是说不要解码,资源消耗非常低),在做过基础的参数配置之后(对应demo里面OpenPullHandle()),设置音视频数据回调,然后调用StartPullStream()即可:

    1.1 基础参数设置:

    bool nt_stream_relay_wrapper::OpenPullHandle(const std::string& url, bool is_rtsp_tcp_mode, bool is_mute)
    {
    	if ( pull_handle_ != NULL )
    		return true;
    
    	if ( url.empty() )
    		return false;
    
    	duration_ = 0;
    
    	NT_HANDLE pull_handle = NULL;
    
    	ASSERT( pull_api_ != NULL );
    	if (NT_ERC_OK != pull_api_->Open(&pull_handle, render_wnd_, 0, NULL))
    	{
    		return false;
    	}
    
    	ASSERT(pull_handle != NULL);
    
    	pull_api_->SetEventCallBack(pull_handle, this, &NT_Pull_SDKEventHandle);
    
    	pull_api_->SetBuffer(pull_handle, 0);
    	pull_api_->SetFastStartup(pull_handle, 1);
    	pull_api_->SetRTSPTcpMode(pull_handle, is_rtsp_tcp_mode ? 1 : 0);
    	pull_api_->SetMute(pull_handle, is_mute ? 1 : 0);
    
    	if ( NT_ERC_OK != pull_api_->SetURL(pull_handle, url.c_str()) )
    	{
    		pull_api_->Close(pull_handle);
    		pull_handle = NULL;
    		return false;
    	}
    
    	if ( setting_pos_ >= 0ll )
    	{
    		pull_api_->SetPos(pull_handle, setting_pos_);
    	}
    
    	pull_handle_ = pull_handle;
    
    	return true;
    }

    1.2 设置音视频数据回调:

    	pull_api_->SetPullStreamVideoDataCallBack(pull_handle_, this, &SP_SDKPullStreamVideoDataHandle);
    
    	pull_api_->SetPullStreamAudioDataCallBack(pull_handle_, this, &SP_SDKPullStreamAudioDataHandle);

    1.3 开始拉流:

    	auto ret = pull_api_->StartPullStream(pull_handle_);
    	if ( NT_ERC_OK != ret )
    	{
    		if ( !is_playing_ )
    		{
    			pull_api_->Close(pull_handle_);
    			pull_handle_ = NULL;
    		}
    		
    		return false;
    	}

    拉流整体代码如下:

    bool nt_stream_relay_wrapper::StartPull(const std::string& url, bool is_rtsp_tcp_mode, bool is_transcode_aac)
    {
    	if ( is_pulling_ )
    		return false;
    
    	if ( !OpenPullHandle(url, is_rtsp_tcp_mode) )
    		return false;
    
    	pull_api_->SetPullStreamVideoDataCallBack(pull_handle_, this, &SP_SDKPullStreamVideoDataHandle);
    
    	pull_api_->SetPullStreamAudioDataCallBack(pull_handle_, this, &SP_SDKPullStreamAudioDataHandle);
    
    	pull_api_->SetPullStreamAudioTranscodeAAC(pull_handle_, is_transcode_aac? 1: 0);
    
    	auto ret = pull_api_->StartPullStream(pull_handle_);
    	if ( NT_ERC_OK != ret )
    	{
    		if ( !is_playing_ )
    		{
    			pull_api_->Close(pull_handle_);
    			pull_handle_ = NULL;
    		}
    		
    		return false;
    	}
    
    	is_pulling_ = true;
    
    	return true;
    }

    2. 停止拉流:

    停止拉流流程比较简单,先判断是否在拉流状态,如果拉流,调用StopPullStream() 即可,如没有预览画面,调用Close()接口关闭拉流实例。

    void nt_stream_relay_wrapper::StopPull()
    {
    	if ( !is_pulling_ )
    		return;
    
    	pull_api_->StopPullStream(pull_handle_);
    
    	if ( !is_playing_ )
    	{
    		pull_api_->Close(pull_handle_);
    		pull_handle_ = NULL;
    	}
    
    	is_pulling_ = false;
    }

    3. 拉流端预览:

    拉流端预览,说白了就是播放拉流数据,流程比较简单,demo调用如下,如不需要播放声音,调用SetMute(),实时打开/关闭即可:

    bool nt_stream_relay_wrapper::StartPlay(const std::string& url, bool is_rtsp_tcp_mode, bool is_mute)
    {
    	if ( is_playing_ )
    		return false;
    
    	if ( !OpenPullHandle(url, is_rtsp_tcp_mode, is_mute) )
    		return false;
    
    	pull_api_->SetMute(pull_handle_, is_mute ? 1 : 0);
    
    	auto ret = pull_api_->StartPlay(pull_handle_);
    	if ( NT_ERC_OK != ret )
    	{
    		if ( !is_pulling_ )
    		{
    			pull_api_->Close(pull_handle_);
    			pull_handle_ = NULL;
    		}
    
    		return false;
    	}
    
    	is_playing_ = true;
    
    	return true;
    }

    4. 拉流端关闭预览:

    void nt_stream_relay_wrapper::StopPlay()
    {
    	if ( !is_playing_ )
    		return;
    
    	pull_api_->StopPlay(pull_handle_);
    
    	if ( !is_pulling_ )
    	{
    		pull_api_->Close(pull_handle_);
    		pull_handle_ = NULL;
    	}
    
    	is_playing_ = false;
    }

    5. 开始推流到RTMP服务器:

    推流的流程,如之前所述,调用RTMP推送模块,然后数据源传编码后的音视频数据即可,下图的demo源码,同时展示了,RTSP流获取到后,转推RTMP的时候,数据解密的处理:

    bool nt_stream_relay_wrapper::StartPush(const std::string& url)
    {
    	if ( is_pushing_ )
    		return false;
    
    	if ( url.empty() )
    		return false;
    
    	if ( !OpenPushHandle() )
    		return false;
    
    	auto push_handle = GetPushHandle();
    	ASSERT(push_handle != nullptr);
    
    	ASSERT(push_api_ != NULL);
    	if ( NT_ERC_OK != push_api_->SetURL(push_handle, url.c_str(), NULL) )
    	{
    		if ( !is_started_rtsp_stream_ )
    		{
    			push_api_->Close(push_handle);
    			SetPushHandle(nullptr);
    		}
    
    		return false;
    	}
    
    	// 加密测试 +++
    
    	// push_api_->SetRtmpEncryptionOption(push_handle, url.c_str(), 1, 1);
    
    	// NT_BYTE test_key[16] = {'1', '2', '3'};
    
    	// push_api_->SetRtmpEncryptionKey(push_handle, url.c_str(), test_key, 16);
    
    	// 加密测试 --
    
    	if ( NT_ERC_OK != push_api_->StartPublisher(push_handle, NULL) )
    	{
    		if ( !is_started_rtsp_stream_ )
    		{
    			push_api_->Close(push_handle);
    			SetPushHandle(nullptr);
    		}
    
    		return false;
    	}
    
    
    	// // test push rtsp ++
    
    	// push_api_->SetPushRtspTransportProtocol(push_handle, 1);
    	// // push_api_->SetPushRtspTransportProtocol(push_handle, 2);
    
    	// push_api_->SetPushRtspURL(push_handle, "rtsp://player.daniulive.com:554/liverelay111.sdp");
    
    	// push_api_->StartPushRtsp(push_handle, 0);
    
    	// // test push rtsp--
    
    	is_pushing_ = true;
    
    	return true;
    }

    6. 传递转推RTMP数据:

    void nt_stream_relay_wrapper::OnVideoDataHandle(NT_HANDLE handle, NT_UINT32 video_codec_id, 
    	NT_BYTE* data, NT_UINT32 size, NT_SP_PullStreamVideoDataInfo* info)
    {
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if ( pull_handle_ != handle )
    		return;
    
    	if (data == NULL)
    		return;
    
    	if (size < 1)
    		return;
    
    	if (info == NULL)
    		return;
    
    	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);
    
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if (push_handle_ == NULL)
    		return;
    
    	push_api_->PostVideoEncodedDataV2(push_handle_, video_codec_id,
    		data, size, info->is_key_frame_, info->timestamp_, info->presentation_timestamp_);
    }
    
    void nt_stream_relay_wrapper::OnAudioDataHandle(NT_HANDLE handle, NT_UINT32 auido_codec_id,
    	NT_BYTE* data, NT_UINT32 size, NT_SP_PullStreamAuidoDataInfo* info)
    {
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if (pull_handle_ != handle)
    		return;
    
    	if (data == NULL)
    		return;
    
    	if (size < 1)
    		return;
    
    	if (info == NULL)
    		return;
    
    	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);
    
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if (push_handle_ == NULL)
    		return;
    
    	push_api_->PostAudioEncodedData(push_handle_, auido_codec_id, data, size,
    		info->is_key_frame_, info->timestamp_, 
    		info->parameter_info_, info->parameter_info_size_);
    }

    7. 关闭实时RTMP转推

    void nt_stream_relay_wrapper::StopPush()
    {
    	if ( !is_pushing_ )
    		return;
    
    	is_pushing_ = false;
    
    	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);
    
    	if ( nullptr == push_handle_ )
    		return;
    
    	push_api_->StopPublisher(push_handle_);
    
    	// // test push rtsp ++
    	// push_api_->StopPushRtsp(push_handle_);
    	// // test push rtsp--
    
    
    	if ( !is_started_rtsp_stream_ )
    	{
    		push_api_->Close(push_handle_);
    
    		push_handle_ = nullptr;
    	}
    }

    以上就是RTSP或RTMP流转RTMP推送的流程,感兴趣的开发者,可做设计参考。

    展开全文
  • 一个好的转发模块,首先要低延迟!其次足够稳定、灵活、有状态反馈...2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;3. 录像:...

    一个好的转发模块,首先要低延迟!其次足够稳定灵活、有状态反馈机制、资源占用低,跨平台,最好以接口形式提供,便于第三方系统集成。

    以Windows平台为例,我们的考虑的点如下

    1. 拉流:通过RTSP直播播放SDK的数据回调接口,拿到音视频数据;

    2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;

    3. 录像:如果需要录像,借助RTSP直播播放SDK,拉到音视频数据后,直接存储MP4文件即可;

    4. 快照:如果需要实时快照,拉流后,解码调用播放端快照接口,生成快照,因为快照涉及到video数据解码,如无必要,可不必开启,不然会额外消耗性能。

    5. 拉流预览:如需预览拉流数据,只要调用播放端的播放接口,即可实现拉流数据预览;

    6. 数据转AAC后转发:考虑到好多监控设备出来的音频可能是PCMA/PCMU的,如需要更通用的音频格式,可以转AAC后,在通过RTMP推送;

    7. 转推RTMP实时静音:只需要在传audio数据的地方,加个判断即可;

    8. 拉流速度反馈:通过RTSP播放端的实时码率反馈event,拿到实时带宽占用即可;

    9. 整体网络状态反馈:考虑到有些摄像头可能会临时或异常关闭,RTMP服务器亦是,可以通过推拉流的event回调状态,查看那整体网络情况,如此界定:是拉不到流,还是推不到RTMP服务器。

    系统设计架构图

    af11244a06944e69150d12f1e69fb489.png

    ​Windows转发demo分析

    大牛直播SDK的转发demo,Windows平台,对应C++ demo工程:WIN-RelaySDK-CPP-Demo,如需下载demo源码,参看 Github

    1. 拉流:拉流和播放有些类似,但不需要播放(也就是说不要解码,资源消耗非常低),在做过基础的参数配置之后(对应demo里面OpenPullHandle()),设置音视频数据回调,然后调用StartPullStream()即可:

    1.1 基础参数设置:

    bool nt_stream_relay_wrapper::OpenPullHandle(const std::string& url, bool is_rtsp_tcp_mode, bool is_mute)
    {
    	if ( pull_handle_ != NULL )
    		return true;
    
    	if ( url.empty() )
    		return false;
    
    	duration_ = 0;
    
    	NT_HANDLE pull_handle = NULL;
    
    	ASSERT( pull_api_ != NULL );
    	if (NT_ERC_OK != pull_api_->Open(&pull_handle, render_wnd_, 0, NULL))
    	{
    		return false;
    	}
    
    	ASSERT(pull_handle != NULL);
    
    	pull_api_->SetEventCallBack(pull_handle, this, &NT_Pull_SDKEventHandle);
    
    	pull_api_->SetBuffer(pull_handle, 0);
    	pull_api_->SetFastStartup(pull_handle, 1);
    	pull_api_->SetRTSPTcpMode(pull_handle, is_rtsp_tcp_mode ? 1 : 0);
    	pull_api_->SetMute(pull_handle, is_mute ? 1 : 0);
    
    	if ( NT_ERC_OK != pull_api_->SetURL(pull_handle, url.c_str()) )
    	{
    		pull_api_->Close(pull_handle);
    		pull_handle = NULL;
    		return false;
    	}
    
    	if ( setting_pos_ >= 0ll )
    	{
    		pull_api_->SetPos(pull_handle, setting_pos_);
    	}
    
    	pull_handle_ = pull_handle;
    
    	return true;
    }
    

    1.2 设置音视频数据回调:

    	pull_api_->SetPullStreamVideoDataCallBack(pull_handle_, this, &SP_SDKPullStreamVideoDataHandle);
    
    	pull_api_->SetPullStreamAudioDataCallBack(pull_handle_, this, &SP_SDKPullStreamAudioDataHandle);

    1.3 开始拉流:

    	auto ret = pull_api_->StartPullStream(pull_handle_);
    	if ( NT_ERC_OK != ret )
    	{
    		if ( !is_playing_ )
    		{
    			pull_api_->Close(pull_handle_);
    			pull_handle_ = NULL;
    		}
    		
    		return false;
    	}

    拉流整体代码如下:

    bool nt_stream_relay_wrapper::StartPull(const std::string& url, bool is_rtsp_tcp_mode, bool is_transcode_aac)
    {
    	if ( is_pulling_ )
    		return false;
    
    	if ( !OpenPullHandle(url, is_rtsp_tcp_mode) )
    		return false;
    
    	pull_api_->SetPullStreamVideoDataCallBack(pull_handle_, this, &SP_SDKPullStreamVideoDataHandle);
    
    	pull_api_->SetPullStreamAudioDataCallBack(pull_handle_, this, &SP_SDKPullStreamAudioDataHandle);
    
    	pull_api_->SetPullStreamAudioTranscodeAAC(pull_handle_, is_transcode_aac? 1: 0);
    
    	auto ret = pull_api_->StartPullStream(pull_handle_);
    	if ( NT_ERC_OK != ret )
    	{
    		if ( !is_playing_ )
    		{
    			pull_api_->Close(pull_handle_);
    			pull_handle_ = NULL;
    		}
    		
    		return false;
    	}
    
    	is_pulling_ = true;
    
    	return true;
    }
    

    2. 停止拉流:

    停止拉流流程比较简单,先判断是否在拉流状态,如果拉流,调用StopPullStream() 即可,如没有预览画面,调用Close()接口关闭拉流实例。

    void nt_stream_relay_wrapper::StopPull()
    {
    	if ( !is_pulling_ )
    		return;
    
    	pull_api_->StopPullStream(pull_handle_);
    
    	if ( !is_playing_ )
    	{
    		pull_api_->Close(pull_handle_);
    		pull_handle_ = NULL;
    	}
    
    	is_pulling_ = false;
    }

    3. 拉流端预览:

    拉流端预览,说白了就是播放拉流数据,流程比较简单,demo调用如下,如不需要播放声音,调用SetMute(),实时打开/关闭即可:

    bool nt_stream_relay_wrapper::StartPlay(const std::string& url, bool is_rtsp_tcp_mode, bool is_mute)
    {
    	if ( is_playing_ )
    		return false;
    
    	if ( !OpenPullHandle(url, is_rtsp_tcp_mode, is_mute) )
    		return false;
    
    	pull_api_->SetMute(pull_handle_, is_mute ? 1 : 0);
    
    	auto ret = pull_api_->StartPlay(pull_handle_);
    	if ( NT_ERC_OK != ret )
    	{
    		if ( !is_pulling_ )
    		{
    			pull_api_->Close(pull_handle_);
    			pull_handle_ = NULL;
    		}
    
    		return false;
    	}
    
    	is_playing_ = true;
    
    	return true;
    }

    4. 拉流端关闭预览:

    void nt_stream_relay_wrapper::StopPlay()
    {
    	if ( !is_playing_ )
    		return;
    
    	pull_api_->StopPlay(pull_handle_);
    
    	if ( !is_pulling_ )
    	{
    		pull_api_->Close(pull_handle_);
    		pull_handle_ = NULL;
    	}
    
    	is_playing_ = false;
    }

    5. 开始推流到RTMP服务器:

    推流的流程,如之前所述,调用RTMP推送模块,然后数据源传编码后的音视频数据即可,下图的demo源码,同时展示了,RTSP流获取到后,转推RTMP的时候,数据解密的处理:

    bool nt_stream_relay_wrapper::StartPush(const std::string& url)
    {
    	if ( is_pushing_ )
    		return false;
    
    	if ( url.empty() )
    		return false;
    
    	if ( !OpenPushHandle() )
    		return false;
    
    	auto push_handle = GetPushHandle();
    	ASSERT(push_handle != nullptr);
    
    	ASSERT(push_api_ != NULL);
    	if ( NT_ERC_OK != push_api_->SetURL(push_handle, url.c_str(), NULL) )
    	{
    		if ( !is_started_rtsp_stream_ )
    		{
    			push_api_->Close(push_handle);
    			SetPushHandle(nullptr);
    		}
    
    		return false;
    	}
    
    	// 加密测试 +++
    
    	// push_api_->SetRtmpEncryptionOption(push_handle, url.c_str(), 1, 1);
    
    	// NT_BYTE test_key[16] = {'1', '2', '3'};
    
    	// push_api_->SetRtmpEncryptionKey(push_handle, url.c_str(), test_key, 16);
    
    	// 加密测试 --
    
    	if ( NT_ERC_OK != push_api_->StartPublisher(push_handle, NULL) )
    	{
    		if ( !is_started_rtsp_stream_ )
    		{
    			push_api_->Close(push_handle);
    			SetPushHandle(nullptr);
    		}
    
    		return false;
    	}
    
    
    	// // test push rtsp ++
    
    	// push_api_->SetPushRtspTransportProtocol(push_handle, 1);
    	// // push_api_->SetPushRtspTransportProtocol(push_handle, 2);
    
    	// push_api_->SetPushRtspURL(push_handle, "rtsp://player.daniulive.com:554/liverelay111.sdp");
    
    	// push_api_->StartPushRtsp(push_handle, 0);
    
    	// // test push rtsp--
    
    	is_pushing_ = true;
    
    	return true;
    }

    6. 传递转推RTMP数据:

    void nt_stream_relay_wrapper::OnVideoDataHandle(NT_HANDLE handle, NT_UINT32 video_codec_id, 
    	NT_BYTE* data, NT_UINT32 size, NT_SP_PullStreamVideoDataInfo* info)
    {
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if ( pull_handle_ != handle )
    		return;
    
    	if (data == NULL)
    		return;
    
    	if (size < 1)
    		return;
    
    	if (info == NULL)
    		return;
    
    	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);
    
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if (push_handle_ == NULL)
    		return;
    
    	push_api_->PostVideoEncodedDataV2(push_handle_, video_codec_id,
    		data, size, info->is_key_frame_, info->timestamp_, info->presentation_timestamp_);
    }
    
    void nt_stream_relay_wrapper::OnAudioDataHandle(NT_HANDLE handle, NT_UINT32 auido_codec_id,
    	NT_BYTE* data, NT_UINT32 size, NT_SP_PullStreamAuidoDataInfo* info)
    {
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if (pull_handle_ != handle)
    		return;
    
    	if (data == NULL)
    		return;
    
    	if (size < 1)
    		return;
    
    	if (info == NULL)
    		return;
    
    	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);
    
    	if (!is_pushing_ && !is_started_rtsp_stream_)
    		return;
    
    	if (push_handle_ == NULL)
    		return;
    
    	push_api_->PostAudioEncodedData(push_handle_, auido_codec_id, data, size,
    		info->is_key_frame_, info->timestamp_, 
    		info->parameter_info_, info->parameter_info_size_);
    }

    7. 关闭实时RTMP转推

    void nt_stream_relay_wrapper::StopPush()
    {
    	if ( !is_pushing_ )
    		return;
    
    	is_pushing_ = false;
    
    	std::unique_lock<std::recursive_mutex> lock(push_handle_mutex_);
    
    	if ( nullptr == push_handle_ )
    		return;
    
    	push_api_->StopPublisher(push_handle_);
    
    	// // test push rtsp ++
    	// push_api_->StopPushRtsp(push_handle_);
    	// // test push rtsp--
    
    
    	if ( !is_started_rtsp_stream_ )
    	{
    		push_api_->Close(push_handle_);
    
    		push_handle_ = nullptr;
    	}
    }

    以上就是RTSP或RTMP流转RTMP推送的流程,感兴趣的开发者,可做设计参考。

    展开全文
  • 一个好的转发模块,首先要低延迟!其次足够稳定、灵活、有状态反馈...2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;3. 录像:...
  • 本文意在帮助大家了解如何在开发模式下进行相关消息推送,主要描述消息需求实现流程与注意事项,具体的开发说明详见微信公众平台技术文档。一、微信公众号消息推送定义在开发模式下,企业消息系统发送图文推送、模板...
  • 一个好的转发模块,首先要低延迟!其次足够稳定、灵活、有状态反馈...2. 转推:通过RTMP直播推送SDK的编码后数据输入接口,把回调上来的数据,传给RTMP直播推送模块,实现RTSP数据流到RTMP服务器的转发;3. 录像:...
  • 写惯了接口都不知道该怎么主动推送数据了。。 Websocket,一种在单个 TCP 连接上进行全双工通讯的协议。也就是说,它允许服务端主动向客户端推送数据。并且只需要一次握手,两者之间就可以创建持久性的连接,并进行...
  • 文章目录后端项目配置配置包WebSocketConfigWebSocketServer控制器接口测试前端项目socket.js网页代码功能测试前端(客户端)推送信息给后端后端推送数据给前端(客户端) 准备2个springboot项目,一个是前端页面的项目,...
  • 其次是对数据的处理,尽管很多第三方服务都提供了很多的api接口,但是总有一些需求是这种第三方服务满足不了的,所以有条件的公司可以自己搭一套消息推送服务。好了废话不多说,先讲一下极光推送这个第三方的服务吧...
  • 好多开发者苦于很难在unity3d下实现RTMP直播推送,本次以大牛直播SDK(Github)的Windows平台RTMP推送模块(以推摄像头为例,如需推屏幕数据,设置相关参数即可)为例,介绍下unity3d的RTMP推送集成。 简单来说,...
  • 好多开发者提到,苦于很难在unity3d下实现RTMP直播推送,本次以大牛直播SDK(Github)的Windows平台RTMP推送模块(以推摄像头为例,如需推屏幕数据,设置相关参数即可)为例,介绍下unity3d的RTMP推送集成。...
  • 推送:平台调用应用服务器的restful接口数据主动发给北向应用(这些restful接口是平台在北向接口文档的消息推送章节提前定好,由应用服务器实现的。)回调地址:应用实现消息推送的restful接口时,可以自定义接口的...
  • iOS客户端如何测试推送

    千次阅读 2018-03-26 16:41:16
    当我们在客户端实现推送跳转需求后,但是服务器还没有做好后台的配置,此时需要测试收到推送消息之后能否按照预期,执行我们的代码。但在这个情况下如何去做呢。这里可以提供给大家两个选择。 1. 封装好处理逻辑的...
  • 关于爬取数据可以参考:Python40行代码实现天气预报和每日鸡汤推送 本文主要讲如何实现发送短信的功能,全部代码只用15行。 首先贴出实现的效果图,后面再分两步详细描述实现过程,第一步免费注册api接口,第二步只...
  • 百度站长工具后台有sitemap实时推送功能,并且提供了api方式,那么我们来看看php如何实现百度sitemap的推送功能但是实时推送sitemap的功能却不一定会开放,具体开放准则我也不是很清楚可能一些原创类的网站会比较容易...
  • 推送:平台调用应用服务器的restful接口数据主动发给北向应用(这些restful接口是平台在北向接口文档的消息推送章节提前定好,由应用服务器实现的。) 回调地址:应用实现消息推送的restful接口时,可以自定义接口...
  • 一般前端请求服务器接口都是使用主动请求的模式,有ajax等。但分不同使用场景有不一样的需求,比如要求实时性比较高的场景使用ajax则不好实现,且实现了开销也大。所以有了websocket这种长链接的形式保持在前端与...
  • 推送:平台调用应用服务器的restful接口数据主动发给北向应用(这些restful接口是平台在北向接口文档的消息推送章节提前定好,由应用服务器实现的。)回调地址:应用实现消息推送的restful接口时,可以自定义接口的...
  • 最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在Android板件拉取本地的RTSP或RTMP流,然后对外推送RTMP出去,亦或内部启个轻量级RTSP服务,提供个对外对接的媒介URL,简单来说,设计架构图如下:...
  • 最近不少开发者找到我们,他们在做智能家居等传统行业时,希望实现在Android板件拉取本地的RTSP或RTMP流,然后对外推送RTMP出去,亦或内部启个轻量级RTSP服务,提供个对外对接的媒介URL,简单来说,设计架构图如下:...
  • 第一部分:基础掌握基本的如何利用signalr实现基本的推送功能模块, 备注:既然工作中使用,总得尽量了解这个signalr的细节,不然使用的心里没底,,通过下面相关文档了解原理,和使用 推送相关核心代码:首先是在...
  • 比如推送数据需要先登录系统等。这里用一个文章发布接口来进行示例。想要通过接口发表文章,首先需要登录,要如何判断这个账号已经登录了呢。登录的账号可能有多个,总不能每次都验证不同的数据吧。这时候就可以通过...
  • 项目要实现类似功能,目前...一些其他的想法:当数据表发生变动,即推送动态给APP用户,这应该是比较好的设计方案,运用MySQL触发器,但是我没找到数据表变化后立即执行代码的方式,请各位有这方面经验的朋友交流交流。
  • 一、前言 作为一名爬虫工程师,在工作中常常会遇到爬取实时数据的需求,比如体育赛事实时...WebSocket 采用的是 推 模式,由服务端主动将数据推送给客户端,这种方式是真正的实时更新。 二、什么是 WebSocket WebSocke
  • 系统接口,实际上就是实现系统间数据传输的常见功能,是企业信息系统间非常常见的功能。 本篇简单和大家聊聊接口的功能开发说明书,对业务顾问来说,应该如何合理表述,从而能保证需求被有效传递。 本篇所分享...
  • 对于微信二维码统计公众号关注粉丝数据实现,微号帮平台提供了渠道二维码生成功能实现,公众号平台提供了接口编程实现功能,均可以实现微信公众号自带参数二维码,不限于粉丝新关注和已关注,只要通过参数二维码扫...
  • 通过EasyScreenLive我们就可以避免接触到稍显复杂的音视频源采集,编码和流媒体推送以及RTSP/RTP/RTCP/RTMP服务流程,只需要调用EasyScreenLive的几个API接口,就能轻松、稳定地把流媒体音视频数据RTMP推送给EasyDSS...

空空如也

空空如也

1 2 3 4 5 6
收藏数 110
精华内容 44
关键字:

如何实现接口推送数据