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

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


前言

使用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: \n" << 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: \n" << 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:\n" << 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 :\n" << 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兼容

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值