跨平台低延迟RTSP转RTMP推送技术方案探讨

news2025/2/24 21:16:15

实现RTSP摄像头数据转RTMP推送到服务器,可以用第三方库或者工具实现,总体设计架构如下:

 

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

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服务器。

下面分别介绍下两种技术方案:

FFmpeg技术方案

  1. 安装FFmpeg:首先,您需要安装FFmpeg。FFmpeg是一个开源的跨平台视频和音频处理工具,它支持将RTSP流转换为RTMP流。您可以从FFmpeg官方网站下载适用于Windows的二进制安装程序,并按照说明进行安装。
  2. 配置FFmpeg:安装完FFmpeg后,您需要配置其命令行参数,以便将RTSP流转换为RTMP流,并将其推送到目标服务器。您可以使用以下命令行参数:
 ffmpeg -i rtsp://[摄像头地址]/[流媒体地址] -f flv rtmp://[服务器地址]/[直播频道]

其中,​​rtsp://[摄像头地址]/[流媒体地址]​​是摄像头的RTSP流地址,​​rtmp://[服务器地址]/[直播频道]​​是目标服务器的RTMP流地址。您可以根据实际情况修改这些参数。

  1. 运行FFmpeg:配置完FFmpeg后,您可以使用命令行或脚本文件来运行FFmpeg。您可以在命令行中直接运行上述命令,或者将命令写入脚本文件(例如bat文件),然后运行脚本文件。需要注意的是,上述方案中的摄像头地址、流媒体地址、服务器地址和直播频道都需要替换为实际的地址和信息。此外,您还需要确保摄像头的RTSP流可公开访问,并且目标服务器的RTMP流地址已经配置正确。
  2. 集成到应用程序中:如果您需要在应用程序中实现实时视频流推送,您可以将FFmpeg集成到应用程序中。您可以使用FFmpeg的API或命令行接口,通过编程方式调用FFmpeg的功能,并将摄像头的RTSP流转换为RTMP流,并将其推送到目标服务器。

SDK技术方案

以大牛直播SDK的Windows平台RTSP转RTMP推送C++的demo为例:

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;
	}

	if ( NT_ERC_OK != push_api_->StartPublisher(push_handle, NULL) )
	{
		if ( !is_started_rtsp_stream_ )
		{
			push_api_->Close(push_handle);
			SetPushHandle(nullptr);
		}

		return false;
	}

	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_);

	if ( !is_started_rtsp_stream_ )
	{
		push_api_->Close(push_handle_);

		push_handle_ = nullptr;
	}
}

总结

无论您选择哪种方案,需要确保以下几点:

  1. 选择一个稳定可靠的第三方库或服务,以确保转换的质量和可靠性;
  2. 了解和掌握相关的技术和协议,例如RTSP和RTMP,以及如何使用相关的库和工具进行转换和处理;
  3. 考虑性能和资源的问题,特别是在处理大量视频流或高并发的场景下。需要确保系统具有足够的处理能力和带宽,以避免延迟或丢帧等问题。

Windows平台上的RTSP转RTMP推送需要一些技术准备和规划,以及对相关协议和工具的理解和使用经验,做个基础的demo,用FFmpeg就可以,但是如果产品话,需要考虑的点实在太多了。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/743526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用python get post数据 http https

0、目的 目的比较简单&#xff0c;测试&#xff0c;使用python来提交数据是非常简洁的&#xff0c;修改代码也容易&#xff0c;除了做人工智能&#xff0c;本身也是一个非常好的测试端工具 1、简单的post 一个简单的示例程序&#xff0c;将 headers 内容置为’application/j…

C#轻松读写NDEF智能海报

NDEF 全称 NFC data exchange format 即 nfc 数据交换格式&#xff0c;是一种标准化的数据格式&#xff0c;可用于在任何兼容的NFC设备与另一个NFC设备或标签之间交换信息。数据格式由NDEF消息和NDEF记录组成。 NDEF信息可以写到不同类型的NFC芯片中&#xff0c;如Ntag系列芯片…

Python算法笔记(1)-时间复杂度、空间复杂度

Python算法笔记&#xff08;1&#xff09;-时间复杂度 1.时间复杂度 时间复杂度是一个描述算法的运行时间的一个函数&#xff0c;它描述了算法的运行时间和输入数据的规模之间的关系&#xff0c;时间复杂度的表示方法用O表示&#xff0c;时间复杂度也用来考察输入值无限趋近无…

【嵌入式Qt开发入门】Qt如何使用多线程——继承QObject的线程

QObject 在上篇已经说过&#xff0c;继承 QThread 类是创建线程的一种方法&#xff0c;另一种就是继承 QObject 类。继承 QObject 类更加灵活。它通过 QObject::moveToThread()方法&#xff0c;将一个 QObeject 的类转移到一个线程里执行&#xff0c;可以通过下图理解。 通过…

注解和反射02(Java)

反射机制 首先需了解静态语言和动态语言。 动态语言是一类在运行时可以改变其结构的语言&#xff1a;例如新的函数、对象、甚至代码可以被引进&#xff0c;已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言&a…

一文解析Arm64 栈回溯

AArch64栈的结构 Arm64有4种栈&#xff0c;分别是空增栈(Empty Ascendant Stack,EA)、空减栈(Empty Descendant Stack,ED)、满增栈(Full Ascendant Stack,FA)、满减栈(Full Descendant Stack,FD)。常用的是满减栈&#xff0c;Linux内核也使用满减栈。 下图是一个满减栈的示意…

AppSpider Pro 7.4.053 for Windows - Web 应用程序安全测试

AppSpider Pro 7.4.053 for Windows - Web 应用程序安全测试 Rapid7 Dynamic Application Security Testing (DAST) 请访问原文链接&#xff1a;https://sysin.org/blog/appspider/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin…

分板机视觉定位切割软硬件方案

【检测目的】 定位切割 【拍摄效果图一】 【拍摄效果图二】 【拍摄效果图三】 【方案评估】 以目前样品进行实验来看&#xff0c;图像效果明显&#xff0c;可以找到中线位置。 视野&#xff1a;44mm*33mm 视觉精度&#xff1a;44mm/2448pixel0.018mm/pixel。 【硬件配置】…

抖音seo源码.视频剪辑功能开发(一)

一、短视频抖音seo账号矩阵系统 批量剪辑功能的开发一般有以下几种方式 1. 前端实现&#xff1a;通过前端技术&#xff0c;利用vue jquery layui JavaScript&#xff0c;等语言&#xff0c;实现一个可视化的编辑器&#xff0c;用户可以批量上传视频文件&#xff0c;设置剪…

uniapp-日历控件

第一步&#xff1a;打开uniapp的插件市场 网址&#xff1a;日历组件可选择周与月标记打卡支持左右切换 - DCloud 插件市场 第二步&#xff1a;导入相应的项目&#xff0c;会有相应的提示&#xff08;路径&#xff09; 第三步&#xff1a;引入对应的位置-例如我引入的位置 imp…

fastadmin视图渲染

基类app\common\controller\Backend会默认渲染以下几个对象到视图中 //渲染站点配置 $this->assign(site, $site); //渲染配置信息 $this->assign(config, $config); //渲染权限对象 $this->assign(auth, $this->auth); //渲染管理员对象 $this->assign(admin,…

ArcGISPro加载在线底图和影像

经常用ArcGIS都知道,在工作中配合在线地图有点多爽。无论是制图还是数据校核都非常方便。之前已经讲过如何在ArcGIS地图里利用simplegis插件加载多种在线地图,那换成pro咋办嘞 今天我们就来说说如何在ArcGIS Pro里加载在线地图 ArcGISPro本身就自带了两种影像,均是源自谷歌…

《Redis 核心技术与实战》课程学习笔记(八)

String 类型为什么不好用了&#xff1f; String 类型可以保存二进制字节流&#xff0c;只要把数据转成二进制字节数组&#xff0c;就可以保存了。String 类型并不是适用于所有场合的&#xff0c;它有一个明显的短板&#xff0c;就是它保存数据时所消耗的内存空间较多。 为什么…

不平衡电网条件下基于变频器DG操作的多目标优化研究(Matlab代码Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

贪心算法、贪心搜索/采样(greedy search/sampling)、集束搜索(beam search)、随机采样(random sample)

首先需要了解贪心算法&#xff1a; 贪心算法&#xff0c;又名贪婪法&#xff0c;是寻找最优解问题的常用方法&#xff0c;这种方法模式一般将求解过程分成若干个步骤&#xff0c;但每个步骤都应用贪心原则&#xff0c;选取当前状态下最好/最优的选择&#xff08;局部最有利的选…

Tenable Nessus 10.5.3 (Unix, Linux, Windows) - #1 漏洞评估解决方案

Tenable Nessus 10.5.3 (Unix, Linux, Windows) - #1 漏洞评估解决方案 发布 Nessus 试用版自动化安装程序&#xff0c;支持 macOS Ventura、RHEL 9 和 Ubuntu 22.04 请访问原文链接&#xff1a;https://sysin.org/blog/nessus-10/&#xff0c;查看最新版。原创作品&#xff…

开源堡垒机Guacamole二次开发记录之二

这篇主要记录录屏和SFTP的实现。 录屏及视频播放 对于录屏及录屏的播放&#xff0c;因为我们的项目中需要把guacd和java后端分开两台服务器部署&#xff0c;而guacamole的录屏是通过guacd程序录制的。我的要求是在Java后端直接把录好的视频文件通过http前端播放&#xff0c;因…

手机外壳缺陷视觉检测软硬件方案

单独使用一种光源效果图 同轴光会出现亮度不够的情况&#xff1b;回形面光因为光源中间的圆孔会使图像有阴影&#xff0c;造成图像效果不均衡&#xff0c;所以不采用单独光源打光 使用同轴回形面光源效果图 回形光源照亮产品要寻找的边缘&#xff0c;同轴光源起到补光的作用&a…

裁剪opencv库到2Mb

摘要&#xff1a;本文描述了如何对opencv进行裁剪已达到最小化&#xff0c;不限于使用模块编译&#xff0c;去除第三方库依赖&#xff0c;改变编译选项&#xff0c;限制导出符号等。   关键字&#xff1a;opencv、导出符号 opencv库大小优化的文章网络上很少&#xff0c;大部…

【C++ 学习 ⑩】- 详解 string 类(下):string 类的模拟实现和写时拷贝

目录 一、string 类的模拟实现 1.1 - string.h 1.2 - test.cpp 二、string 类的写时拷贝 2.1 - 示例 2.2 - 原理 一、string 类的模拟实现 1.1 - string.h #pragma once#include <assert.h> #include <string.h> #include <iostream>namespace yzz {…