C++ webrtc开发(非原生开发,linux上使用libdatachannel库)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录
  • 前言
  • 一、libdatachannel库的下载和build
  • 二、开始使用
    • 1.
    • 2.引入库
    • 3.开始使用
  • 总结

前言

使用c++开发webrtc在互联网上留下的资料甚少,经过我一段时间的探索,有大概这几种可以用于c++进行webrtc开发的方法。
1.c++ webrtc native 开发,这个开发方法很麻烦,编译这个库十分麻烦,索性网上还留有部分资料可供参考,但是因为我是想在嵌入式上部署webrtc,所以没有考虑这个方法。
2.kvs webrtc c sdk 库二次开发,利用amazon给出的用于aws的sdk,我们编译生成静态库后可以抛弃掉其中信令服务器等内容,利用里面ice部分媒体传输部分完成自己的功能的开发。优点是这个库的对外api的介绍写的挺清楚,缺点是kvs的github社区里面的问题基本都是使用它的整套服务过程中提出的问题,对于单独提取它的一些库来完成自己功能过程中遇到的问题很少,可能你在自己魔改的过程中遇到奇奇怪怪的问题里面一点线索都找不到。其次,这是一个c库,对外的api写的挺清楚,但是遇到bug后你阅读源码的过程中,大量的c代码会让你很难受。并且该库依赖大量的第三方库,编译过程比webrtc native舒适不少,但还是会遇到各种问题,尤其是你想要交叉编译到嵌入式板子上时,可能这个过程更会让你难受。关于使用这个库开发自己的webrtc的中文资料也挺少,推荐这篇博客
嵌入式中实现webrtc的方式
这条路我是跟着这篇文章做过一阵,但是最后还是失败了,如果有人这个方法做出来可以告诉我
3.libdatachannel库,这个库很轻量级,简单make就可以,虽然看名字这个库似乎只能用于datachannel,但实际他是支持媒体传输的,代码质量很高,缺点是要c++17,但是我的嵌入式板子刚好支持c++17,于是就这样使用下来了,体验很不错,基本上使用库遇到的问题你都能在github的issue中找到解决的方法。

一、libdatachannel库的下载和build

没什么好说的,因为这个库真的很轻量级,我甚至没有交叉编译,我直接在嵌入式板子上编译最终都通过了。
make就完事了,中间可能会遇到openssl库的一个问题,google一下就能解决问题。
build教学

二、开始使用

1.

如果直接使用的话,你在make之后再make install一下,大概就会把相关需要的文件放在系统的某个目录下了,你可以把include和lib自己拿出来放到项目文件夹中,或者你再cmake中指明库在系统哪个地方也行,不懂的可以问chatgpt,适当的提示词可以让gpt很快地解决你的问题。除此之外你还需要自己include一个json库,你喜欢的任意一种c++json库都行。

2.引入库

在这里插入图片描述

就像这样引入这些库吧。最重要的是rtc.hpp,别把它忘了就好

3.开始使用

大家可以照看github中examples的代码自己读,自己改出一版自己的webrtc,接下来我来用我自己的代码来做一个简单的教学,内容在代码中的注释展现

#include "../include/rtc/rtc.hpp"
#include <iostream>
#include "json.hpp"
#include <memory>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
typedef int SOCKET;
static std::string from;
static std::string to;
static std::string sessionid;


using std::string;
using std::shared_ptr;
using std::weak_ptr;
using std::cout ;
using std::endl;
const int BUFFER_SIZE = 2048;
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
shared_ptr<rtc::PeerConnection> pc;
rtc::WebSocket ws;
using nlohmann::json;
int main()
{
    // std::cout << "hehe" << std::endl;
    //Debug的程度,一般设置为Debug就行,Verbose会展示网络的具体细节
	rtc::InitLogger(rtc::LogLevel::Verbose);
	bool flag = false;
	
	//ws开启
	ws.onOpen([&flag]() {
    	std::cout << "WebSocket open" << std::endl;
		flag = true;
	});
	//socket绑定,这一段代码非必须,如果你阅读了github中examples的相关内容,你应该会理解这么做是在干嘛
   	SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
	struct sockaddr_in addr = {};
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	addr.sin_port = htons(6000);
	if (bind(sock, reinterpret_cast<const sockaddr *>(&addr), sizeof(addr)) < 0)
		throw std::runtime_error("Failed to bind UDP socket on 127.0.0.1:6000");
	int rcvBufSize = 212992;
	setsockopt(sock, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char *>(&rcvBufSize),
	           sizeof(rcvBufSize));
	           
	//设置websocket的回调函数,里面相关的json报文解析要按照你自己的信令服务器来进行设置
	//我来说说里面比较重要的东西
	//1.setRemoteDescription(需要你传入sdp和type的string),注意当你没有持有offer时,这时里面会
	//自动调用setLocalDescription然后开始产生你的对offer的answer
	//对应于js中的写法,就相当于setremotedescription后自动调用了CreateAnswer
	//而你自己获得answer后却不会产生自己的sdp
	
	ws.onMessage([](auto data) {
		// data holds either std::string or rtc::binary
		if (!std::holds_alternative<std::string>(data))
			return;
		// cout <<"1111" << endl;
		std::string ndata = std::get<std::string>(data);
		std::cout << "got data" << ndata << std::endl;
		json message = json::parse(ndata);
		std::string type = message["type"].get<std::string>();
		// cout << "2222" <<endl;
		if (type == "offer") {
			std::cout << "get offer " << std::endl;
			std::string sdp = message["data"]["description"]["sdp"].get<std::string>();
			std::cout << "get sdp: 
" << sdp << std::endl;
			pc->setRemoteDescription(rtc::Description(sdp, type));
		} else if(type == "answer"){
			std::cout << "get answer " << std::endl;
			std::string sdp = message["data"]["description"]["sdp"].get<std::string>();
			std::cout << "get sdp: 
" << sdp << std::endl;
			pc->setRemoteDescription(rtc::Description(sdp, type));
		}else if (type == "candidate") {
			auto candidate = message["data"]["candidate"]["candidate"].get<std::string>();
			auto mid       = message["data"]["candidate"]["sdpMid"].get<std::string>();
			// auto mid = message["mid"].get<std::string>();
			std::cout << "get candidate:
" << candidate << std::endl;
			pc->addRemoteCandidate(rtc::Candidate(candidate, mid));
		}
	});
	//

	//ws连接,如果你是主动发送offer方,请务必等ws连接完毕后再进行后续,否则可能会出现ws还未连接但是已经收集完description并尝试发送了
   	ws.open("your own websocket url");
	// int cnt++;
	while(!flag){
		;
	}
	
	//stun服务器和turn服务器的设置,如果你要设置turn,其实直接写一个turn地址就够了,这个turn也会用作stun
	//并且你不用在ip中指明是turn还是stun,当你设置有密码和账号后,就会认为是turn了
	rtc::Configuration config;
	// config.iceServers.emplace_back("stun.l.google.com:19302");
	config.iceServers.emplace_back("url");
	// const rtc::IceServer turnServer("ip", "port", "name", "password");
	// config.iceServers.emplace_back(turnServer);

	//简单的媒体track
	rtc::Description::Video media("video", rtc::Description::Direction::SendOnly);
	media.addH264Codec(96);
	media.addSSRC( rtc::SSRC(45), "video-send" );
	pc = std::make_shared<rtc::PeerConnection>(config);
	auto track = pc->addTrack(media);
	// pc->createdata
	
	//收集本地candidate回调,每收集到一个就会发送一个
	pc->onLocalCandidate([](rtc::Candidate candidate) {
		std::cout << "Local Candidate:"
		          << std::endl;
		std::cout << std::string(candidate) << std::endl << std::endl;
		// std::cout << std::string(candidate.mid()) << std::endl;
		json message;
		message["type"] = "candidate";
		message["data"]["from"] = "1433";
		message["data"]["to"] = "1453";
		message["data"]["candidate"]["candidate"] = std::string(candidate);
		message["data"]["candidate"]["sdpMid"] = candidate.mid();
		message["data"]["session_id"] = "1453-1433";
		ws.send(message.dump());
	});
	
	//ice状态
	pc->onStateChange([](rtc::PeerConnection::State state) {
		std::cout << "[State: " << state << "]" << std::endl;
	});
	
	//本地sdp
	pc->onLocalDescription([](rtc::Description sdp){
		auto description = pc->localDescription();
                json message;
                message["type"] = description->typeString();
                message["data"]["to"] = "1453";
                message["data"]["from"] = "1433";
                message["data"]["description"]["sdp"] = string(description.value());
                message["data"]["description"]["type"] = description->typeString();
                message["data"]["session_id"] = "1453-1433";
                message["data"]["media"] = "video";
                std::cout << "send answer json :
" << message.dump() << std::endl;
                ws.send(message.dump());
	});
//没什么大用,告知candidate收集状态
	pc->onGatheringStateChange([](rtc::PeerConnection::GatheringState state) {
        cout << "Gathering State: " << state << endl;
        if (state == rtc::PeerConnection::GatheringState::Complete) {
            std::cout  << "gather ok" << std::endl;
        }
    });
// pc->setLocalDescription();
	// pc->onTrack)
	// const std::string label = "test";
	// std::cout << "Creating DataChannel with label "" << label << """ << std::endl;
	// auto dc = pc->createDataChannel(label);

	// 	dc->onOpen([wdc = make_weak_ptr(dc)]() {
	// 		// std::cout << "DataChannel from " << id << " open" << std::endl;
	// 		if (auto dc = wdc.lock())
	// 			dc->send("Hello from wl");
	// 	});

	// 	dc->onClosed([]() { std::cout << "DataChannel from " << " closed" << std::endl; });

	// 	dc->onMessage([wdc = make_weak_ptr(dc)](auto data) {
	// 		// data holds either std::string or rtc::binary
	// 		if (std::holds_alternative<std::string>(data))
	// 			std::cout << "Message from " << "peer" << " received: " << std::get<std::string>(data)
	// 			          << std::endl;
	// 		else
	// 			std::cout << "Binary message from " << "peer "
	// 			          << " received, size=" << std::get<rtc::binary>(data).size() << std::endl;
	// 	});
	char buffer[2048];
		int len;
		while ((len = recv(sock, buffer, BUFFER_SIZE, 0)) >= 0) {
			if (len < sizeof(rtc::RtpHeader) || !track->isOpen())
				continue;
			std::cout << "send buffer: " << len << std::endl;
			auto rtp = reinterpret_cast<rtc::RtpHeader *>(buffer);
			rtp->setSsrc(rtc::SSRC(45));

			track->send(reinterpret_cast<const std::byte *>(buffer), len);
		}



//	while(1);
	return 0;
}

总结

libdatachannel是一个很好用的webrtc库,经过测试,它的协议栈能和firefox和flutter-webrtc兼容

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows11 专业版 docker desktop 安装指南

家庭中文版需升级专业版&#xff0c;家庭版没有hyper-v。 开始运行optionalfeatures.exe打开windows功能 安装wsl2 步骤 1 - 启用适用于 Linux 的 Windows 子系统步骤 2 - 检查运行 WSL 2 的要求步骤 3 - 启用虚拟机功能步骤 4 - 下载 Linux 内核更新包 步骤 1 - 启用适用于 L…

解锁前端开发速度的秘密武器【Vite】

在前端开发的江湖中&#xff0c;有人偏爱 Webpack 的强大与稳定&#xff0c;有人钟情于 Rollup 的轻量与高效。而 Vite&#xff0c;这个后来居上的工具&#xff0c;却以“极致的快”和“极简的易”赢得了开发者的芳心。众所周知万事都有缘由&#xff0c;接下来我们就来深度剖析…

AI发展与LabVIEW程序员就业

人工智能&#xff08;AI&#xff09;技术的快速发展确实对许多行业带来了变革&#xff0c;包括自动化、数据分析、软件开发等领域。对于LabVIEW程序员来说&#xff0c;AI的崛起确实引发了一个值得关注的问题&#xff1a;AI会不会取代他们的工作&#xff0c;导致大量失业&#x…

决策曲线分析(DCA)中平均净收益用于评价模型算法(R自定义函数)

决策曲线分析&#xff08;DCA&#xff09;中平均净收益用于评价模型算法 DCA分析虽然不强调用来评价模型算法或者变量组合的优劣&#xff0c;但是实际应用过程中感觉DCA曲线的走势和模型的效能具有良好的一致性&#xff0c;其实这种一致性也可以找到内在的联系&#xff0c;比如…

【Linux】:多线程(POSIX 信号量 、基于环形队列的生产消费者模型)

&#x1f4c3;个人主页&#xff1a;island1314 ​​ &#x1f525;个人专栏&#xff1a;Linux—登神长阶 ⛺️ 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f49e; &#x1f49e; &#x1f49e; 目录 1. POSIX 信号量…

人工智能的历史概况和脉络

人工智能( AI ) 的历史始于古代&#xff0c;当时有神话、故事和谣言称&#xff0c;人工生物被工匠大师赋予了智慧或意识。从古代到现在&#xff0c;对逻辑和形式推理的研究直接导致了20 世纪 40 年代可编程数字计算机的发明&#xff0c;这是一种基于抽象数学推理的机器。这种设…

昇思25天学习打卡营第33天|共赴算力时代

文章目录 一、平台简介二、深度学习模型2.1 处理数据集2.2 模型训练2.3 加载模型 三、共赴算力时代 一、平台简介 昇思大模型平台&#xff0c;就像是AI学习者和开发者的超级基地&#xff0c;这里不仅提供丰富的项目、模型和大模型体验&#xff0c;还有一大堆经典数据集任你挑。…

基于32单片机的RS485综合土壤传感器检测土壤PH、氮磷钾的使用(超详细)

1-3为RS485综合土壤传感器的基本内容 4-5为基于STM32F103C8T6单片机使用RS485传感器检测土壤PH、氮磷钾并显示在OLED显示屏的相关配置内容 注意&#xff1a;本篇文件讲解使用的是PH、氮磷钾四合一RS485综合土壤传感器&#xff0c;但里面的讲解内容适配市面上的所有多合一的RS…

Vue入门到精通:运行环境

Vue入门到精通&#xff1a;运行环境 Vue3的运行环境搭建主要有两种方法&#xff1a;一种是直接在页面中引入Vue库&#xff0c;另一种是通过脚手架工具创建Vue项目。 &#xff08;一&#xff09;页面直接引入Vue库 页面直接引入Vue库的方法&#xff0c;是指在HTML网页中通过s…

PHP项目从 php5.3 版本升级到 php8.3 版本时的一些问题和解决方法记录

一个原来的项目&#xff0c;因为业务需要&#xff0c;进行了PHP版本升级&#xff0c;从php5.3直接升级到php8.3。变化挺大的&#xff0c;原程序中有很多不再兼容&#xff0c;在此处进行一下记录。 一、Deprecated: 显式转换问题 报错内容&#xff1a;Deprecated: Implicit con…

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

文章目录 PyQt5 超详细入门级教程前言序篇&#xff1a;1-3部分&#xff1a;PyQt5基础与常用控件第1部分&#xff1a;初识 PyQt5 和安装1.1 什么是 PyQt5&#xff1f;1.2 在 PyCharm 中安装 PyQt51.3 在 PyCharm 中编写第一个 PyQt5 应用程序1.4 代码详细解释1.5 在 PyCharm 中运…

Apache Kylin最简单的解析、了解

官网&#xff1a;Overview | Apache Kylin 一、Apache Kylin是什么&#xff1f; 由中国团队研发具有浓厚的中国韵味&#xff0c;使用神兽麒麟&#xff08;kylin&#xff09;为名 的一个OLAP多维数据分析引擎:&#xff08;据官方给出的数据&#xff09; 亚秒级响应&#xff…

【工具】linux matlab 的使用

问题1 - 复制图表 在使用linux matlab画图后&#xff0c;无法保存figure。 例如在windows下 但是在linux下并没有这个“Copy Figure”的选项。 这是因为 “ The Copy Figure option is not available on Linux systems. Use the programmatic alternative.” 解决方案&…

opencv——(图像梯度处理、图像边缘化检测、图像轮廓查找和绘制、透视变换、举例轮廓的外接边界框)

一、图像梯度处理 1 图像边缘提取 cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) 功能&#xff1a;用于对图像进行卷积操作。卷积是图像处理中的一个基本操作&#xff0c;它通过一个称为卷积核&#xff08;或滤波器&#xff09;的小矩阵在图像上…

Redis - 集合 Set 及代码实战

Set 类型 定义&#xff1a;类似 Java 中的 HashSet 类&#xff0c;key 是 set 的名字&#xff0c;value 是集合中的值特点 无序元素唯一查找速度快支持交集、并集、补集功能 常见命令 命令功能SADD key member …添加元素SREM key member …删除元素SCARD key获取元素个数SI…

androidstudio导入项目至成功运行(保姆级教程)

目录 一、下载资源包 二、创建一个新的项目 三、替换配置文件 四、打开Android Studio 一、下载资源包 将你得到的资源包下载到你能够找到的位置然后解压&#xff0c;方便操作 二、创建一个新的项目 如果已经有新创建的项目就不用新建了&#xff0c;但是需要注意的是&am…

SpringBoot SPI

参考 https://blog.csdn.net/Peelarmy/article/details/106872570 https://javaguide.cn/java/basis/spi.html#%E4%BD%95%E8%B0%93-spi SPI SPI(service provider interface)是JDK提供的服务发现机制。以JDBC为例&#xff0c;JDK提供JDBC接口&#xff0c;在包java.sql.*。MY…

linux部署ansible自动化运维

ansible自动化运维 1&#xff0c;编写ansible的仓库&#xff08;比赛已经安装&#xff0c;无需关注&#xff09; 1、虚拟机右击---设置---添加---CD/DVD驱动器---完成---确定 2、将ansible.iso的光盘连接上&#xff08;右下角呈绿色状态&#xff09; 3、查看光盘挂载信息 df -h…

Java——网络编程(中)—TCP通讯(上)

一 (网络编程常用类) (Java为了跨平台&#xff0c;在网络应用通信时是不允许直接调用操作系统接口的&#xff0c;而是由java.net包来提供网络功能。下面来介绍几个java.net包中的常用的类) 1 InetAddress类的使用 (封装计算机的IP地址和DNS) (这个类没有构造方法——>如…

SQL server学习05-查询数据表中的数据(上)

目录 一&#xff0c;基本格式 1&#xff0c;简单的SQL查询语句 2&#xff0c;关键字TOP 3&#xff0c;关键字DISTINCT 二&#xff0c;模糊查询 1&#xff0c;通配符 三&#xff0c;对结果集排序 1&#xff0c;不含关键字DISTINCT 2&#xff0c;含关键字DISTINCT 3&…