当前位置: 首页 > news >正文

使用eXosip+ffmpeg、ffplay命令行实现sip客户端

文章目录

  • 前言
  • 一、关键实现
    • 1、主要流程
    • 2、解决端口冲突
      • (1)、出现原因
      • (2)、解决方法
    • 3、解析sdp
      • (1)、定义实体
      • (2)、解析视频
      • (3)、解析音频
    • 4、命令行推拉流
      • (1)、视频推流
      • (2)、音频推流
      • (3)、音视频播放
  • 二、sipua接口设计
  • 三、使用示例
  • 四、完整代码
  • 五、效果预览
  • 总结


前言

使用sip做视频通话时,会遇到需要使用ip摄像头作为视频源的情况,查了资料使用pjsip通常也需要改源码。pjsip包含的功能很完整,但有点过于庞大,很多功能并不需要。而且笔者有一个想法,只要有个能处理sip交互的库比如eXosip,音视频这块另外实现,比如先使用ffmpeg和ffplay命令行作为音视频测试,成功后再写代码实现。本文就是测试成功的方案,真正灵活的方式还是要写代码调ffmpeg,本文更多的是提供一种实现思路。


一、关键实现

主要的实现步骤是使用eXosip处理sip、自己解析sdp、流媒体使用ffmpeg、ffplay命令行。

1、主要流程

在这里插入图片描述

2、解决端口冲突

(1)、出现原因

按照上述流程会遇到端口冲突问题,推流和拉流需要使用同一个本地udp端口,由于ffmpeg和ffplay是两个进程同使用相同的端口就会冲突。具体细节如下:
在这里插入图片描述

(2)、解决方法

一般想到的解决办法是使用jrtplib只建立一个rtp会话兼顾发送和接收,且流媒体通过ffmpeg代码实现。本文没有使用此方法,为了坚持使用ffmpeg和ffplay命令行,最好的方法是使用udp代理监听端口对数据进行转发,就可以有效的解决端口冲突问题。

在这里插入图片描述

3、解析sdp

虽然eXosip提供了sdp的获取方法,但是对于具体的信息还是需要自己解析,其实也是比较简单。

(1)、定义实体

//流类型
enum StreamType {STREAMTYPE_VIDEO,STREAMTYPE_AUDIO
};
/// <summary>
/// 流信息
/// </summary>
class StreamInfo {
public://流类型StreamType type;//rtp推流地址,可以用此地址ffmpeg直接推流,也可以用下面参数自定义推流char rtpAdress[128] = { 0 };//流的远端地址char remoteIp[32] = { 0 };//流的远端端口int remotePort = 0;//本地接收/发送端口int localPort = 0;//编码格式char codec[16];//负载类型int payload = 0;union{//采样率,音频int sampleRate = 0;//时间基、视频int timebase;};//声道数int channels = 0;
};

(2)、解析视频

std::vector<StreamInfo> SipUA::_getVideoStreams(sdp_message_t* sdp_msg)
{std::vector<StreamInfo> streams;if (!sdp_msg)return streams;sdp_connection_t* connection = eXosip_get_video_connection(sdp_msg);if (!connection)return streams;std::string ip = connection->c_addr; sdp_media_t* sdp = eXosip_get_video_media(sdp_msg);if (!sdp)return streams;int	port = atoi(sdp->m_port); for (int i = 0; i < sdp->a_attributes.nb_elt; i++){sdp_attribute_t* attr = (sdp_attribute_t*)osip_list_get(&sdp->a_attributes, i);if (attr){std::string audio_filed = attr->a_att_field;if (audio_filed == "rtpmap"){StreamInfo stream;stream.type = StreamType::STREAMTYPE_VIDEO;snprintf(stream.remoteIp, 32, ip.c_str());stream.remotePort = port;std::string value = attr->a_att_value;std::string::size_type pt_idx = value.find_first_of(0x20);if (pt_idx == std::string::npos)continue;stream.payload = atoi(value.substr(0, pt_idx).c_str());std::string::size_type bitrate_idx = value.find_first_of('/');if (bitrate_idx == std::string::npos)continue;stream.timebase = atoi(value.substr(bitrate_idx + 1).c_str());snprintf(stream.codec, 32, value.substr(pt_idx + 1, bitrate_idx - pt_idx - 1).c_str());streams.push_back(stream);}}}return streams;
}

(3)、解析音频

std::vector<StreamInfo> SipUA::_getAudioStreams(sdp_message_t* sdp_msg)
{std::vector<StreamInfo> streams;if (!sdp_msg)return streams;sdp_connection_t* connection = eXosip_get_audio_connection(sdp_msg);if (!connection)return streams;std::string audio_ip = connection->c_addr; //audio_ipsdp_media_t* audio_sdp = eXosip_get_audio_media(sdp_msg);if (!audio_sdp)return streams;int	audio_port = atoi(audio_sdp->m_port); //audio_portfor (int i = 0; i < audio_sdp->a_attributes.nb_elt; i++){sdp_attribute_t* attr = (sdp_attribute_t*)osip_list_get(&audio_sdp->a_attributes, i);if (attr){std::string audio_filed = attr->a_att_field;if (audio_filed == "rtpmap"){StreamInfo stream;stream.type = StreamType::STREAMTYPE_AUDIO;snprintf(stream.remoteIp, 32, audio_ip.c_str());stream.remotePort = audio_port;std::string value = attr->a_att_value;auto strs = StringHelper::split(value, " ");if (strs.size() > 1){stream.payload = atoi(strs[0].c_str());auto format = StringHelper::split(strs[1], "/");if (format.size() > 1){snprintf(stream.codec, 16, format[0].c_str());stream.sampleRate = atoi(format[1].c_str());if (format.size() > 2)stream.channels = atoi(format[2].c_str());}}streams.push_back(stream);}}}return streams;
}

4、命令行推拉流

(1)、视频推流

转发rtsp的h264流为例,rtp推流同时显示预览框。

ffmpeg -i rtmp://127.0.0.1/live/a123 -an -vcodec copy -payload_type 96 -f rtp rtp://127.0.0.1:25026?localrtpport=15514 -window_size 192x108 -f sdl 

(2)、音频推流

以本地文件转码为g.711u为例,每个包大小160bytes。

ffmpeg -re -stream_loop -1 -i D:\test_music.wav -vn -acodec pcm_mulaw -ar 8000 -ac 1 -af "aresample=8000[0];[0]asetnsamples=n=160:p=0" -payload_type 0 -f rtp rtp://127.0.0.1:15026?localrtpport=25514

音频设备采集编码为g.711u为例,每个包大小160bytes。

ffmpeg -f dshow -i audio="音频设备名称" -vn -acodec pcm_mulaw -ar 8000 -ac 1 -af "aresample=8000[0];[0]asetnsamples=n=160:p=0" -payload_type 0 -f rtp rtp://127.0.0.1:15026?localrtpport=25514

注:如果音频与视频为同一个输入源也可以合并为同一条命令。

(3)、音视频播放

将sdp字符串保存本地文件
本地播放的sdp

v=0
o=1002 158 1 IN IP4 127.0.0.1
s=Talk
c=IN IP4 127.0.0.1
t=0 0
m=video 25008 RTP/AVP 96
a=rtpmap:96 H264/90000
a=rtcp:25008
m=audio 25310 RTP/AVP 0
a=rtpmap:0 PCMU/8000
a=rtcp:25310

保存到test.sdp

FILE* f=NULL;
fopen_s(&f, "test.sdp", "wb");
if (f)
{fwrite(call->sdp, 1, strlen(call->sdp), f);fclose(f);
}

命令行播放

ffplay.exe -x 640 -y 360 -protocol_whitelist \"file,udp,rtp\" -i test.sdp

二、sipua接口设计

#pragma once
#include<functional>
#include <string>
#include <vector>
#include "UdpProxy.h"
#include <eXosip2\eXosip.h>
#include"MessageQueue.h"/// 这是一个sipua,内部实现是eXosip2,只提供sip交互,sdp解析、udp代理功能。
/// udp代理分离端口功能:
/// sdp的每个m媒体的推拉流需要使用一个端口,sip服务器要检查来源。
/// 如果此时采样ffmpeg.exe推流、ffplay.exe拉流,两个进程都需要绑定本地同一个端口,就会产生端口冲突。
/// 那就只能个使用jrtplib之类的库,打开一个连接同时发送和接收数据。
/// 但是有一个巧妙的解决办法那就是使用udp代理转发数据,就可以将端口拓展为多个了。/// <summary>
/// sip状态
/// </summary>
enum SipUAState {//收到对方inviteSIPUAEVENT_INVITE,//收到对方回复SIPUAEVENT_ANSWER,//处理流媒体,推流拉流端口有做分离,便于推拉流分开实现。SIPUAEVENT_STREAM,//结束通话,对方挂断SIPUAEVENT_ENDED,
};/// <summary>
/// 流类型
/// </summary>
enum StreamType {STREAMTYPE_VIDEO,STREAMTYPE_AUDIO
};/// <summary>
/// 流信息
/// </summary>
class StreamInfo {
public://流类型StreamType type;//rtp推流地址,可以用此地址ffmpeg直接推流,也可以用下面参数自定义推流char rtpAdress[128] = { 0 };//流的远端地址char remoteIp[32] = { 0 };//流的远端端口int remotePort = 0;//本地接收/发送端口int localPort = 0;//编码格式char codec[16];//负载类型int payload = 0;union{//采样率,音频int sampleRate = 0;//时间基、视频int timebase;};//声道数int channels = 0;
};/// <summary>
/// 通话对象
/// </summary>
class SipCall {
public:int callId = 0;//对方idconst char* userId = nullptr;//播发的sdpconst char* sdp = nullptr;//需要推流的视频信息StreamInfo* video = nullptr;//需要推流的音频信息StreamInfo* audio = nullptr;
};
class SipUA
{
public:/// <summary>/// 状态改变回调,目前版本除媒体流外只有对方的消息会触发状态改变/// </summary>std::function<void(SipUAState state, SipCall* call)> onState = [](auto, auto) {};SipUA(const std::string& serverIp, int serverPort, const std::string& username, const std::string& password);~SipUA();/// <summary>/// 开启客户端,此方法是阻塞的,可以在线程中开启。/// </summary>/// <param name="exitFlag">退出标记,值为true则退出</param>void exec(int* exitFlag);/// <summary>/// 呼叫/// </summary>/// <param name="remoteUserID">对方id</param>/// <param name="hasVideo">有视频否</param>/// <param name="hasAudio">有音频否</param>/// <returns>是否呼叫成功</returns>bool call(const std::string& remoteUserID, bool hasVideo = true, bool hasAudio = true);/// <summary>/// 应答/// </summary>/// <param name="hasVideo">有视频否</param>/// <param name="hasAudio">有音频否</param>void answer(bool hasVideo, bool hasAudio);/// <summary>/// 挂断/// </summary>void hangup();
};

三、使用示例

/// <summary>
/// 本示例启动后会自动拨号,
/// 接收到通话请求会自动接听
/// </summary>
void main() {SipUA ua("192.168.1.10", 5060, "1002", "1234");int exitFlag = false;ua.onState = [&](SipUAState state, SipCall* call) {switch (state){case SIPUAEVENT_INVITE:ua.answer(true, true);break;case SIPUAEVENT_ANSWER:break;case SIPUAEVENT_STREAM://视频推流if (call->video){std::string srcUrl = "test.mp4";std::string format = "-re -stream_loop -1";auto codec = StringHelper::toLower(call->video->codec);std::string params = "";char cmd[512];	if (codec == "h264"){params = "-preset ultrafast -tune zerolatency -level 4.2";}//发送桌面流,同时使用sdl本地预览sprintf_s(cmd, "ffmpeg %s  -i %s  -an -vcodec %s -pix_fmt yuv420p %s  -s 640x360   -b:v 500k  -r 30   -g 10   -payload_type %d   -f rtp %s -window_size 192x108 -f sdl \"%s\"  ",format.c_str(), srcUrl.c_str(), codec.c_str(), params.c_str(), call->video->payload, call->video->rtpAdress, srcUrl.c_str());//运行命令行runCmd(cmd);}//音频推流,如何是同一个输入流也可以和视频合并为一条命令if (call->audio){	std::string srcUrl = "test_music.wav";std::string format = "-re -stream_loop -1";	auto codec = StringHelper::toLower(call->audio->codec);std::string params = "";char cmd[512];if (codec == "opus"){codec = "libopus";}if (codec == "pcmu"){codec = "pcm_mulaw";params = "-ac 1 -af \"aresample=8000[0];[0]asetnsamples=n=160:p=0\"";//af滤镜确保每个包160bytes}//转发本地文件sprintf_s(cmd, "ffmpeg  %s -i %s -vn -acodec %s  -ar %d  %s -payload_type %d -f rtp %s",format.c_str(), srcUrl.c_str(), codec.c_str(), call->audio->sampleRate, params.c_str() , call->audio->payload, call->audio->rtpAdress);printf(cmd);//运行命令行runCmd(cmd);}//播放对方音视频if (call->sdp){FILE* f=NULL;fopen_s(&f, "test.sdp", "wb");if (f){fwrite(call->sdp, 1, strlen(call->sdp), f);fclose(f);std::string cmd = "ffplay.exe -x 640 -y 360 -protocol_whitelist \"file,udp,rtp\" -i test.sdp";//运行命令行runCmd(cmd);}else{printf("fopen_s test.sdp error\n");}}break;case SIPUAEVENT_ENDED://关闭所有子进程closeJobObject();break;default:break;}};//开启测试拨号new std::thread([&]() {Sleep(2000);ua.call("1004", true);});ua.exec(&exitFlag);
}

四、完整代码

eXosip版本为5.1,ffmpeg.exe为4.3,vs2022项目。

https://download.csdn.net/download/u013113678/88180712


五、效果预览

使用freeswitch作为sip服务器
本文程序的运行效果:
推送本地mp4到sip
在这里插入图片描述
使用linphone作为对端运行效果:
在这里插入图片描述


总结

以上就是今天讲述的内容,本文使用的技术很简单,但是实现过程有点曲折。尤其是端口冲突问题,花了不少的时间确定原因,解决办法也是无意中想到的,否则可能很早就用代码去实现整个sip客户端了。本文的实现方式,很好的解耦了sip和流媒体以及rtp,sip可以单独实现、流媒体也可以自由选择、也不需要共用一个rtp会话,有时想要快速搭建一个测试项目就变得容易多了。

相关文章:

使用eXosip+ffmpeg、ffplay命令行实现sip客户端

文章目录 前言一、关键实现1、主要流程2、解决端口冲突&#xff08;1&#xff09;、出现原因&#xff08;2&#xff09;、解决方法 3、解析sdp&#xff08;1&#xff09;、定义实体&#xff08;2&#xff09;、解析视频&#xff08;3&#xff09;、解析音频 4、命令行推拉流&am…...

dotNet 之网络TCP

**硬件支持型号 点击 查看 硬件支持 详情** DTU701 产品详情 DTU702 产品详情 DTU801 产品详情 DTU802 产品详情 DTU902 产品详情 G5501 产品详情 ARM dotnet 编程 dotNet使用TCP&#xff0c;可以使用Socket和TcpClient 、TcpListener类 2种&#xff0c;对于高级用户&…...

python基础面试题汇总(持续更新),冲击offer

目录 1.概念理解题python内置数据结构&#xff0c;哪些是不可变的python新式类和经典类的区别is和有什么区别Python中变量查找顺序python函数的参数是值传递还是引用传递python垃圾回收机制什么是闭包什么是装饰器&#xff0c;开发中用到举例如何实现只读属性Python中类方法、实…...

Java课题笔记~ AOP编程术语(掌握)

&#xff08;1&#xff09; 切面&#xff08;Aspect&#xff09; 切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知&#xff08;Advice&#xff09;。实际就是对主业务逻辑的一种增强。 &#xff08;2&#xff09; 连接点&#xff08;Jo…...

暑假刷题第23天--8/6

3748. 递增子串 - AcWing题库 #include<iostream> #include<string> const int N200005; int a[N]; using namespace std; int main(){int t;cin>>t;for(int q1;q<t;q){int n;cin>>n;string s;cin>>s;int cnt1;a[1]1;for(int i2;i<n;i){i…...

ArcGIS API for JavaScript 4.x 教程(一) 显示一张地图

了解如何创建和显示带有基本地图图层的地图。 地图包含地理数据层。地图包含一个基本地图层&#xff0c;以及一个或多个数据层&#xff08;可选&#xff09;。可以使用地图视图显示地图的特定区域&#xff0c;并设置位置和缩放级别。 本教程将向您展示如何使用地形底图层创建和…...

Python-OpenCV中的图像处理

Python-OpenCV中的图像处理 颜色空间转换物体跟踪获取HSV的值几何变换图像缩放图像平移图像旋转仿射变换透视变换 图像阈值单阈值自适应阈值Otsus二值化 颜色空间转换 在 OpenCV 中有超过 150 中进行颜色空间转换的方法。但是你以后就会 发现我们经常用到的也就两种&#xff1…...

分清性能测试,负载测试,压力测试这三个的区别

做测试一年多来&#xff0c;虽然平时的工作都能很好的完成&#xff0c;但最近突然发现自己在关于测试的整体知识体系上面的了解很是欠缺&#xff0c;所以&#xff0c;在工作之余也做了一些测试方面的知识的补充。不足之处&#xff0c;还请大家多多交流&#xff0c;互相学习。 …...

前端架构师岗位的工作职责(合集)

前端架构师岗位的工作职责1 职责&#xff1a; 1.制定前端的标准和规范&#xff0c;并推广和应用&#xff0c;提高团队的开发效率; 2.前端架构的框架或核心模块的设计与实现; 3.在前端架构、设计与开发上对团队进行足够的指导; 4.在日常的系统设计与优化上与服务端团队紧密合…...

使用 Amazon ECS Anywhere 在边缘部署 Amazon IoT Greengrass

1.概述 亚马逊云科技提供了完备的IoT服务能力&#xff0c;涵盖设备服务、连接和控制服务以及云端分析服务&#xff0c;是快速构建安全可靠、可扩展的 IoT 平台的常见选择。Amazon IoT Greengrass 边缘运行时和云服务&#xff0c;可帮助您在设备上构建、部署和管理 IoT 应用。A…...

pytorch Stream 多流处理

CUD Stream https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-language-extensions 中指出在kenel的调用函数中最后一个可选参数表示该核函数处在哪个流之中。 - 参数Dg用于定义整个grid的维度和尺寸&#xff0c;即一个grid有多少个block。为dim3类型。…...

微信小程序选项卡切换(滑动切换,点击切换)

效果如下&#xff1a;可点击切换&#xff0c;滑动切换 代码如下 这个可以在项目用 index.wxml <view classtopTabSwiper><view classtab {{currentData 0 ? "tabBorer" : ""}} data-current "0" bindtapcheckCurrent>选项一&…...

安路FPGA的赋值报错——移位处理,加括号

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时&#xff0c;发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…...

GO学习之 接口(Interface)

GO系列 1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 文章目录 GO系列前言一、什么是…...

ansible常见模块的运用

ansible常见模块的运用 一&#xff1a;Ansible简介二&#xff1a;ansible 环境安装部署管理端安装 ansibleansible 目录结构配置主机清单配置密钥对验证 三&#xff1a;ansible 命令行模块1&#xff0e;command 模块在远程主机执行命令&#xff0c;不支持管道&#xff0c;重定向…...

合宙Air724UG LuatOS-Air script lib API--patch

patch Table of Contents patch patch.safeJsonDecode(s) (local函数 无法被外部调用) patch 模块功能&#xff1a;Lua补丁 patch.safeJsonDecode(s) (local函数 无法被外部调用) 封装自定义的json.decode接口 参数 名称 传入值类型 释义 s string json格式的字符串 返回值 t…...

pytorch求导

pytorch求导的初步认识 requires_grad tensor(data, dtypeNone, deviceNone, requires_gradFalse)requires_grad是torch.tensor类的一个属性。如果设置为True&#xff0c;它会告诉PyTorch跟踪对该张量的操作&#xff0c;允许在反向传播期间计算梯度。 x.requires_grad 判…...

Java基础异常详解

Java基础异常详解 文章目录 Java基础异常详解编译时异常&#xff08;Checked Exception&#xff09;&#xff1a;运行时异常&#xff08;Unchecked Exception&#xff09;: Java中的异常是用于处理程序运行时出现的错误或异常情况的一种机制。 异常本身也是一个类。 异常分为…...

vue3+vue-i18n 监听语言的切换

最近在用 vue3 做一个后台管理系统&#xff0c;之前是只考虑中文&#xff0c;现在加了个需求是多语言。 本来也不是太难的需求&#xff0c;但是我用的并不熟悉&#xff0c;并且除了页面展示不同的语言&#xff0c;需求是在切换语言的时候在几个页面中需要做出一些自定义的行为&…...

【考研复习】24王道数据结构课后习题代码|2.3线性表的链式表示

文章目录 总结01 递归删除结点02 删除结点03 反向输出04 删除最小值05 逆置06 链表递增排序07 删除区间值08 找公共结点09 增序输出链表10 拆分链表--尾插11 拆分链表--头插12 删除相同元素13 合并链表14 生成含有公共元素的链表C15 求并集16 判断子序列17 判断循环链表是否对称…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)

引言 在人工智能飞速发展的今天&#xff0c;大语言模型&#xff08;Large Language Models, LLMs&#xff09;已成为技术领域的焦点。从智能写作到代码生成&#xff0c;LLM 的应用场景不断扩展&#xff0c;深刻改变了我们的工作和生活方式。然而&#xff0c;理解这些模型的内部…...