GStreamer 简明教程(十一):插件开发,以一个音频生成(Audio Source)插件为例
系列文章目录
- GStreamer 简明教程(一):环境搭建,运行 Basic Tutorial 1 Hello world!
- GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline
- GStreamer 简明教程(三):动态调整 Pipeline
- GStreamer 简明教程(四):Seek 以及获取文件时长
- GStreamer 简明教程(五):Pad 相关概念介绍,Pad Capabilities/Templates
- GStreamer 简明教程(六):利用 Tee 复制流数据,巧用 Queue 实现多线程
- GStreamer 简明教程(七):实现管道的动态数据流
- GStreamer 简明教程(八):常用工具介绍
- GStreamer 简明教程(九):Seek 与跳帧
- GStreamer 简明教程(十):插件开发,以一个音频特效插件为例
文章目录
- 系列文章目录
- 前言
- 一、准备工作
- 二、Show me the code
- 3.1 线程模型分析
- 消费端模型
- 生产端实现方案
- 3.2 深入 audiotestsrc 的实现逻辑
- 3.2.1 生产者线程的启动时机
- 3.2.2 GStreamer Pad Task 机制详解
- 核心接口与功能
- 设计优势
- 3.2.3 loop 中做了哪些事情
- 3.2.4 格式协商
- 3.2 写一个 AudioSource 插件
- 3.2.1 初始化函数
- 3.2.2 激活函数
- 3.3.3 loop 循环
- 总结
- 参考
前言
GStreamer 中插件分为三种:Source、Filter 和 Sink,在上一章中我们学习了如何写一个 Filter 插件,可以说 Filter 插件是最简单的,因为它只需要关系数据的处理逻辑,而 Source 和 Sink 就更加复杂一些。本章我们来讨论如何写一个 Source 插件。
本章所提及的代码你可以在 my_plugin 找到。
一、准备工作
准备工作与 GStreamer 简明教程(十):插件开发,以一个音频特效插件为例 中提到的类似,不再赘述。
二、Show me the code
接下来详细说明代码中各个细节,其中很多逻辑都是参考 audiotestsrc 来实现的,大家如果想自己对代码进行详细的分线,建议写一个 audiotestsrc 的 demo,进行代码调试。
3.1 线程模型分析
要理解GStreamer pipeline中的数据流动机制,需要首先明确其线程模型。我们以基础音频流水线为例:
audiotestsrc -> autoaudiosink
- 生产者:
audiotestsrc
元素,负责生成音频测试信号 - 消费者:
autoaudiosink
元素,负责将音频输出到系统声卡
消费端模型
autoaudiosink
的实现通常会依赖系统音频服务(如ALSA/PulseAudio)的回调机制:
- 系统音频线程定期通过回调请求数据
- 形成天然的"消费线程"驱动模型
生产端实现方案
生产者有两种典型的实现范式:
方案A:Push模式(主动生产)
audiotestsrc
创建独立的生产者线程- 持续生成数据并推送(push)至下游
- 下游可能需维护数据缓冲区
- 优势:实现直接,适合连续数据流
- 缺点:可能需要维护缓冲区
方案B:Pull模式(按需生产)
audiotestsrc
保持被动状态- 当
autoaudiosink
需要数据时,通过链式调用向上游拉取(pull) - 优势:流量控制精准
- 难点:需要实现复杂的同步机制
由于Push模式更符合"生产者-消费者"的直观理解,且实现复杂度较低,本文选择方案A作为实现基础。Pull模式涉及GStreamer更底层的调度机制,将在后续深入研究后另文探讨。
3.2 深入 audiotestsrc 的实现逻辑
我们先分析官方 audiotestsrc
的关键实现,学习 GStreamer 标准 source 元素的调度机制。
3.2.1 生产者线程的启动时机
当 pipeline 进入播放流程时,audiotestsrc
的生产者线程在 PAUSED 状态下就已经启动了。通过调试分析,其线程启动流程如下:
典型触发路径:
-
状态切换触发
当gst_element_set_state(pipeline, GST_STATE_PAUSED)
被调用时,会触发对所有元素的 pad 激活操作:gst_pad_set_active(pad, TRUE) // 激活所有 pad
-
Pad 激活回调
audiotestsrc
的 src pad 重写了activatemode_function
,此时会调用继承链:→ gst_base_src_activate_mode() // GstBaseSrc 的标准实现
-
任务线程创建
在gst_base_src_activate_mode()
中,最终通过:gst_pad_start_task(pad, gst_base_src_loop, ...)
启动独立线程执行主循环逻辑
-
主循环工作
gst_base_src_loop()
包含完整处理逻辑:- 格式协商(caps negotiation)
- 发送
STREAM_START
事件 - 生成音频数据
- 数据推送(
gst_pad_push()
)
关键结论:
- 线程启动的实际触发点是 PAUSED 状态下的 pad 激活,而非 PLAYING 状态
- 通过重写
activatemode_function
可以自定义启动逻辑 GstBaseSrc
已封装标准线程调度框架,子类只需实现数据生成
3.2.2 GStreamer Pad Task 机制详解
在 GStreamer 框架中,GstPad
不仅负责数据流的连接与协商,还提供了一套完整的异步任务(Task)接口,允许开发者将线程逻辑直接封装在 Pad 层面,而非传统的 Element 中。这一设计显著提升了模块化程度和灵活性。
核心接口与功能
-
任务启动:
gst_pad_start_task()
gboolean gst_pad_start_task(GstPad *pad,GstTaskFunction func,gpointer user_data,GDestroyNotify notify );
- 作用:启动一个专用线程,循环执行指定的
GstTaskFunction
。 - 关键特性:
- 线程会自动进入循环,持续调用目标函数,无需开发者手动实现循环逻辑。
- 典型应用场景:在
GstBaseSrc
的子类中,gst_base_src_loop()
仅需实现单次数据生成逻辑,任务线程会负责循环调度。例如音频源(audiosource)可通过此机制持续生成音频帧。
- 作用:启动一个专用线程,循环执行指定的
-
任务暂停:
gst_pad_pause_task()
- 行为:临时挂起任务线程的执行,但保留任务状态(如内部变量)。
- 用途:实现动态流控,如响应管道的暂停状态或资源限制。
-
任务终止:
gst_pad_stop_task()
- 行为:完全停止任务线程并释放相关资源。
- 注意:与暂停不同,停止后需重新调用
start_task
才能恢复执行。
设计优势
- 逻辑解耦:将线程管理与业务逻辑分离,Element 只需关注数据处理,Pad Task 处理线程调度。
- 性能优化:避免在 Element 层频繁创建/销毁线程,任务线程可复用。
- 标准化的流控:通过统一的任务接口实现暂停/恢复,简化状态管理。
3.2.3 loop 中做了哪些事情
在 GStreamer 中,audiotestsrc
这类源元素(source element)通过 gst_pad_start_task
启动一个任务循环(gst_base_src_loop
),其中有两件事情非常重要
-
格式协商(negotiate):
和下游商量用什么格式传递数据(比如采样率、位深等)。这一步确保数据能被正确处理。 -
生成数据并推送(push):
按协商好的格式生成音频数据,然后推给下游。
接下来我们重点讲 格式协商,当格式确定后如何生成数据和推给下游就会变得简单很多
3.2.4 格式协商
格式协商的前提是两个元素已经 link 成功。两个元素能够相互连接的前提是它们 pad 的 Capability 是有交集的,比如 src pad 支持的音频采样率是 [1, 96000],那么如果下游支持的采样率在这个范围内,他们就能 link 成功,否则在元素 link 阶段就会失败
auto ok = gst_element_link_many(ele0, ele1, NULL);
if(!ok){printf("link failed");
}
在 link 阶段仅仅是确认了元素之间支持的数据格式是包含一个子集的,那么接下来在运行阶段,我们要从这个子集中,确认唯一的格式,这样才能确定数据是以什么形式进行流动,例如确定音频采样率是 44100,声道数 2,32位浮点数。也就是说,协商的过程就是找到一个这样的固定格式,audiotestsrc 根据这个固定格式来生成音频数据。为了说明这一点,我这边举一个简单的例子。
有三个元素 Source、Filter 和 Sink,它们顺序相互连接,支持的采样率分别是:
- Source : {16000, 32000}
- Filter: [1, Max]
- Sink: {32000, 44100, 48000}
+--------+ +--------+ +--------+
| Source |------>| Filter |------>| Sink |
+--------+ +--------+ +--------+
{16000, 32000} [1, Max] {16000, 32000, 44100, 48000}
首先,它们的采样率有一个公共的子集,即 {16000, 32000}
,因此它们在 link 阶段是成功的;接着,格式协商由 Source 发起,它用自己的格式作为格式过滤器(filter),获取 peer 端的所支持的格式,流程大致是:
- Sink 支持采样率
{16000, 32000, 44100, 48000}
与{16000, 32000}
取交集,得到{16000, 32000}
记作 A - Filter 支持采样率
[1, Mac]
与 A 取交集,得到{16000, 32000}
记作 B - Source 支持采样率
{16000, 32000}
与 B 取交集,得到{16000, 32000}
记作 peercaps
这时候 peercaps 仍然是一个范围,不是一个固定的值,最终由 source 来决定使用哪个固定值,固定下来后,再将它作为 source 的 caps,并通过事件通知给其他元素,其他元素会收到 GST_QUERY_ACCEPT_CAPS 事件,在这个事件中获取 caps 数据做相应的处理。
// 获取 source 的 caps
thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (src), NULL);
// 以 thiscaps 作为 filter,获取 peercaps
peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), thiscaps);
// basesrc 来决定最终使用哪些固定值
caps = gst_base_src_fixate (basesrc, caps);
// 固定 caps,并发送 GST_QUERY_ACCEPT_CAPS 事件
result = gst_base_src_set_caps (basesrc, caps);
3.2 写一个 AudioSource 插件
了解了上面的知识后,我们来开始写一个自己的音频生成插件,为了让代码简单,我们做了这些简化:
- 只支持单声道、F32LE 、interleave 格式的数据
- 只支持 push 模式
详细的代码实现参考 my_plugin,使用 demo 参考 gstmyaudiotestsrc_example
3.2.1 初始化函数
在类初始化函数如下:
static void gst_my_audio_test_src_class_init(GstMyAudioTestSrcClass *klass) {
//...gobject_class->set_property = gst_my_audio_test_src_set_property;gobject_class->get_property = gst_my_audio_test_src_get_property;gobject_class->finalize = gst_my_audio_test_src_finalize;gst_my_audio_test_class_init_install_properties(gobject_class, klass);
// ...
}
我们覆写三个函数
set_property
,用于设置属性值get_property
,用于获取属性值finalize
,用于类的析构,释放一些申请的资源
gst_my_audio_test_class_init_install_properties
函数中注册了属性,具体大家自行看源码,不展开说了。
实例的初始化函数如下:
static void gst_my_audio_test_src_init(GstMyAudioTestSrc *filter) {filter->impl = new GstMyAudioTestSrcImpl();filter->srcpad = gst_pad_new_from_static_template(&gst_audio_test_src_src_template, "src");gst_pad_set_activatemode_function(filter->srcpad,gst_my_audio_test_src_activate_mode);gst_element_add_pad(GST_ELEMENT(filter), filter->srcpad);
}
- 申请
GstMyAudioTestSrcImpl
实例,它用 c++ 来写,可以简化一些代码逻辑 - 创建
srcpad
,并设置srcpad
的激活函数(_activatemode_function
)
3.2.2 激活函数
前面提到,Pad 的 _activatemode_function
是线程启动的入口,我们看看函数逻辑是怎么样的
static gboolean gst_my_audio_test_src_activate_mode(GstPad *pad,GstObject *parent,GstPadMode mode,gboolean active) {auto *src = GST_MYAUDIOTESTSRC(parent);switch (mode) {case GST_PAD_MODE_PULL: {res = gst_my_audio_test_src_pull();break;}case GST_PAD_MODE_PUSH: {res = gst_my_audio_test_src_activate_push(src->srcpad, parent, active);break;}// ...
}
目前 GST_PAD_MODE_PULL
是不支持的,因此看 GST_PAD_MODE_PUSH
即可
static gboolean gst_my_audio_test_src_activate_push(GstPad *srcpad,GstObject *parent,gboolean active) {if (active) {g_print("start loop");gst_pad_start_task(srcpad, (GstTaskFunction)gst_my_audio_test_src_loop,srcpad, NULL);} else {g_print("stop loop");gst_pad_stop_task(srcpad);}return TRUE;
}
_activate_push
函数很简单,启动 _src_loop
或者停止 _src_loop
3.3.3 loop 循环
接下来看最重要的 _src_loop
函数,主要做的两个事情就是格式协商和数据填充
格式协商逻辑如下:
static void gst_my_audio_test_src_loop(GstPad *pad) {GstMyAudioTestSrc *src;src = GST_MYAUDIOTESTSRC(GST_OBJECT_PARENT(pad));GstCaps *caps = NULL;gboolean result = FALSE;if (gst_pad_check_reconfigure(pad)) {g_print("need renegotiate\n");GstCaps *thiscaps = gst_pad_query_caps(src->srcpad, NULL);GST_DEBUG_OBJECT(src, "caps of src: %" GST_PTR_FORMAT, thiscaps);GstCaps *peercaps = gst_pad_peer_query_caps(src->srcpad, thiscaps);GST_DEBUG_OBJECT(src, "caps of peer: %" GST_PTR_FORMAT, peercaps);if (peercaps) {caps = peercaps;gst_caps_unref(thiscaps);} else {caps = thiscaps;}if (caps && !gst_caps_is_empty(caps)) {caps = gst_my_audio_test_src_fixate(src, caps);caps = gst_caps_fixate(caps);GST_DEBUG_OBJECT(src, "fixated to: %" GST_PTR_FORMAT, caps);if (gst_caps_is_fixed(caps)) {/* yay, fixed caps, use those then, it's possible that the subclass* does not accept this caps after all and we have to fail. */result = gst_my_audio_test_src_set_caps(src, caps);if (result) {result = gst_pad_push_event(src->srcpad, gst_event_new_caps(caps));}}}if (!result) {GST_DEBUG_OBJECT(src, "negotiation failed");gst_pad_pause_task(pad);}}// ...
}
逻辑大致是:
- 获取当前 src pad 的 caps,获取 peer pad 的 caps,两者取交集
- 调用
gst_my_audio_test_src_fixate
去设置 audio src 最期望的数据格式,然后调用gst_caps_fixate
固化数据格式(此时所有数据格式已经确定,不再是一个范围值) gst_my_audio_test_src_set_caps
函数从 caps 中获取 AudioInfo 信息,拿到例如采样率、声道数等关键信息gst_pad_push_event
发送事件,通知下游数据格式
数据填充数据如下:
static void gst_my_audio_test_src_loop(GstPad *pad) {//...// generate audio dataGstBuffer *buf = NULL;guint blocksize = impl->samples_per_buffer;guint bufferSize = blocksize * sizeof(float);buf = gst_buffer_new_allocate(NULL, bufferSize, NULL);if (buf == NULL) {GST_DEBUG_OBJECT(src, "alloc buffer failed");}GstMapInfo map;gst_buffer_map(buf, &map, GST_MAP_WRITE);float *data = (float *)map.data;impl->fill(data, blocksize);auto ret = gst_pad_push(src->srcpad, buf);if (ret != GST_FLOW_OK) {GST_DEBUG_OBJECT(src, "push buffer failed");gst_pad_pause_task(pad);}
}
gst_buffer_new_allocate
申请 GstBuffer,用于存放音频数据gst_buffer_map
从 GstBuffer 中拿到可写音频数据的地址impl->fill(data, blocksize);
用于填充音频数据,这部分用的是一个 LFO 生成器,具体逻辑大家可以不用在意。总之就是往一块内存中,写入生成的音频数据,你甚至可以写入随机噪声。。gst_pad_push
将数据 push 到下游
总结
以上,我们就将 AudioSource 如何生成数据的逻辑大致讲了一遍,运行 gstmyaudiotestsrc_example 之后可以听到正弦波的声音。后面我会去研究下如何实现 pull 模式,以及支持更多类型的音频数据和波形。
参考
- my_plugin
- gstmyaudiotestsrc_example
相关文章:
GStreamer 简明教程(十一):插件开发,以一个音频生成(Audio Source)插件为例
系列文章目录 GStreamer 简明教程(一):环境搭建,运行 Basic Tutorial 1 Hello world! GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline GStreamer 简明教程(三…...
【阿里云大模型高级工程师ACP学习笔记】2.1 用大模型构建新人答疑机器人
学习目标 在备考阿里云大模型高级工程师ACP认证时,学习《2.1用大模型构建新人答疑机器人》这部分内容,主要是为了掌握利用大模型技术构建高效答疑机器人的方法,提升在大模型应用开发领域的专业能力。具体目标如下: 掌握大模型API调用:学会通过API调用通义千问大模型,熟悉…...
嵌入式鸿蒙系统环境搭建与配置要求实现01
各位开发者大家好,今天主要给大家分享一下,鸿蒙系统的环境配置实现。 第一:鸿蒙配置基本要求 对电脑的要求,虚拟机配置建议 200GB 硬盘大小,10GB 内存,4*2CPU。 安装必要的依赖文件方法: sudo apt-get update && sudo apt-get install binutils git git-lfs g…...

form表单提交前设置请求头request header及文件下载
需求:想要在form表单submit之前,设置一下请求头。 除了用Ajax发起请求之外,还可以使用FormData来实现,咱不懂就问。 1 问:FormData什么时间出现的?与ajax什么联系? 2 问:FormData使…...

【c++11】c++11新特性(下)(可变参数模板、default和delete、容器新设定、包装器)
🌟🌟作者主页:ephemerals__ 🌟🌟所属专栏:C 目录 前言 五、可变参数模板 1. 概念及简单定义 2. 包扩展 六、 default和delete 七、容器新设定 1. 新容器 2. 新接口 emplace系列接口 八、函数包…...

PyTorch 实现食物图像分类实战:从数据处理到模型训练
一、简介 在计算机视觉领域,图像分类是一项基础且重要的任务,广泛应用于智能安防、医疗诊断、电商推荐等场景。本文将以食物图像分类为例,基于 PyTorch 框架,详细介绍从数据准备、模型构建到训练测试的全流程,帮助读者…...

Qt —— 在Linux下试用QWebEngingView出现的Js错误问题解决(附上四种解决办法)
错误提示:js: A parser-blocking, cross site (i.e. different eTLD+1) script, https:xxxx, is invoked via document.write. The network request for this script MAY be blocked by the browser in this or a future page load due to poor network connectivity. If bloc…...

命名空间(C++)
命名空间主要用于大型项目中。 局部命名在该局部会覆盖全局命名。C语言中唯一一种在局部调用全局相同命名的全局变量的方式:指针在C中可以用作用域运算符来访问全局变量,作用域运算符的前面可以是作用域也可以是类。 命名空间实际上是对全局作用域的再次…...
使用Python脚本在Mac上彻底清除Chrome浏览历史:开发实战与隐私保护指南
题目: 《基于PyCharm与Mac系统的Chrome历史记录清理工具开发实战》 引言 在Mac系统下,Chrome浏览器的历史记录文件通常以SQLite数据库形式存储于用户目录中,仅通过浏览器内置功能清理可能残留索引文件。本文通过一个Python脚本(c…...

LabVIEW圆锥滚子视觉检测系统
基于LabVIEW平台的视觉检测系统提高圆锥滚子内组件的生产质量和效率。通过集成高分辨率摄像头和先进的图像处理算法,系统能够自动识别和分类产品缺陷,从而减少人工检查需求,提高检测的准确性和速度。 项目背景 随着制造业对产品质…...

OpenAI 推出「轻量级」Deep Research,免费用户同享
刚刚,OpenAI 正式上线了面向所有用户的「轻量级」Deep Research 版本,意味着即便没有付费订阅,也能体验这一强大工具的核心功能。 核心差异:o4-mini vs. o3 模型迭代 传统的深度研究功能基于更大规模的 o3 模型。轻量级版本则改以…...

罗伯·派克:Go语言创始者的极客人生
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 罗伯派克:Go语言创始者的极客人生 一、传奇程序员的成长历程 1. 早年经历…...
面试题:Redis 一次性获取大量Key的风险及优化方案
Redis 一次性获取大量Key的风险及优化方案 在Redis中一次性获取大量Key(如使用KEYS命令或大量GET操作)会带来多种风险和性能问题,以下是详细分析和解决方案: 主要风险 1. 阻塞风险 KEYS命令阻塞:KEYS *会扫描整个数…...
中国头部云服务商分析
1. 阿里云 国内云服务的开创者与龙头,占据约三分之一的国内市场份额,其中IaaS占比72%,PaaS与SaaS占比相对较小 全球范围内500万客户,基础设施目前面向全球四大洲,开服运营15个国家、30个公共云地域、89个可用区&#x…...
关于使用git init --bare 裸仓库的使用
1、创建文件夹 对于需要作为仓库的文件夹使用git init --bare进行裸仓库初始化 2、将裸仓库添加为自己的远程仓库 使用的方法和添加远程仓库的方式相同,但是路径需要为绝对路径,同时需要加入file:///协议 git remote add origin file:///d:/Desktop/Lo…...
解释一下计算机中的内存对齐
1. 内存对齐的基本概念 内存对齐是计算机系统优化内存访问效率的一种机制,要求数据在内存中的起始地址必须为某个值的整数倍(通常为数据类型大小的整数倍)。例如: int (4字节) 应对齐到4的倍数地址(如0x00, 0x04, 0x…...

小白工具视频转MPG, 功能丰富齐全,无需下载软件,在线使用,超实用
在视频格式转换需求日益多样的今天,小白工具网的在线视频转 MPG 功能https://www.xiaobaitool.net/videos/convert-to-mpg/ )脱颖而出,凭借其出色特性,成为众多用户处理视频格式转换的优质选择。 从格式兼容性来看,它支…...
跟着deepseek学golang--认识golang
文章目录 一、Golang核心优势1. 极简部署方式生产案例:依赖管理:容器实践: 2. 静态类型系统类型安全示例:性能优势:代码重构: 3. 语言级并发支持GMP调度模型实例&…...
目前市面上知名的数据采集器
程序员爱自己动手打造一切,但这样离钱就会比较远。 市面上知名的数据采集工具 数据采集工具(也称为网络爬虫或数据抓取工具)在市场上有很多选择,以下是目前比较知名和广泛使用的工具分类介绍: 一、开源免费工具 Scra…...
问答页面支持拖拽和复制粘贴文件,MaxKB企业级AI助手v1.10.6 LTS版本发布
2025年4月24日,MaxKB开源企业级AI助手正式发布v1.10.6 LTS版本。这一版本主要进行了一些功能优化和问题修复。 功能优化 ■ 应用:文件上传支持上传其他自定义的文件类型,该类型文件需要自行写入函数解析; ■ 问答页面ÿ…...

day32 学习笔记
文章目录 前言一、霍夫变换二、标准霍夫变换三、统计概率霍夫变换四、霍夫圆变换 前言 通过今天的学习,我掌握了霍夫变换的基本原本原理及其在OpenCV中的应用方法 一、霍夫变换 霍夫变换是图像处理中的常用技术,主要用于检测图像中的直线,圆…...
二项分布详解:从基础到应用
二项分布详解:从基础到应用 目录 引言二项分布的定义概率质量函数及其证明期望与方差推导二项分布的重要性质常见应用场景与其他分布的关系知识梳理练习与思考 引言 概率论中,二项分布是最基础也是最常用的离散概率分布之一。它描述了在固定次数的独…...

CentOS 7上Memcached的安装、配置及高可用架构搭建
Memcached是一款高性能的分布式内存缓存系统,常用于加速动态Web应用的响应。本文将在CentOS 7上详细介绍Memcached的安装、配置,以及如何实现Memcached的高可用架构。 (1)、搭建memcached 主主复制架构 Memcached 的复制功能支持…...

如何让 HTML 文件嵌入另一个 HTML 文件:详解与实践
目录 一、为什么需要在HTML中嵌入其他HTML文件? 二、常用的方法概览 三、利用 1. 基本原理 2. 使用场景 3. 优缺点 4. 实践示例 5. 适用建议 四、利用JavaScript动态加载内容 1. 原理简介 2. 实现步骤 示例代码 3. 优缺点分析 4. 应用场景 5. 实践建…...
mac brew 无法找到php7.2 如何安装php7.2
mac brew 无法找到php7.2 如何安装php7.2 原因是升级过高版本的brew后已经不支持7.2了,但可以通过第三方工具来安装 brew tap shivammathur/php brew install shivammathur/php/php7.2标题安装完成后会提示以下信息: The php.ini and php-fpm.ini fil…...

人工智能与机器学习:Python从零实现逻辑回归模型
🧠 向所有学习者致敬! “学习不是装满一桶水,而是点燃一把火。” —— 叶芝 我的博客主页: https://lizheng.blog.csdn.net 🌐 欢迎点击加入AI人工智能社区! 🚀 让我们一起努力,共创…...

windows服务器及网络:搭建FTP服务器
前言:(各位大佬们,昨天太忙了,整得没有发布昨天那该写的那一篇,属实有点可惜的说QAQ,不过问题已经解决,我又回来啦) 今天我要介绍的是在Windows中关于搭建FTP服务器的流程与方法 注…...
Python学习之路(五)-接口API
在 Python 中结合数据库开发接口 API 通常使用 Web 框架(如 Flask 或 Django)和 ORM(对象关系映射)工具(如 SQLAlchemy 或 Django ORM)。以下是使用 Flask 和 SQLAlchemy 的详细步骤,展示如何结合数据库开发一个简单的 API。 使用 Flask 和 SQLAlchemy 开发 API 1. 安…...

欧拉计划 Project Euler56(幂的数字和)题解
欧拉计划 Project Euler 56 题解 题干思路code 题干 思路 直接暴力枚举即可,用c要模拟大数的乘法,否则会溢出 code // 972 #include <bits/stdc.h>using namespace std;using ll long long;string mul(const string &num1, int num2) {int…...

C++初窥门径
const关键字 一、const关键字 修饰成员变量 常成员变量:必须通过构造函数的初始化列表进行初始化,且初始化后不可修改。 示例: class Student { private: const int age; // 常成员变量 public: Student(string name, int age) : age(ag…...