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

news2025/1/15 10:02:46

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

文章目录
  • 前言
  • 一、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兼容

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

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

相关文章

【工具】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.” 解决方案&…

系统思考—本质

“执行力不足&#xff0c;真的是问题的根本吗&#xff1f;” 最近我和不少企业创办人交流时&#xff0c;大家普遍提到“执行力”的问题&#xff0c;但我发现&#xff0c;背后其实隐藏着更深层次的原因。当我用系统思考的视角深入拆解时&#xff0c;执行力不足&#xff0c;未必…

模方如何置平处理模型表面?

(1) 点击“多边形绘制“后在编辑模型视图中通过绘制多边形对 OBJ 进行编辑&#xff0c;将立面进行置平处理 选择需要表面置平的范围 (2) 点击“置平“即可看到效果

AAAI 2025 camera ready提交注意事项

您必须在截止日期前填写、签署并返回 AAAI 版权表&#xff08;除非 AAAI Press 指示使用 AAAI 分发许可证&#xff09;。 您必须根据作者的格式说明阅读并格式化您的论文和 PDF。 您必须使用我们的电子提交表格准时提交您的电子文件和摘要。 您必须向 AAAI Press 支付任何所需的…

QT核心功能概览

QT的学习&#xff08;一&#xff09;如何查阅QT文档&#xff1f;&#xff08;以QPushButton为例&#xff09;_qt的组件在qt文档那里面看-CSDN博客 QPushButton Class | Qt Widgets 5.15.18 一、前言 QT虽说功能很庞大&#xff0c;但是常用的控件也不是很多&#xff0c;也就只…

opencv——图片矫正

图像矫正 图像矫正的原理是透视变换&#xff0c;下面来介绍一下透视变换的概念。 听名字有点熟&#xff0c;我们在图像旋转里接触过仿射变换&#xff0c;知道仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程&#xff0c;转换过程坐标点的相对位置和属性不发生变换&a…

动态流程图制作方法

动态流程图制作方法 1.方法1 有各种 echars模板 可以自己改代码 https://www.isqqw.com/viewer?id42201echars 在线生成 https://codevtool.com/echarts2. 方法2电脑软件&#xff0c;画图的。 《亿图图示》

H.323音视频协议

概述 H.323是国际电信联盟&#xff08;ITU&#xff09;的一个标准协议栈&#xff0c;该协议栈是一个有机的整体&#xff0c;根据功能可以将其分为四类协议&#xff0c;也就是说该协议从系统的总体框架&#xff08;H.323&#xff09;、视频编解码&#xff08;H.263&#xff09;、…

java八股-索引下推(图解对比)

参考链接 https://xiaolincoding.com/mysql/base/how_select.html#%E6%89%A7%E8%A1%8C%E5%99%A8 https://javaguide.cn/database/mysql/mysql-index.html#%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8 如何理解索引下推这个概念&#xff0c;其实就是index把Server层的工作&#xff0…

在 Ubuntu 中 make 是否是系统自带的?怎么样查看Linux系统中是否有make?

make 命令 并不是所有 Ubuntu 系统都默认安装的&#xff0c;但它通常是开发工具链的一部分&#xff0c;许多开发者会在安装系统后配置它。make 是一个非常重要的构建工具&#xff0c;用于自动化编译和构建过程&#xff0c;特别是在编译软件或内核时。 make 的来源 make 是一个…

Android系统(android app和系统架构)

文章目录 AndroidAndroid Apps四大组件 Android系统Platform API之下&#xff1a;一个微笑内核adb(Android Debug Bridge) Android包管理机制Android的Intent机制参考 Android LinuxFrameworkJVM 在Linux/Java上做了个二次开发&#xff1f;并不完全是&#xff1a;Android定义…

opencv——识别图片颜色并绘制轮廓

图像边缘检测 本实验要用到Canny算法&#xff0c;Canny边缘检测方法常被誉为边缘检测的最优方法。 首先&#xff0c;Canny算法的输入端应为图像的二值化结果&#xff0c;接收到二值化图像后&#xff0c;需要按照如下步骤进行&#xff1a; 高斯滤波。计算图像的梯度和方向。非极…

【Java笔记】LinkedList 底层结构

一、LinkedList 的全面说明 LinkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复)&#xff0c;包括null线程不安全&#xff0c;没有实现同步 二、LinkedList 的底层操作机制 三、LinkedList的增删改查案例 public class LinkedListCRUD { public stati…

(笔记)解决select下拉框默认选中selected属性不起作用问题

在 vue3 中使用 HTML原生开发&#xff0c;想给 select 下拉框选中 selected 属性不起作用。这是因为 vue3中使用了 Composition API&#xff08;组合式 api&#xff09;&#xff0c;而 Composition API 中的响应式数据是独立的&#xff0c;不会自动更新到 DOM 中。可以使用 v-m…

国科大智能设备安全-APK逆向分析实验

APK逆向分析实验 使用APK常用逆向分析工具&#xff0c;对提供的移动应用程序APK文件进行逆向分析&#xff0c;提交逆向后代码和分析报告。具体任务如下&#xff1a; 任务一&#xff1a;安装并熟悉Apktool、Jadx等APK常用逆向工具的使用方法&#xff0c;对提供的Facebook Updat…

黑皮书-计算机科学导论02

目录 第二部分&#xff1a;计算机硬件 第5章计算机组成 5.1中央处理单元 Ⅰ.算数逻辑单元 Ⅱ.控制单元 Ⅲ.寄存器 5.2主存储器 Ⅰ.随机存取存储器(RAM) Ⅱ.只读存储器(ROM) 高速缓冲存储器(Cache) 5.3输入/输出子系统 Ⅰ.非存储设备 Ⅱ.存储设备&#xff08;辅助存…

解决Linux安装yum时出现apt-get install E: 无法定位软件包问题

ubuntu系统安装mysql之前安装yum&#xff0c;出现报错&#xff1a;E: 无法定位软件包问题 &#xff08;1&#xff09;找到源镜像&#xff0c;备份 cd /etc/apt/ // 切换到/etc/apt/ 目录下 sudo cp sources.list sources.list.old // 先把源文件…

[工具升级问题] 钉钉(linux版)升级带来的小麻烦

本文由Markdown语法编辑器编辑完成。 1. 背景: 今日钉钉又发布了新的升级版本。由于我工作时使用的是Ubuntu 20.04版本&#xff0c;收到的升级推送信息是&#xff0c;可以升级到最新的7.6.25-Release版本。根据钉钉官方给出的历次更新版说明&#xff0c;这个新的版本&#xf…

UnityShaderLab 实现黑白着色器效果

实现思路&#xff1a;取屏幕像素的RGB值&#xff0c;将三个通道的值相加&#xff0c;除以一个大于值使颜色值在0-1内&#xff0c;再乘上一个强度值调节黑白强度。 在URP中实现需要开启Opaque Texture ShaderGraph实现&#xff1a; ShaderLab实现&#xff1a; Shader "Bl…

python学习笔记—8—数字精度控制

1. %m.nd %m.nf score 100 print("socre %5d"% score) salary 6.66 print("salary %4.2f" % salary)