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

GStreamer第一阶段的简单总结

这里写目录标题

  • 前言
  • 个人的总结
  • v4l2src插件的简单使用

前言

因为涉及很多细节的GStreamer官方论坛有详细解链接: GStreamer官网,这里不做说明,以下只是涉及到个人的理解和认知,方便后续的查阅。


个人的总结

1)了解pipeline的使用,简单说就是通道的概念, 因为涉及到数据的复用和拓展, 里面有一个filter(tee插件), 通过tee和后续拓展
的绑定关系复用数据,很像许多Soc编解码芯片中的vpss通道的概念。 数据复用为视频编码通道, 数据jpeg通道以及对数据处理的通道。

2)重点是去了解Pad Templates 中的Availability,这个关系到link的方式,另外关注Element Properties的属性设置。

3)工作使用最多的不是作为播放器,而是将采集数据获取后做后续的图像处理,因此以下是从v4l2src插件中获取到数据的方式:
4)GStreamer复用的拓展性导致其复杂性, 还涉及到glib的学习,因此学习成本还是比较高的, 但现主流的流媒体芯片或边缘化芯片大部分都用到GStreamer,
比如NVIDIA和瑞芯微,特别是瑞芯微直接将GStreamer作为框架,增加自己的硬编解码。
5) GStreamer的许多控件非常适合拓展, 比如推流rtp/rtsp/rtmp/webrtc开发非常快,但走API这套的调试以及对API的理解和学习周期比较长。
这里只花了3天时间学习, 做一个记录。

v4l2src插件的简单使用

/***			@brief  实现从v4l2src中拿到数据,方便后续数据的操作,比如用作重新硬编解码用appsink关键属性: emit-signals默认是关闭emit-signals        : Emit new-preroll and new-sample signalsflags: readable, writableBoolean. Default: falseElement Signals:"eos" :	void user_function (GstElement* object,gpointer user_data);"new-preroll" :	GstFlowReturn user_function (GstElement* object,gpointer user_data);"new-sample" :  GstFlowReturn user_function (GstElement* object,gpointer user_data);Element Actions:"pull-preroll" :  GstSample * user_function (GstElement* object);"pull-sample" :	GstSample * user_function (GstElement* object);"try-pull-preroll" :  GstSample * user_function (GstElement* object,guint64 arg0);"try-pull-sample" :	GstSample * user_function (GstElement* object,guint64 arg0);*/
#include <unistd.h>
#include <gst/gst.h>
#include <pthread.h>#include <time.h>
#include <sys/time.h>#define		TEST_MODULE//#define VIDEO_X_RAWtypedef struct ExtraCtrl_
{int s32brightness;int s32Contrast;int s32Saturation;int s32Hue;int s32Sharpness;}Extra_Ctrl_S;/*
*		@brief 设置ISP图像质量,主要是图像增强类@prams[in] source  - SRC plugin			extraCtrl - 外部的图像喜好@return  null@test:1. 通过inspect查看插件支持的图像属性有哪些
*/
void setCameraQuality(GstElement *source, const Extra_Ctrl_S *extraCtrl)
{g_object_set(source, "do-timestamp", TRUE, NULL);
#if 0GstStructure* extraCtrls = gst_structure_new("logitech_controls","brightness", extraCtrl->s32brightness,"contrast", G_TYPE_INT, extraCtrl->s32Contrast,"saturation", G_TYPE_INT, extraCtrl->s32Saturation,"hue", G_TYPE_INT, extraCtrl->s32Hue,"sharpness", G_TYPE_INT, extraCtrl->s32Sharpness,//  "focus_auto", G_TYPE_BOOLEAN, FALSE,//  "white_balance_temperature_auto", G_TYPE_BOOLEAN, FALSE,//  "white_balance_temperature", G_TYPE_INT, 3500,NULL);
#else /*gst-1.0 无sharpness默认值 brightness 0, hue 0 contrast 36 saturation:54*/int brightness = 1, hue = 1, contrast = 1, saturation = 1;g_object_get(source , "brightness", &brightness, "hue", &hue, "contrast", &contrast, "saturation", &saturation, NULL);g_print("get: brightness:%d, hue:%d, contrast:%d, saturation:%d\n", brightness, hue, contrast, saturation);#if 1int setBrightness = 0;GstStructure* extraCtrls = gst_structure_new("logitech_controls","brightness", G_TYPE_INT, brightness ,"hue", G_TYPE_INT, hue,"contrast", G_TYPE_INT, contrast,"saturation", G_TYPE_INT, saturation,//160,NULL);g_object_set(source, "extra-controls" , extraCtrls, NULL);#endif      
#endif g_object_get(source , "brightness", &brightness, "hue", &hue, "contrast", &contrast, "saturation", &saturation, NULL);g_print("get: brightness:%d, hue:%d, contrast:%d, saturation:%d\n", brightness, hue, contrast, saturation);
}TEST_MODULE void *ispCtlProc(void *arg)
{while(1){GstElement * element = (GstElement*)arg;if(element){static int rec = 0;g_print("rec:%d\n", rec++);Extra_Ctrl_S s;memset(&s, 0, sizeof(s));static int count = 0;s.s32brightness = count++ % 255;s.s32Contrast = 128;s.s32Saturation = 128;s.s32Hue = 128;s.s32Sharpness = 128;setCameraQuality(element, &s);}usleep(1000*100);}return NULL;
}/*@brief  原始数据回调
*/
GstFlowReturn cb_appsink_new_sample(GstElement* appsink, gpointer user_data){// LOG_INFO_MSG ("cb_appsink_new_sample called, user data: %p", user_data);
#if 0SinkPipeline* sp = reinterpret_cast<SinkPipeline*> (user_data);
#endifGstSample* sample = NULL;GstBuffer* buffer = NULL;GstMapInfo map;const GstStructure* info = NULL;GstCaps* caps = NULL;GstFlowReturn ret = GST_FLOW_OK;int sample_width = 0;int sample_height = 0;// equals to gst_app_sink_pull_sample (GST_APP_SINK_CAST (appsink), sample);g_signal_emit_by_name (appsink, "pull-sample", &sample, &ret);if (ret != GST_FLOW_OK) {g_print ("can't pull GstSample.\n");return ret;}if (sample) {buffer = gst_sample_get_buffer (sample);if ( buffer == NULL ) {g_print ("get buffer is null\n");goto exit;}gboolean mapret= gst_buffer_map (buffer, &map, GST_MAP_READ);if(mapret == FALSE){goto exit;}/**/caps = gst_sample_get_caps (sample);if ( caps == NULL ) {g_print ("get caps is null\n");goto exit;}info = gst_caps_get_structure (caps, 0);if ( info == NULL ) {g_print ("get info is null\n");goto exit;}// -------- Read frame and convert to opencv format --------// convert gstreamer data to OpenCV Mat, you could actually// resolve height / width from caps...gst_structure_get_int (info, "width", &sample_width);gst_structure_get_int (info, "height", &sample_height);g_print("format:%s, width:%d, height:%d\n",  gst_structure_get_string(info, "format"), sample_width, sample_height);if(NULL == map.data){g_print("appsink buffer data empty\n");goto exit;}	static struct timeval s, e;static int flag = 1;if(flag){gettimeofday(&s, 0); e = s;flag = 0;}gettimeofday(&e, 0);static int frameNo = 0;frameNo++;unsigned int timespan = (e.tv_sec - s.tv_sec )*1000*1000 + e.tv_usec - s.tv_usec;if(timespan >= 1000000){g_print("frame siz:%d, max_siz:%d, %02d FPS\n", map.size, map.maxsize, frameNo);frameNo = 0;gettimeofday(&s, 0);}//FILE *fp = 
#if 0// customized user action{// init a cv::Mat with gst buffer address: deep copy// sometime you may got a empty bufferif (map.data == NULL) {LOG_ERROR_MSG("appsink buffer data empty\n");return GST_FLOW_OK;}cv::Mat img (sample_height, sample_width, CV_8UC3,(unsigned char*)map.data, cv::Mat::AUTO_STEP);img = img.clone();// redirection outside operation: for decoupling useif (sp->m_putDataFunc) {sp->m_putDataFunc(std::make_shared<cv::Mat> (img),sp->m_putDataArgs);} else {goto exit;}}#endifexit:if (buffer) {gst_buffer_unmap (buffer, &map);}if (sample) {gst_sample_unref (sample);}return GST_FLOW_OK;}}
/*@brief 创造线程设置图像数据, TEST 模块
*/
TEST_MODULE int ispInit(GstElement *source)
{int ret = 0;pthread_t ispControlThread;pthread_create(&ispControlThread, 0, ispCtlProc, (void *)source);
}int main(int argc, char *argv[]) 
{GstElement *pipeline, *source;GstElement  *converter,*appsink;											GstBus *bus;GstMessage *msg;/* Initialize GStreamer */gst_init (&argc, &argv);/* Create the elements *//*src(always)*/source = gst_element_factory_make ("v4l2src", "v4l2src");converter= 	gst_element_factory_make("videoconvert","converter01");//sinkappsink = gst_element_factory_make("appsink", "appsink");/* Create the empty pipeline */pipeline = gst_pipeline_new ("test-pipeline");if ( !pipeline || !source || !appsink ) {g_printerr ("Not all elements could be created.\n");return -1;}//	set appsink 
g_object_set(appsink, "emit-signals", TRUE, NULL);g_signal_connect (appsink, "new-sample", G_CALLBACK (cb_appsink_new_sample), NULL);/* Link all elements that can be automatically linked because they have "Always" pads */gst_bin_add_many (GST_BIN (pipeline), source, converter, appsink, NULL);//  TEST_MODULE ispInit(source); /*@note:1. 需要在gst_bin_add_many之后调用先需要知道摄像头支持的Pixel Format,通过命令可查,v4l2-ctl --list-formats-ext --device /dev/video0可以查看支持的格式只有YUYV和MJPG,另外可以查看到具体的分辨率和帧率
*/
#if defined(VIDEO_X_RAW)GstCaps *caps = gst_caps_new_simple ("video/x-raw", //  "format", G_TYPE_STRING, "YV12", //<<< IF THIS IS SET TO ARGB (THE FORMAT I WANT IT FAILS ON LINKING) // 	"format", G_TYPE_STRING, "NV21","framerate", GST_TYPE_FRACTION, 25, 1, // "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, "width", G_TYPE_INT, 320, "height", G_TYPE_INT, 240, NULL); 
#elseGstCaps *caps = gst_caps_new_simple ("video/mpeg", "mpegversion", G_TYPE_INT, 2,"systemstream", G_TYPE_BOOLEAN, TRUE,NULL); #endif gst_element_link_filtered(source, converter, caps);gst_caps_unref(caps);/*这里做了转换,需要逐一连接*/gst_element_link(source, converter);gst_element_link(converter, appsink);/* Start playing the pipeline */gst_element_set_state (pipeline, GST_STATE_PLAYING);/* Wait until error or EOS */bus = gst_element_get_bus (pipeline);msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);if(msg != NULL)
{GError *err;gchar *debug_info;switch (GST_MESSAGE_TYPE (msg)) {case GST_MESSAGE_ERROR:gst_message_parse_error (msg, &err, &debug_info);g_printerr ("Error received from element %s: %s\n",GST_OBJECT_NAME (msg->src), err->message);g_printerr ("Debugging information: %s\n",debug_info ? debug_info : "none");g_clear_error (&err);g_free (debug_info);break;case GST_MESSAGE_EOS:g_print ("End-Of-Stream reached.\n");break;default:/* We should not reach here because we only asked for ERRORs and EOS */g_printerr ("Unexpected message received.\n");break;}gst_message_unref (msg);
}else
{g_printerr("error.");
}/* Release the request pads from the Tee, and unref them *//* Free resources */if (msg != NULL)gst_message_unref (msg);gst_object_unref (bus);gst_element_set_state (pipeline, GST_STATE_NULL);gst_object_unref (pipeline);return 0;
}

编译:

cmake_minimum_required(VERSION 3.0)
project(test_project)find_package(PkgConfig REQUIRED)
pkg_search_module(GST1 REQUIRED gstreamer-1.0)#查找到gstreamer-1.0 并用GST1代表,GST1自己定义add_executable(a.out v4l2src_appsink.c)target_include_directories(a.out PRIVATE ${GST1_INCLUDE_DIRS})
target_link_libraries(a.out ${GST1_LIBRARIES} -lpthread)message("GST1_LIBRARIES
${GST1_LIBRARIES}
")

运行结果:

format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
frame siz:4147200, max_siz:4147200, 07 FPS
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080
format:YUY2, width:1920, height:1080

相关文章:

GStreamer第一阶段的简单总结

这里写目录标题 前言个人的总结v4l2src插件的简单使用 前言 因为涉及很多细节的GStreamer官方论坛有详细解链接: GStreamer官网&#xff0c;这里不做说明&#xff0c;以下只是涉及到个人的理解和认知&#xff0c;方便后续的查阅。 个人的总结 1)了解pipeline的使用&#xff0…...

【网络进阶】服务器模型Reactor与Proactor

文章目录 1. Reactor模型2. Proactor模型3. 同步IO模拟Proactor模型 在高并发编程和网络连接的消息处理中&#xff0c;通常可分为两个阶段&#xff1a;等待消息就绪和消息处理。当使用默认的阻塞套接字时&#xff08;例如每个线程专门处理一个连接&#xff09;&#xff0c;这两…...

使用div替代<frameset><frame>的问题以及解决办法

首先是原版三层框架的html&#xff1a; <html> <head> <title>THPWP</title> </head> <!-- 切记frameset不能写在body里面&#xff0c;以下代表首页由三层模块组成&#xff0c;其中第一层我是用来放菜单高度占比14%&#xff0c;中间的用作主…...

Verilog中的`define与`if的使用

一部分代码可能有时候用&#xff0c;有时候不用&#xff0c;为了避免全部编译占用资源&#xff0c;可以使用条件编译语句。 语法 // Style #1: Only single ifdef ifdef <FLAG>// Statements endif// Style #2: ifdef with else part ifdef <FLAG>// Statements …...

沃尔玛、亚马逊影响listing的转化率4大因素,测评补单自养号解析

1、listing的相关性&#xff1a;前期我们在找词&#xff0c;收集词的时候&#xff0c;我们通过插件来协助我们去筛选词。我们把流量高&#xff0c;中&#xff0c;低的关键词都一一收集&#xff0c;然后我们再进行对收集得来的关键词进行分析&#xff0c;再进行挑词&#xff0c;…...

静态分析和动态分析

在开发早期&#xff0c;发现并修复bug在许多方面都有好处。它可以减少开发时间&#xff0c;降低成本&#xff0c;并且防止数据泄露或其他安全漏洞。特别是对于DevOps&#xff0c;尽早持续地将测试纳入SDLC软件开发生命周期是非常有帮助的。 这就是动态和静态分析测试的用武之地…...

代码随想录_贪心_leetcode 1005 134

leetcode 1005. K 次取反后最大化的数组和 1005. K 次取反后最大化的数组和 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以…...

笔记:对多维torch进行任意维度的多“行”操作

如何取出多维torch指定维度的指定“行” 从二维torch开始新建torch取出某一行取出某一列一次性取出多行取出连续的多行取出不连续的多行 一次取出多列取出连续的多列取出不连续的多列 考虑三维torch取出三维torch的任意两行&#xff08;means 在dim0上操作&#xff09;取出连续…...

【VSCode】1、VSCode 如何连接服务器

文章目录 一、安装 remote-ssh 插件二、直接连接三、配置 SSH 公匙&#xff0c;免密登录 一、安装 remote-ssh 插件 点击插件搜索框&#xff0c;搜 remote-ssh&#xff0c;点击安装 安装完成后就会出现下面的图标&#xff1a; 二、直接连接 点击加号&#xff0c;输入 ssh 连接…...

AI工具:通过智能实现工作和学习效率的革命化

AI工具是指一系列人工智能技术和工具&#xff0c;包括机器学习、深度学习、自然语言处理、计算机视觉等。这些工具可以帮助开发人员和数据科学家通过处理和分析海量数据来自动识别和解决问题&#xff0c;提供智能的决策和预测模型。常见的AI工具包括TensorFlow、PyTorch、Keras…...

static 和构造方法

文章目录 static构造方法内存中数据的存储方式示例 static 具体对象的属性&#xff0c;称之为对象属性&#xff0c;成员属性&#xff0c;实例属性。 具体对象的方法&#xff0c;称之为对象方法&#xff0c;成员方法&#xff0c;实例方法。 静态&#xff1a;static 和具体对…...

【Linux 裸机篇(八)】I.MX6U EPIT 定时器中断、定时器按键消抖

目录 一、EPIT 定时器简介二、定时器按键消抖 一、EPIT 定时器简介 EPIT 的全称是&#xff1a; Enhanced Periodic Interrupt Timer&#xff0c;直译过来就是增强的周期中断定时器&#xff0c;它主要是完成周期性中断定时的。学过 STM32 的话应该知道&#xff0c; STM32 里面的…...

Web安全 XSS靶场搭建(玩转整个XSS环境.)

Web安全 XSS靶场搭建 XSS又叫CSS&#xff08;Cross Site Script&#xff09;跨站脚本攻击&#xff0c;指的是攻击者在Web页面&#xff0c;插入恶意JS代码&#xff0c;当用户浏览该页之时&#xff0c;嵌入其中JS代码就会被执行&#xff0c;从而达到攻击的目的.&#xff08;包含…...

前端开发技术——DOM(上)

一.单选题&#xff08;共4题,44.4分&#xff09; 1 下列选项中&#xff0c;关于事件的描述错误的是&#xff08;&#xff09; A、 事件指的是可以被JavaScript侦测到的行为 B、 事件驱动程序指的是事件触发后要执行的代码 C、 事件源是指触发的事件 D、 事件的种类称为事件…...

银河麒麟v10服务器版安装OpenDDS

1. OpenDDS简介 OpenDDS是OMG数据分发服务(DDS)的一种开源实现&#xff0c;它遵循实时系统v1.2的DDS规范(OMG Document formal/07-01-01)和实时公布/订阅互操作性通信协议v2.1的DDS-RTPS规范(OMG Document formal/2010-11-01)。OpenDDS由OCI公司设计和维护&#xff0c;可从http…...

curl方式调用电商API接口示例 详细介绍

cURL是一个利用URL语法在命令行下工作的文件传输工具&#xff0c;1997年首次发行。它支持文件上传和下载&#xff0c;所以是综合传输工具&#xff0c;但按传统&#xff0c;习惯称cURL为下载工具。cURL还包含了用于程序开发的libcurl。 cURL支持的通信协议有FTP、FTPS、HTTP、H…...

Duboo介绍与入门

文章目录 1、Dubbo的前世今生2、Dubbo的快速入门2.1、Dubbo的基本架构2.2、Nacos2.3、管理后台2.4、入门案例2.4.1、服务提供者搭建环境代码实现配置文件 2.4.2、服务消费者搭建环境代码实现配置文件 最后说一句 1、Dubbo的前世今生 2011年10月27日&#xff0c;阿里巴巴开源了…...

人工智能中(Pytorch)框架下模型训练效果的提升方法

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能中(Pytorch)框架下模型训练效果的提升方法。随着深度学习技术的快速发展&#xff0c;越来越多的应用场景需要建立复杂的、高精度的深度学习模型。为了实现这些目标&#xff0c;必须采用一系列复杂的技术来提…...

树莓派CSI摄像头使用python调用opencv库函数进行运动检测识别

目录 一、完成摄像头的调用 二、利用python调用opencv库函数对图像进行处理 2.1 图像处理大体流程 2.2 opencv调用函数的参数以及含义 2.2.1 ret, img cap.read() 读取帧图像 2.2.2 cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 灰度图像 2.2.3 gray_diff_img cv2.absdiff(g…...

Parameters(in)、Parameters(out) and Parameters(inout)

0前言 参数类型&#xff08;Parameters&#xff09;指的是函数参数在调用时所具有的性质&#xff0c;从而对函数的调用方式产生影响。在 C 语言中&#xff0c;存在三种不同类型的函数参数&#xff1a;Parameters(in)、Parameters(out) 和 Parameters(inout) 1定义 Parameter…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...